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

all: refactor so NewBlock, WithBody take types.Body #29482

Merged
merged 3 commits into from Apr 30, 2024

Conversation

lightclient
Copy link
Member

Something we've discussed a bit in the past; thought I would see if there is interest in it now before we look into merging PRs for 6110 and 7002 -- they both add new quantities to the body.

@lightclient lightclient changed the title all: refactor so NewBlock, NewBody take types.Body all: refactor so NewBlock, WithBody take types.Body Apr 7, 2024
core/types/block.go Outdated Show resolved Hide resolved
core/types/block.go Outdated Show resolved Hide resolved
core/types/block.go Outdated Show resolved Hide resolved
core/types/block.go Outdated Show resolved Hide resolved
@rjl493456442
Copy link
Member

generally i think the idea is pretty good.

core/types/block.go Outdated Show resolved Hide resolved
@lightclient
Copy link
Member Author

Thanks @rjl493456442 and @fjl. PR updated based on your feedback.

@@ -220,10 +221,20 @@ type extblock struct {
// The values of TxHash, UncleHash, ReceiptHash and Bloom in header
// are ignored and set to values derived from the given txs, uncles
// and receipts.
func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, hasher TrieHasher) *Block {
b := &Block{header: CopyHeader(header)}
func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher TrieHasher) *Block {
Copy link
Member

Choose a reason for hiding this comment

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

@rjl493456442 noted that in WithBody, you passed a non-pointer Body. Either case might be fine, but maybe it should be consistent across the two methods.

Copy link
Member

Choose a reason for hiding this comment

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

Given that a Body is fairly small containing only slices, and you also have this strange check below to handle nil,it might be simpler to go with the struct.

Copy link
Member

Choose a reason for hiding this comment

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

Unrelated: it's a bit confusing when you should use NewBlock and when NewBlockWithHeader.WithBody. Perhaps we could consider tweaking the names of one or the other a bit to make it clearer that they do different things and serve different purposes. Otherwise IMO it's asking for it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Since we already handle Body as a pointer value in other places, I think making it a struct in NewBlock is going to be a bit annoying. This is seen mostly in implementations of consensus.Engine where FinalizeAndAssemble takes a pointer Body.

If you think it is worth turning all uses of Body over to struct values, I can do it. It just seems like it might actually be better to keep the pointer value given this.

Similarly with NewBlock and WithBody interfaces matching, if they need to match, they should probably both be *Body.

cc @fjl since I think it was his thought to convert to struct val.

Copy link
Member Author

Choose a reason for hiding this comment

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

Also agree on it being confusing when to use NewBlock vs. NewBlockWithHeader.WithBody. I think the answer is you use NewBlock when you need to compute the header accumulators with the body values and NewBlockWithHeader.WithBody when you need to recreate the block which has been disassembled (written to disk, received over the wire, etc).

Naming isn't perfect, but struggling to come up with something markedly better.

Copy link
Contributor

Choose a reason for hiding this comment

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

I still think it's a bit non-obvious, but it might suffice to update some method-docs.

Copy link
Contributor

Choose a reason for hiding this comment

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

The difference with NewBlock is that it would deep copy all inputs. It's meant to be used in tests, and that's also why it has a short name. Across production code, very few places actually create blocks, so we can live with a longer function name and we can also avoid some of the copying.

core/types/block.go Outdated Show resolved Hide resolved
block := types.NewBlockWithHeader(&header)
block = block.WithBody(transactions, nil)
block = block.WithWithdrawals(withdrawals)
block := types.NewBlockWithHeader(&header).WithBody(types.Body{Transactions: transactions, Withdrawals: withdrawals})
Copy link
Member

Choose a reason for hiding this comment

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

Why not the new NewBlock (with everything)?

Copy link
Member

Choose a reason for hiding this comment

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

I guess you don't have the receipts to recalculate header fields.

Copy link
Member Author

Choose a reason for hiding this comment

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

Exactly. Plus it might be a little weird to recalc the header fields when here we're trying to tease out our block def from a beacon block's execution payload. Should probably 1:1 the data and do a hash comparison somewhere to ensure integrity.

@karalabe
Copy link
Member

PR and general directions seems ok, just some open ended questions to debate.

@karalabe karalabe closed this Apr 11, 2024
@karalabe karalabe reopened this Apr 11, 2024
@ethereum ethereum deleted a comment from web3santa Apr 14, 2024
Copy link
Contributor

@holiman holiman left a comment

Choose a reason for hiding this comment

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

LGTM

@@ -220,10 +221,20 @@ type extblock struct {
// The values of TxHash, UncleHash, ReceiptHash and Bloom in header
// are ignored and set to values derived from the given txs, uncles
// and receipts.
func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, hasher TrieHasher) *Block {
b := &Block{header: CopyHeader(header)}
func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher TrieHasher) *Block {
Copy link
Contributor

Choose a reason for hiding this comment

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

I still think it's a bit non-obvious, but it might suffice to update some method-docs.

Comment on lines 457 to 456
// WithBody returns a copy of the block with the given transaction and uncle contents.
func (b *Block) WithBody(transactions []*Transaction, uncles []*Header) *Block {
block := &Block{
header: b.header,
transactions: make([]*Transaction, len(transactions)),
uncles: make([]*Header, len(uncles)),
withdrawals: b.withdrawals,
}
copy(block.transactions, transactions)
for i := range uncles {
block.uncles[i] = CopyHeader(uncles[i])
}
return block
}

// WithWithdrawals returns a copy of the block containing the given withdrawals.
func (b *Block) WithWithdrawals(withdrawals []*Withdrawal) *Block {
func (b *Block) WithBody(body Body) *Block {
Copy link
Contributor

Choose a reason for hiding this comment

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

Method doc should expand a bit. E.g.

// WithBody returns a new block, consisting of the orginal header (not copied) along
// with the body contents (which are deep-copied).
// OBS: The header-fields based on body derivatives (e..g txhash) are _not_ updated, and will
// mismatch if the body does not match the header.

It's a bit funny / curious that this method avoids copying the original header, but conscientiously copies the incoming body.

Copy link
Contributor

Choose a reason for hiding this comment

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

So the idea here is, header fields inside types.Block can't leak out because we copy their values in block accessors. We do need to copy the fields in the passed Body here because a later modification of the input data (e.g. uncle header values) could mess up the internal caches in Block.

@holiman
Copy link
Contributor

holiman commented Apr 17, 2024

This PR has a pretty subtle but scary bug, surfaced by this test (well, and many others):

state_processor_test.go:263: test 0:
        have "missing withdrawals in block body"
        want "could not apply tx 1 [0x0026256b3939ed97e2c4a6f3fce8ecf83bdcfa6d507c47838c308a1fb0436f62]: nonce too low: address 0x71562b71999873DB5b286dF957af199Ec94617F7, tx: 0 state: 1" 

Empty withdrawals become nil withdrawals on b, here, in block.go:

	if withdrawals == nil {
		b.header.WithdrawalsHash = nil
	} else if len(withdrawals) == 0 {
		b.header.WithdrawalsHash = &EmptyWithdrawalsHash
	} else {
		hash := DeriveSha(Withdrawals(withdrawals), hasher)
		b.header.WithdrawalsHash = &hash
		b.withdrawals = slices.Clone(withdrawals)
	}

Previously, it would (after some call dispatching), come to func (b *Block) WithWithdrawals and do

	block.withdrawals = make([]*Withdrawal, len(withdrawals))
	copy(block.withdrawals, withdrawals)

Copy link
Contributor

@holiman holiman left a comment

Choose a reason for hiding this comment

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

un-approved for now :)

@lightclient
Copy link
Member Author

That was a sneaky one! I updated so that NewBlock(..) now explicitly sets the body's withdrawals to an empty, non-nil slice of withdrawals. I looked into WithBody(..) to see if there might also be an issue there (like nil withdrawals ends up as non-nil somehow) but it looks like slices.Clone retains the nil-ness of the input. So cloning a nil withdrawals list will still return nil.

@holiman
Copy link
Contributor

holiman commented Apr 29, 2024

Needs a rebase

b.header.WithdrawalsHash = &h
hash := DeriveSha(Withdrawals(withdrawals), hasher)
b.header.WithdrawalsHash = &hash
b.withdrawals = slices.Clone(withdrawals)
Copy link
Contributor

Choose a reason for hiding this comment

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

Can't you just do this unconditioally? IIUC, slices.Clone respects nil vs empty

Copy link
Contributor

@holiman holiman left a comment

Choose a reason for hiding this comment

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

Seems ok to me

@fjl fjl added this to the 1.14.1 milestone Apr 30, 2024
@holiman holiman merged commit 2e8e35f into ethereum:master Apr 30, 2024
2 of 3 checks passed
fjl pushed a commit to fjl/go-ethereum that referenced this pull request May 21, 2024
…9482)

* all: refactor so NewBlock(..) and WithBody(..) take a types.Body

* core: fixup comments, remove txs != receipts panic

* core/types: add empty withdrawls to body if len == 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants