cl/merkle_tree: add EIP-7495 ProgressiveContainer root - #23130
Conversation
| func MixInActiveFields(root [32]byte, activeFields []bool) ([32]byte, error) { | ||
| if len(activeFields) > 256 { | ||
| return [32]byte{}, errors.New("active fields exceed 256 bits") | ||
| } |
There was a problem hiding this comment.
MixInActiveFields is currently only checking len(activeFields) > 256. But as per EIP-7495 spec, an active_fields configuration cannot be empty (len == 0) and cannot end with a 0 bit (!activeFields[len-1]).
Right now ProgressiveContainerRoot validates these edge cases, but since MixInActiveFields is exported publicly, if someone calls it directly with something like []bool{true, false} or empty slice, it silently hashes without giving error.
Can we add these checks here as well?
if len(activeFields) == 0 {
return [32]byte{}, errors.New("active fields cannot be empty")
}
if !activeFields[len(activeFields)-1] {
return [32]byte{}, errors.New("active fields must end with an active field")
}| return [32]byte{}, errors.New("active field count does not match field roots") | ||
| } | ||
|
|
||
| expandedRoots := make([][32]byte, len(activeFields)) |
There was a problem hiding this comment.
here make([][32]byte, len(activeFields)) will allocate on heap for every call.
In actual execution / consensus paths, almost all progressive containers have ≤ 32 fields (for example ExecutionPayload in EIP-7807 has only 18 fields).
We can do one small optimization here: use a small stack array var stackRoots [32][32]byte for len(activeFields) <= 32 so we completely avoid heap allocations on hot paths (similar to maxStackLeaves = 32 in merkle_root.go).
Something like:
var stackRoots [32][32]byte
var expandedRoots [][32]byte
if len(activeFields) <= 32 {
expandedRoots = stackRoots[:len(activeFields)]
} else {
expandedRoots = make([][32]byte, len(activeFields))
}This will save GC pressure when processing blocks repeatedly.
| activeFieldCount := 0 | ||
| for _, active := range activeFields { | ||
| if active { | ||
| activeFieldCount++ | ||
| } | ||
| } | ||
| if activeFieldCount != len(fieldRoots) { | ||
| return [32]byte{}, errors.New("active field count does not match field roots") | ||
| } |
There was a problem hiding this comment.
Also here, currently we are iterating over activeFields 3 times in total (first time to count 1s, second time to populate expandedRoots, and third time inside MixInActiveFields).
We can actually merge this count validation directly into the expandedRoots loop in a single pass:
fieldIndex := 0
for i, active := range activeFields {
if active {
if fieldIndex >= len(fieldRoots) {
return [32]byte{}, errors.New("active field count does not match field roots")
}
expandedRoots[i] = fieldRoots[fieldIndex]
fieldIndex++
}
}
if fieldIndex != len(fieldRoots) {
return [32]byte{}, errors.New("active field count does not match field roots")
}That way loop pass is reduced and it also fails fast if fieldRoots count is less than active bits.
| } | ||
| } | ||
|
|
||
| func TestMixInActiveFieldsReferenceVectors(t *testing.T) { |
There was a problem hiding this comment.
here, all current reference vector tests (sparseFields, boundaryFields, etc.), field index 0 is always set to true.
Could we add a small unit test where field index 0 is inactive (activeFields = []bool{false, true, true})? Just to ensure zero-filling at leaf position 0 / gindex 4 works as expected when the first field itself is absent.
Summary
Add
ProgressiveContainerRootfollowing EIP-7495.The helper:
MerkleizeProgressivewithMixInActiveFieldsactive_fieldsconfigurationsTests cover the EIP-7807 18-field layout, sparse fields, the 256-bit boundary, invalid configurations, and input immutability.
References
ethereum/EIPs@c81d843b3f8aa839fe42911c5b6e501c7d2940a3ethereum/remerkleable@2f0baeef0082d4278acaef7d822deb7009d7db7eethereum/EIPs@75d7bc2c20a91ec017d147f400d6bbf767843e2cReference vectors were generated from the pinned
remerkleablerevision.Testing
go test ./cl/merkle_tree -run '^TestProgressiveContainerRoot' -count=1go test ./cl/merkle_tree -count=1go test -race ./cl/merkle_tree -count=1go tool golangci-lint run --config ./.golangci.yml ./cl/merkle_tree/...make erigon integrationDepends on #22528.