Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Block library refactoring #883

Merged
merged 29 commits into from
Oct 12, 2020
Merged

Block library refactoring #883

merged 29 commits into from
Oct 12, 2020

Conversation

holgerd77
Copy link
Member

This PR is a start on the block library refactoring, going very much along the structure introduced along #812 on the tx library.

WIP, non-block-dependent header tests pass, on the first refactoring round I kept values as Buffers and didn't adopt to domain-specific value representations as in the tx library refactor. This can be changed upon subsequent commits.

Noteworthy changes on the first commit:

  • Changed the genesis header number Buffer from being the empty Buffer to 0-Buffer, for RLP serialization this is now unpadded
  • Changed the default number Buffer from this legacy homestead Buffer to the empty Buffer (this can be discussed)

@codecov
Copy link

codecov bot commented Sep 21, 2020

Codecov Report

Merging #883 into master will decrease coverage by 0.30%.
The diff coverage is 74.36%.

Impacted file tree graph

Flag Coverage Δ
#account 92.85% <ø> (ø)
#block 77.05% <72.79%> (+0.12%) ⬆️
#blockchain 80.86% <92.00%> (+0.72%) ⬆️
#common 93.19% <ø> (+0.17%) ⬆️
#ethash 83.33% <100.00%> (+1.11%) ⬆️
#tx 90.38% <61.53%> (-2.70%) ⬇️
#vm 88.54% <93.10%> (+0.37%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

@holgerd77
Copy link
Member Author

Ok, have now also pushed the changes with the static factory methods to the block library, some fixes left and tests are not passing yet.

Will stop for today, if someone wants to continue during the day feel free to pick up, then please leave a short note here. Also feel free to evolve on all the design decisions made.

@jochem-brouwer
Copy link
Member

Will pick up here after some research regarding this number padding and partially solve #726

@jochem-brouwer
Copy link
Member

jochem-brouwer commented Sep 24, 2020

Rebased and fixed the build errors. The CI is very likely to fail. This was much more complex than I anticipated.

Some stuff which I noticed (might not be related to this PR):

Why are we using FakeTx in from-rpc.ts?

It seems that we are making Header readonly (good!). Do we want to make Block read only as well? This would make a lot of sense (but it is very likely to cause a lot of errors in the VM and Blockchain package).

Also related to read-only: while the Header might me read-only I think you can still edit the Buffers themselves, for instance by slicing the Buffer and then writing to this Buffer (points to same memory area). We might consider (maybe in a follow-up PR) to create read only buffers? @s1na

Will stop here for today.

@holgerd77
Copy link
Member Author

Hi @jochem-brouwer, great that you've picked up on this, I think these kind of PRs benefit if work on this is shared since input on design decisions can be much more diverse and in discussion! 👍 😄

Some notes on what I noticed during my initial implementation part:

  1. This new structure with the static factory methods reduces the number of format combinations, a block with associated txs and uncle headers can be initialized, so e.g. it is not possible any more to pass in a block dict together with tx in an RLP-serialized format. This is not necessarily a bad thing, but we should start adopting the consuming API calls in the other libraries relatively early on (even if this comes with the cost of some additional necessary iterations) to get a feeling on what kind of combinations are actually used in production and where it would hurt if a combination gets eliminated.

  2. The API structure with the demarcation towards the main constructor being having the tx and uncle header objects already initialized is far from finalized. So feel free to suggest other structures which might fit better, also curious to hear the opinion from @ryanio on that.

  3. And - closely related: my strong tendency (but open to be convinced from the alternative way) is to introduce BlockHeader -> Block inheritance, since block is calling into header on several occasions anyhow and this would structurally a natural fit I would say (there is no block without a header, but a header without a block). If we do this will along structurally shape decisions on 2.

Ok, so far. Will do some TypeScript transition work on the client today (just to let you know so that we won't be doing any parallel double work). 😄

@jochem-brouwer
Copy link
Member

Will leave a note if/when I continue.

Another note: before we merge this one, BLOCK_difficulty_GivenAsList should be removed from the skip tests list in tests/config.ts, as this issue should be fixed by now.

@jochem-brouwer
Copy link
Member

Will continue here.

@ryanio
Copy link
Contributor

ryanio commented Sep 28, 2020

awesome work here guys. @jochem-brouwer do you want to update the vars you identified as numbers from Buffer to BN? (gasLimit, difficulty, gasUsed, number, timestamp)

@jochem-brouwer
Copy link
Member

Will stop here.

test-block succeeded in 17a33a8, but are failing on 72b5b78. I checked the diff and have no idea why this happens.

ryanio added 3 commits October 7, 2020 17:46
freeze both block and header objects
use Address for coinbase
@ryanio
Copy link
Contributor

ryanio commented Oct 8, 2020

i am finished for today but will pick up again tomorrow morning 👍 everything seems to be coming along nicely.

@holgerd77 holgerd77 mentioned this pull request Oct 8, 2020
39 tasks
@holgerd77
Copy link
Member Author

Will do a review here now. Totally loved this "three people worked on the same PR seamlessly" work style here. 👍 😄

Copy link
Member Author

@holgerd77 holgerd77 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wow, wow, wow.

What a PR! 🙂

Can confirm that this looks great! 👍 🎉

Would give this a go, but please give this Common immutability question a dedicated look.

Please also wait with merging, I was settling out with @evertonfraga how we do the process here in relation to #886 (respectively you guys can of course also settle directly, we should just exchange on the merge -> rebase order here before we proceed).

Note: just realize that I actually can't approve myself anyhow since I am the original PR author, lol 😛 , so take this as an informal approval and feel free to approve yourself.

@@ -184,7 +184,7 @@ export class Block {
}
})

return stringError ? errors.join(' ') : errors.length === 0
return stringError ? errors : errors.length === 0
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, just re-read the discussion with @alcuadrado, this really makes a lot of sense to skip the join here.

if (txErrors !== '') {
throw new Error(txErrors)
if (txErrors.length > 0) {
throw new Error(`invalid transactions: ${txErrors.join(' ')}`)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

opts: BlockOptions = {},
) {
this.header = header
this.transactions = transactions
this.uncleHeaders = uncleHeaders
this._common = this.header._common

Object.freeze(this)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For understanding on further implications: does this Object.freeze() mean we can't do further modifications on e.g. the Common instance passed?

So would a subsequent call (e.g. in a VM context) common.setHardfork('byzantium') (or whatever) throw in this context or would this still work?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll have to give it a test, but I think the common object can still update, it just can't be set to a totally new instance (e.g. block._common = newCommon would fail)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I think this is the case according to here:

Note that values that are objects can still be modified, unless they are also frozen.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze


return rlpEncode ? rlp.encode(raw) : raw
serialize(): Buffer {
return rlp.encode(this.raw)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whew, such a strange call mixture with the raw() method before here. This is so much cleaner decoupled, great!

stateRoot ? checkBufferLength(toBuffer(stateRoot), 32) : zeros(32),
transactionsTrie ? checkBufferLength(toBuffer(transactionsTrie), 32) : KECCAK256_RLP,
receiptTrie ? checkBufferLength(toBuffer(receiptTrie), 32) : KECCAK256_RLP,
coinbase ? new Address(toBuffer(coinbase)) : Address.zero(),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super beautiful to see this new Address class in action in places like this now! 😀

public static genesis(blockData: BlockData = {}, opts: BlockOptions = {}) {
opts = { ...opts, initWithGenesisHeader: true }
return Block.fromBlockData(blockData, opts)
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, thought we can really keep this as simple as you proposed (or more or less), so optimally Block.genesis(common), and everyone needing something adjusted can just use the Block.fromBlockData() variant?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I wanted to too but in practice (in our test suite) it was helpful to accept custom overrides as well.

public static genesis(headerData: HeaderData = {}, opts: BlockOptions = {}) {
opts = { ...opts, initWithGenesisHeader: true }
return BlockHeader.fromHeaderData(headerData, opts)
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

@@ -6,16 +6,16 @@ import { Block } from '../src'
tape('[Block]: block functions', function (t) {
t.test('should test block initialization', function (st) {
const common = new Common({ chain: 'ropsten', hardfork: 'chainstart' })
const block1 = Block.fromBlockData({}, { common: common, initWithGenesisHeader: true })
st.ok(block1.hash().toString('hex'), 'block should initialize')
const genesis = Block.genesis({}, { common })
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, on the other hand (on the signature question): this also looks pretty simple already, so maybe not worth the further simplification, not sure.

const genesisBlock = new Block([header.raw, [], []], { common })
await blockchain.putGenesis(genesisBlock)
const header = testData.genesisBlockHeader
const genesis = Block.genesis({ header }, { common })
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scrolls down...

scrolls down...

Phew, I really wasn't aware that there were so many references / usages of this genesis functionality. 😃

@@ -48,7 +47,7 @@ tape('runBlockchain', (t) => {
t.test('should run with genesis block', async (st) => {
try {
const common = new Common({ chain: 'goerli', hardfork: 'chainstart' })
const genesis = createGenesis({ common })
const genesis = Block.genesis(undefined, { common })
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this undefined here instead of {}?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both work since the param defaults to {} if undefined.

@holgerd77
Copy link
Member Author

Update: ok, @evertonfraga has given his ok for the merge here. 😊

Copy link
Contributor

@ryanio ryanio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super great, thanks for the in-depth review @holgerd77!

@ryanio ryanio merged commit 7f3f4b4 into master Oct 12, 2020
@holgerd77 holgerd77 deleted the refactor-block-library branch October 12, 2020 20:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants