diff --git a/core/types/bal/bal.go b/core/types/bal/bal.go
new file mode 100644
index 00000000000..fca54f7681f
--- /dev/null
+++ b/core/types/bal/bal.go
@@ -0,0 +1,182 @@
+// Copyright 2025 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package bal
+
+import (
+ "bytes"
+ "maps"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/holiman/uint256"
+)
+
+// CodeChange contains the runtime bytecode deployed at an address and the
+// transaction index where the deployment took place.
+type CodeChange struct {
+ TxIndex uint16
+ Code []byte `json:"code,omitempty"`
+}
+
+// ConstructionAccountAccess contains post-block account state for mutations as well as
+// all storage keys that were read during execution. It is used when building block
+// access list during execution.
+type ConstructionAccountAccess struct {
+ // StorageWrites is the post-state values of an account's storage slots
+ // that were modified in a block, keyed by the slot key and the tx index
+ // where the modification occurred.
+ StorageWrites map[common.Hash]map[uint16]common.Hash `json:"storageWrites,omitempty"`
+
+ // StorageReads is the set of slot keys that were accessed during block
+ // execution.
+ //
+ // Storage slots which are both read and written (with changed values)
+ // appear only in StorageWrites.
+ StorageReads map[common.Hash]struct{} `json:"storageReads,omitempty"`
+
+ // BalanceChanges contains the post-transaction balances of an account,
+ // keyed by transaction indices where it was changed.
+ BalanceChanges map[uint16]*uint256.Int `json:"balanceChanges,omitempty"`
+
+ // NonceChanges contains the post-state nonce values of an account keyed
+ // by tx index.
+ NonceChanges map[uint16]uint64 `json:"nonceChanges,omitempty"`
+
+ // CodeChange is only set for contract accounts which were deployed in
+ // the block.
+ CodeChange *CodeChange `json:"codeChange,omitempty"`
+}
+
+// NewConstructionAccountAccess initializes the account access object.
+func NewConstructionAccountAccess() *ConstructionAccountAccess {
+ return &ConstructionAccountAccess{
+ StorageWrites: make(map[common.Hash]map[uint16]common.Hash),
+ StorageReads: make(map[common.Hash]struct{}),
+ BalanceChanges: make(map[uint16]*uint256.Int),
+ NonceChanges: make(map[uint16]uint64),
+ }
+}
+
+// ConstructionBlockAccessList contains post-block modified state and some state accessed
+// in execution (account addresses and storage keys).
+type ConstructionBlockAccessList struct {
+ Accounts map[common.Address]*ConstructionAccountAccess
+}
+
+// NewConstructionBlockAccessList instantiates an empty access list.
+func NewConstructionBlockAccessList() ConstructionBlockAccessList {
+ return ConstructionBlockAccessList{
+ Accounts: make(map[common.Address]*ConstructionAccountAccess),
+ }
+}
+
+// AccountRead records the address of an account that has been read during execution.
+func (b *ConstructionBlockAccessList) AccountRead(addr common.Address) {
+ if _, ok := b.Accounts[addr]; !ok {
+ b.Accounts[addr] = NewConstructionAccountAccess()
+ }
+}
+
+// StorageRead records a storage key read during execution.
+func (b *ConstructionBlockAccessList) StorageRead(address common.Address, key common.Hash) {
+ if _, ok := b.Accounts[address]; !ok {
+ b.Accounts[address] = NewConstructionAccountAccess()
+ }
+ if _, ok := b.Accounts[address].StorageWrites[key]; ok {
+ return
+ }
+ b.Accounts[address].StorageReads[key] = struct{}{}
+}
+
+// StorageWrite records the post-transaction value of a mutated storage slot.
+// The storage slot is removed from the list of read slots.
+func (b *ConstructionBlockAccessList) StorageWrite(txIdx uint16, address common.Address, key, value common.Hash) {
+ if _, ok := b.Accounts[address]; !ok {
+ b.Accounts[address] = NewConstructionAccountAccess()
+ }
+ if _, ok := b.Accounts[address].StorageWrites[key]; !ok {
+ b.Accounts[address].StorageWrites[key] = make(map[uint16]common.Hash)
+ }
+ b.Accounts[address].StorageWrites[key][txIdx] = value
+
+ delete(b.Accounts[address].StorageReads, key)
+}
+
+// CodeChange records the code of a newly-created contract.
+func (b *ConstructionBlockAccessList) CodeChange(address common.Address, txIndex uint16, code []byte) {
+ if _, ok := b.Accounts[address]; !ok {
+ b.Accounts[address] = NewConstructionAccountAccess()
+ }
+ b.Accounts[address].CodeChange = &CodeChange{
+ TxIndex: txIndex,
+ Code: bytes.Clone(code),
+ }
+}
+
+// NonceChange records tx post-state nonce of any contract-like accounts whose
+// nonce was incremented.
+func (b *ConstructionBlockAccessList) NonceChange(address common.Address, txIdx uint16, postNonce uint64) {
+ if _, ok := b.Accounts[address]; !ok {
+ b.Accounts[address] = NewConstructionAccountAccess()
+ }
+ b.Accounts[address].NonceChanges[txIdx] = postNonce
+}
+
+// BalanceChange records the post-transaction balance of an account whose
+// balance changed.
+func (b *ConstructionBlockAccessList) BalanceChange(txIdx uint16, address common.Address, balance *uint256.Int) {
+ if _, ok := b.Accounts[address]; !ok {
+ b.Accounts[address] = NewConstructionAccountAccess()
+ }
+ b.Accounts[address].BalanceChanges[txIdx] = balance.Clone()
+}
+
+// PrettyPrint returns a human-readable representation of the access list
+func (b *ConstructionBlockAccessList) PrettyPrint() string {
+ enc := b.toEncodingObj()
+ return enc.PrettyPrint()
+}
+
+// Copy returns a deep copy of the access list.
+func (b *ConstructionBlockAccessList) Copy() *ConstructionBlockAccessList {
+ res := NewConstructionBlockAccessList()
+ for addr, aa := range b.Accounts {
+ var aaCopy ConstructionAccountAccess
+
+ slotWrites := make(map[common.Hash]map[uint16]common.Hash, len(aa.StorageWrites))
+ for key, m := range aa.StorageWrites {
+ slotWrites[key] = maps.Clone(m)
+ }
+ aaCopy.StorageWrites = slotWrites
+ aaCopy.StorageReads = maps.Clone(aa.StorageReads)
+
+ balances := make(map[uint16]*uint256.Int, len(aa.BalanceChanges))
+ for index, balance := range aa.BalanceChanges {
+ balances[index] = balance.Clone()
+ }
+ aaCopy.BalanceChanges = balances
+ aaCopy.NonceChanges = maps.Clone(aa.NonceChanges)
+
+ if aa.CodeChange != nil {
+ aaCopy.CodeChange = &CodeChange{
+ TxIndex: aa.CodeChange.TxIndex,
+ Code: bytes.Clone(aa.CodeChange.Code),
+ }
+ }
+ res.Accounts[addr] = &aaCopy
+ }
+ return &res
+}
diff --git a/core/types/bal/bal_encoding.go b/core/types/bal/bal_encoding.go
new file mode 100644
index 00000000000..d7d08801b11
--- /dev/null
+++ b/core/types/bal/bal_encoding.go
@@ -0,0 +1,344 @@
+// Copyright 2025 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package bal
+
+import (
+ "bytes"
+ "cmp"
+ "errors"
+ "fmt"
+ "io"
+ "maps"
+ "slices"
+ "strings"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/ethereum/go-ethereum/rlp"
+ "github.com/holiman/uint256"
+)
+
+//go:generate go run github.com/ethereum/go-ethereum/rlp/rlpgen -out bal_encoding_rlp_generated.go -type BlockAccessList -decoder
+
+// These are objects used as input for the access list encoding. They mirror
+// the spec format.
+
+// BlockAccessList is the encoding format of ConstructionBlockAccessList.
+type BlockAccessList struct {
+ Accesses []AccountAccess `ssz-max:"300000"`
+}
+
+// Validate returns an error if the contents of the access list are not ordered
+// according to the spec or any code changes are contained which exceed protocol
+// max code size.
+func (e *BlockAccessList) Validate() error {
+ if !slices.IsSortedFunc(e.Accesses, func(a, b AccountAccess) int {
+ return bytes.Compare(a.Address[:], b.Address[:])
+ }) {
+ return errors.New("block access list accounts not in lexicographic order")
+ }
+ for _, entry := range e.Accesses {
+ if err := entry.validate(); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// Hash computes the keccak256 hash of the access list
+func (e *BlockAccessList) Hash() common.Hash {
+ var enc bytes.Buffer
+ err := e.EncodeRLP(&enc)
+ if err != nil {
+ // errors here are related to BAL values exceeding maximum size defined
+ // by the spec. Hard-fail because these cases are not expected to be hit
+ // under reasonable conditions.
+ panic(err)
+ }
+ return crypto.Keccak256Hash(enc.Bytes())
+}
+
+// encodeBalance encodes the provided balance into 16-bytes.
+func encodeBalance(val *uint256.Int) [16]byte {
+ valBytes := val.Bytes()
+ if len(valBytes) > 16 {
+ panic("can't encode value that is greater than 16 bytes in size")
+ }
+ var enc [16]byte
+ copy(enc[16-len(valBytes):], valBytes[:])
+ return enc
+}
+
+// encodingBalanceChange is the encoding format of BalanceChange.
+type encodingBalanceChange struct {
+ TxIdx uint16 `ssz-size:"2"`
+ Balance [16]byte `ssz-size:"16"`
+}
+
+// encodingAccountNonce is the encoding format of NonceChange.
+type encodingAccountNonce struct {
+ TxIdx uint16 `ssz-size:"2"`
+ Nonce uint64 `ssz-size:"8"`
+}
+
+// encodingStorageWrite is the encoding format of StorageWrites.
+type encodingStorageWrite struct {
+ TxIdx uint16
+ ValueAfter [32]byte `ssz-size:"32"`
+}
+
+// encodingStorageWrite is the encoding format of SlotWrites.
+type encodingSlotWrites struct {
+ Slot [32]byte `ssz-size:"32"`
+ Accesses []encodingStorageWrite `ssz-max:"300000"`
+}
+
+// validate returns an instance of the encoding-representation slot writes in
+// working representation.
+func (e *encodingSlotWrites) validate() error {
+ if slices.IsSortedFunc(e.Accesses, func(a, b encodingStorageWrite) int {
+ return cmp.Compare[uint16](a.TxIdx, b.TxIdx)
+ }) {
+ return nil
+ }
+ return errors.New("storage write tx indices not in order")
+}
+
+// AccountAccess is the encoding format of ConstructionAccountAccess.
+type AccountAccess struct {
+ Address [20]byte `ssz-size:"20"` // 20-byte Ethereum address
+ StorageWrites []encodingSlotWrites `ssz-max:"300000"` // Storage changes (slot -> [tx_index -> new_value])
+ StorageReads [][32]byte `ssz-max:"300000"` // Read-only storage keys
+ BalanceChanges []encodingBalanceChange `ssz-max:"300000"` // Balance changes ([tx_index -> post_balance])
+ NonceChanges []encodingAccountNonce `ssz-max:"300000"` // Nonce changes ([tx_index -> new_nonce])
+ Code []CodeChange `ssz-max:"1"` // Code changes ([tx_index -> new_code])
+}
+
+// validate converts the account accesses out of encoding format.
+// If any of the keys in the encoding object are not ordered according to the
+// spec, an error is returned.
+func (e *AccountAccess) validate() error {
+ // Check the storage write slots are sorted in order
+ if !slices.IsSortedFunc(e.StorageWrites, func(a, b encodingSlotWrites) int {
+ return bytes.Compare(a.Slot[:], b.Slot[:])
+ }) {
+ return errors.New("storage writes slots not in lexicographic order")
+ }
+ for _, write := range e.StorageWrites {
+ if err := write.validate(); err != nil {
+ return err
+ }
+ }
+
+ // Check the storage read slots are sorted in order
+ if !slices.IsSortedFunc(e.StorageReads, func(a, b [32]byte) int {
+ return bytes.Compare(a[:], b[:])
+ }) {
+ return errors.New("storage read slots not in lexicographic order")
+ }
+
+ // Check the balance changes are sorted in order
+ if !slices.IsSortedFunc(e.BalanceChanges, func(a, b encodingBalanceChange) int {
+ return cmp.Compare[uint16](a.TxIdx, b.TxIdx)
+ }) {
+ return errors.New("balance changes not in ascending order by tx index")
+ }
+
+ // Check the nonce changes are sorted in order
+ if !slices.IsSortedFunc(e.NonceChanges, func(a, b encodingAccountNonce) int {
+ return cmp.Compare[uint16](a.TxIdx, b.TxIdx)
+ }) {
+ return errors.New("nonce changes not in ascending order by tx index")
+ }
+
+ // Convert code change
+ if len(e.Code) == 1 {
+ if len(e.Code[0].Code) > params.MaxCodeSize {
+ return fmt.Errorf("code change contained oversized code")
+ }
+ }
+ return nil
+}
+
+// Copy returns a deep copy of the account access
+func (e *AccountAccess) Copy() AccountAccess {
+ res := AccountAccess{
+ Address: e.Address,
+ StorageReads: slices.Clone(e.StorageReads),
+ BalanceChanges: slices.Clone(e.BalanceChanges),
+ NonceChanges: slices.Clone(e.NonceChanges),
+ }
+ for _, storageWrite := range e.StorageWrites {
+ res.StorageWrites = append(res.StorageWrites, encodingSlotWrites{
+ Slot: storageWrite.Slot,
+ Accesses: slices.Clone(storageWrite.Accesses),
+ })
+ }
+ if len(e.Code) == 1 {
+ res.Code = []CodeChange{
+ {
+ e.Code[0].TxIndex,
+ bytes.Clone(e.Code[0].Code),
+ },
+ }
+ }
+ return res
+}
+
+// EncodeRLP returns the RLP-encoded access list
+func (b *ConstructionBlockAccessList) EncodeRLP(wr io.Writer) error {
+ return b.toEncodingObj().EncodeRLP(wr)
+}
+
+var _ rlp.Encoder = &ConstructionBlockAccessList{}
+
+// toEncodingObj creates an instance of the ConstructionAccountAccess of the type that is
+// used as input for the encoding.
+func (a *ConstructionAccountAccess) toEncodingObj(addr common.Address) AccountAccess {
+ res := AccountAccess{
+ Address: addr,
+ StorageWrites: make([]encodingSlotWrites, 0),
+ StorageReads: make([][32]byte, 0),
+ BalanceChanges: make([]encodingBalanceChange, 0),
+ NonceChanges: make([]encodingAccountNonce, 0),
+ Code: nil,
+ }
+
+ // Convert write slots
+ writeSlots := slices.Collect(maps.Keys(a.StorageWrites))
+ slices.SortFunc(writeSlots, common.Hash.Cmp)
+ for _, slot := range writeSlots {
+ var obj encodingSlotWrites
+ obj.Slot = slot
+
+ slotWrites := a.StorageWrites[slot]
+ obj.Accesses = make([]encodingStorageWrite, 0, len(slotWrites))
+
+ indices := slices.Collect(maps.Keys(slotWrites))
+ slices.SortFunc(indices, cmp.Compare[uint16])
+ for _, index := range indices {
+ obj.Accesses = append(obj.Accesses, encodingStorageWrite{
+ TxIdx: index,
+ ValueAfter: slotWrites[index],
+ })
+ }
+ res.StorageWrites = append(res.StorageWrites, obj)
+ }
+
+ // Convert read slots
+ readSlots := slices.Collect(maps.Keys(a.StorageReads))
+ slices.SortFunc(readSlots, common.Hash.Cmp)
+ for _, slot := range readSlots {
+ res.StorageReads = append(res.StorageReads, slot)
+ }
+
+ // Convert balance changes
+ balanceIndices := slices.Collect(maps.Keys(a.BalanceChanges))
+ slices.SortFunc(balanceIndices, cmp.Compare[uint16])
+ for _, idx := range balanceIndices {
+ res.BalanceChanges = append(res.BalanceChanges, encodingBalanceChange{
+ TxIdx: idx,
+ Balance: encodeBalance(a.BalanceChanges[idx]),
+ })
+ }
+
+ // Convert nonce changes
+ nonceIndices := slices.Collect(maps.Keys(a.NonceChanges))
+ slices.SortFunc(nonceIndices, cmp.Compare[uint16])
+ for _, idx := range nonceIndices {
+ res.NonceChanges = append(res.NonceChanges, encodingAccountNonce{
+ TxIdx: idx,
+ Nonce: a.NonceChanges[idx],
+ })
+ }
+
+ // Convert code change
+ if a.CodeChange != nil {
+ res.Code = []CodeChange{
+ {
+ a.CodeChange.TxIndex,
+ bytes.Clone(a.CodeChange.Code),
+ },
+ }
+ }
+ return res
+}
+
+// toEncodingObj returns an instance of the access list expressed as the type
+// which is used as input for the encoding/decoding.
+func (b *ConstructionBlockAccessList) toEncodingObj() *BlockAccessList {
+ var addresses []common.Address
+ for addr := range b.Accounts {
+ addresses = append(addresses, addr)
+ }
+ slices.SortFunc(addresses, common.Address.Cmp)
+
+ var res BlockAccessList
+ for _, addr := range addresses {
+ res.Accesses = append(res.Accesses, b.Accounts[addr].toEncodingObj(addr))
+ }
+ return &res
+}
+
+func (e *BlockAccessList) PrettyPrint() string {
+ var res bytes.Buffer
+ printWithIndent := func(indent int, text string) {
+ fmt.Fprintf(&res, "%s%s\n", strings.Repeat(" ", indent), text)
+ }
+ for _, accountDiff := range e.Accesses {
+ printWithIndent(0, fmt.Sprintf("%x:", accountDiff.Address))
+
+ printWithIndent(1, "storage writes:")
+ for _, sWrite := range accountDiff.StorageWrites {
+ printWithIndent(2, fmt.Sprintf("%x:", sWrite.Slot))
+ for _, access := range sWrite.Accesses {
+ printWithIndent(3, fmt.Sprintf("%d: %x", access.TxIdx, access.ValueAfter))
+ }
+ }
+
+ printWithIndent(1, "storage reads:")
+ for _, slot := range accountDiff.StorageReads {
+ printWithIndent(2, fmt.Sprintf("%x", slot))
+ }
+
+ printWithIndent(1, "balance changes:")
+ for _, change := range accountDiff.BalanceChanges {
+ balance := new(uint256.Int).SetBytes(change.Balance[:]).String()
+ printWithIndent(2, fmt.Sprintf("%d: %s", change.TxIdx, balance))
+ }
+
+ printWithIndent(1, "nonce changes:")
+ for _, change := range accountDiff.NonceChanges {
+ printWithIndent(2, fmt.Sprintf("%d: %d", change.TxIdx, change.Nonce))
+ }
+
+ if len(accountDiff.Code) > 0 {
+ printWithIndent(1, "code:")
+ printWithIndent(2, fmt.Sprintf("%d: %x", accountDiff.Code[0].TxIndex, accountDiff.Code[0].Code))
+ }
+ }
+ return res.String()
+}
+
+// Copy returns a deep copy of the access list
+func (e *BlockAccessList) Copy() (res BlockAccessList) {
+ for _, accountAccess := range e.Accesses {
+ res.Accesses = append(res.Accesses, accountAccess.Copy())
+ }
+ return
+}
diff --git a/core/types/bal/bal_encoding_rlp_generated.go b/core/types/bal/bal_encoding_rlp_generated.go
new file mode 100644
index 00000000000..0d523953290
--- /dev/null
+++ b/core/types/bal/bal_encoding_rlp_generated.go
@@ -0,0 +1,280 @@
+// Code generated by rlpgen. DO NOT EDIT.
+
+package bal
+
+import "github.com/ethereum/go-ethereum/rlp"
+import "io"
+
+func (obj *BlockAccessList) EncodeRLP(_w io.Writer) error {
+ w := rlp.NewEncoderBuffer(_w)
+ _tmp0 := w.List()
+ _tmp1 := w.List()
+ for _, _tmp2 := range obj.Accesses {
+ _tmp3 := w.List()
+ w.WriteBytes(_tmp2.Address[:])
+ _tmp4 := w.List()
+ for _, _tmp5 := range _tmp2.StorageWrites {
+ _tmp6 := w.List()
+ w.WriteBytes(_tmp5.Slot[:])
+ _tmp7 := w.List()
+ for _, _tmp8 := range _tmp5.Accesses {
+ _tmp9 := w.List()
+ w.WriteUint64(uint64(_tmp8.TxIdx))
+ w.WriteBytes(_tmp8.ValueAfter[:])
+ w.ListEnd(_tmp9)
+ }
+ w.ListEnd(_tmp7)
+ w.ListEnd(_tmp6)
+ }
+ w.ListEnd(_tmp4)
+ _tmp10 := w.List()
+ for _, _tmp11 := range _tmp2.StorageReads {
+ w.WriteBytes(_tmp11[:])
+ }
+ w.ListEnd(_tmp10)
+ _tmp12 := w.List()
+ for _, _tmp13 := range _tmp2.BalanceChanges {
+ _tmp14 := w.List()
+ w.WriteUint64(uint64(_tmp13.TxIdx))
+ w.WriteBytes(_tmp13.Balance[:])
+ w.ListEnd(_tmp14)
+ }
+ w.ListEnd(_tmp12)
+ _tmp15 := w.List()
+ for _, _tmp16 := range _tmp2.NonceChanges {
+ _tmp17 := w.List()
+ w.WriteUint64(uint64(_tmp16.TxIdx))
+ w.WriteUint64(_tmp16.Nonce)
+ w.ListEnd(_tmp17)
+ }
+ w.ListEnd(_tmp15)
+ _tmp18 := w.List()
+ for _, _tmp19 := range _tmp2.Code {
+ _tmp20 := w.List()
+ w.WriteUint64(uint64(_tmp19.TxIndex))
+ w.WriteBytes(_tmp19.Code)
+ w.ListEnd(_tmp20)
+ }
+ w.ListEnd(_tmp18)
+ w.ListEnd(_tmp3)
+ }
+ w.ListEnd(_tmp1)
+ w.ListEnd(_tmp0)
+ return w.Flush()
+}
+
+func (obj *BlockAccessList) DecodeRLP(dec *rlp.Stream) error {
+ var _tmp0 BlockAccessList
+ {
+ if _, err := dec.List(); err != nil {
+ return err
+ }
+ // Accesses:
+ var _tmp1 []AccountAccess
+ if _, err := dec.List(); err != nil {
+ return err
+ }
+ for dec.MoreDataInList() {
+ var _tmp2 AccountAccess
+ {
+ if _, err := dec.List(); err != nil {
+ return err
+ }
+ // Address:
+ var _tmp3 [20]byte
+ if err := dec.ReadBytes(_tmp3[:]); err != nil {
+ return err
+ }
+ _tmp2.Address = _tmp3
+ // StorageWrites:
+ var _tmp4 []encodingSlotWrites
+ if _, err := dec.List(); err != nil {
+ return err
+ }
+ for dec.MoreDataInList() {
+ var _tmp5 encodingSlotWrites
+ {
+ if _, err := dec.List(); err != nil {
+ return err
+ }
+ // Slot:
+ var _tmp6 [32]byte
+ if err := dec.ReadBytes(_tmp6[:]); err != nil {
+ return err
+ }
+ _tmp5.Slot = _tmp6
+ // Accesses:
+ var _tmp7 []encodingStorageWrite
+ if _, err := dec.List(); err != nil {
+ return err
+ }
+ for dec.MoreDataInList() {
+ var _tmp8 encodingStorageWrite
+ {
+ if _, err := dec.List(); err != nil {
+ return err
+ }
+ // TxIdx:
+ _tmp9, err := dec.Uint16()
+ if err != nil {
+ return err
+ }
+ _tmp8.TxIdx = _tmp9
+ // ValueAfter:
+ var _tmp10 [32]byte
+ if err := dec.ReadBytes(_tmp10[:]); err != nil {
+ return err
+ }
+ _tmp8.ValueAfter = _tmp10
+ if err := dec.ListEnd(); err != nil {
+ return err
+ }
+ }
+ _tmp7 = append(_tmp7, _tmp8)
+ }
+ if err := dec.ListEnd(); err != nil {
+ return err
+ }
+ _tmp5.Accesses = _tmp7
+ if err := dec.ListEnd(); err != nil {
+ return err
+ }
+ }
+ _tmp4 = append(_tmp4, _tmp5)
+ }
+ if err := dec.ListEnd(); err != nil {
+ return err
+ }
+ _tmp2.StorageWrites = _tmp4
+ // StorageReads:
+ var _tmp11 [][32]byte
+ if _, err := dec.List(); err != nil {
+ return err
+ }
+ for dec.MoreDataInList() {
+ var _tmp12 [32]byte
+ if err := dec.ReadBytes(_tmp12[:]); err != nil {
+ return err
+ }
+ _tmp11 = append(_tmp11, _tmp12)
+ }
+ if err := dec.ListEnd(); err != nil {
+ return err
+ }
+ _tmp2.StorageReads = _tmp11
+ // BalanceChanges:
+ var _tmp13 []encodingBalanceChange
+ if _, err := dec.List(); err != nil {
+ return err
+ }
+ for dec.MoreDataInList() {
+ var _tmp14 encodingBalanceChange
+ {
+ if _, err := dec.List(); err != nil {
+ return err
+ }
+ // TxIdx:
+ _tmp15, err := dec.Uint16()
+ if err != nil {
+ return err
+ }
+ _tmp14.TxIdx = _tmp15
+ // Balance:
+ var _tmp16 [16]byte
+ if err := dec.ReadBytes(_tmp16[:]); err != nil {
+ return err
+ }
+ _tmp14.Balance = _tmp16
+ if err := dec.ListEnd(); err != nil {
+ return err
+ }
+ }
+ _tmp13 = append(_tmp13, _tmp14)
+ }
+ if err := dec.ListEnd(); err != nil {
+ return err
+ }
+ _tmp2.BalanceChanges = _tmp13
+ // NonceChanges:
+ var _tmp17 []encodingAccountNonce
+ if _, err := dec.List(); err != nil {
+ return err
+ }
+ for dec.MoreDataInList() {
+ var _tmp18 encodingAccountNonce
+ {
+ if _, err := dec.List(); err != nil {
+ return err
+ }
+ // TxIdx:
+ _tmp19, err := dec.Uint16()
+ if err != nil {
+ return err
+ }
+ _tmp18.TxIdx = _tmp19
+ // Nonce:
+ _tmp20, err := dec.Uint64()
+ if err != nil {
+ return err
+ }
+ _tmp18.Nonce = _tmp20
+ if err := dec.ListEnd(); err != nil {
+ return err
+ }
+ }
+ _tmp17 = append(_tmp17, _tmp18)
+ }
+ if err := dec.ListEnd(); err != nil {
+ return err
+ }
+ _tmp2.NonceChanges = _tmp17
+ // Code:
+ var _tmp21 []CodeChange
+ if _, err := dec.List(); err != nil {
+ return err
+ }
+ for dec.MoreDataInList() {
+ var _tmp22 CodeChange
+ {
+ if _, err := dec.List(); err != nil {
+ return err
+ }
+ // TxIndex:
+ _tmp23, err := dec.Uint16()
+ if err != nil {
+ return err
+ }
+ _tmp22.TxIndex = _tmp23
+ // Code:
+ _tmp24, err := dec.Bytes()
+ if err != nil {
+ return err
+ }
+ _tmp22.Code = _tmp24
+ if err := dec.ListEnd(); err != nil {
+ return err
+ }
+ }
+ _tmp21 = append(_tmp21, _tmp22)
+ }
+ if err := dec.ListEnd(); err != nil {
+ return err
+ }
+ _tmp2.Code = _tmp21
+ if err := dec.ListEnd(); err != nil {
+ return err
+ }
+ }
+ _tmp1 = append(_tmp1, _tmp2)
+ }
+ if err := dec.ListEnd(); err != nil {
+ return err
+ }
+ _tmp0.Accesses = _tmp1
+ if err := dec.ListEnd(); err != nil {
+ return err
+ }
+ }
+ *obj = _tmp0
+ return nil
+}
diff --git a/core/types/bal/bal_test.go b/core/types/bal/bal_test.go
new file mode 100644
index 00000000000..29414e414e8
--- /dev/null
+++ b/core/types/bal/bal_test.go
@@ -0,0 +1,252 @@
+// Copyright 2025 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package bal
+
+import (
+ "bytes"
+ "cmp"
+ "reflect"
+ "slices"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/internal/testrand"
+ "github.com/ethereum/go-ethereum/rlp"
+ "github.com/holiman/uint256"
+)
+
+func equalBALs(a *BlockAccessList, b *BlockAccessList) bool {
+ if !reflect.DeepEqual(a, b) {
+ return false
+ }
+ return true
+}
+
+func makeTestConstructionBAL() *ConstructionBlockAccessList {
+ return &ConstructionBlockAccessList{
+ map[common.Address]*ConstructionAccountAccess{
+ common.BytesToAddress([]byte{0xff, 0xff}): {
+ StorageWrites: map[common.Hash]map[uint16]common.Hash{
+ common.BytesToHash([]byte{0x01}): {
+ 1: common.BytesToHash([]byte{1, 2, 3, 4}),
+ 2: common.BytesToHash([]byte{1, 2, 3, 4, 5, 6}),
+ },
+ common.BytesToHash([]byte{0x10}): {
+ 20: common.BytesToHash([]byte{1, 2, 3, 4}),
+ },
+ },
+ StorageReads: map[common.Hash]struct{}{
+ common.BytesToHash([]byte{1, 2, 3, 4, 5, 6, 7}): {},
+ },
+ BalanceChanges: map[uint16]*uint256.Int{
+ 1: uint256.NewInt(100),
+ 2: uint256.NewInt(500),
+ },
+ NonceChanges: map[uint16]uint64{
+ 1: 2,
+ 2: 6,
+ },
+ CodeChange: &CodeChange{
+ TxIndex: 0,
+ Code: common.Hex2Bytes("deadbeef"),
+ },
+ },
+ common.BytesToAddress([]byte{0xff, 0xff, 0xff}): {
+ StorageWrites: map[common.Hash]map[uint16]common.Hash{
+ common.BytesToHash([]byte{0x01}): {
+ 2: common.BytesToHash([]byte{1, 2, 3, 4, 5, 6}),
+ 3: common.BytesToHash([]byte{1, 2, 3, 4, 5, 6, 7, 8}),
+ },
+ common.BytesToHash([]byte{0x10}): {
+ 21: common.BytesToHash([]byte{1, 2, 3, 4, 5}),
+ },
+ },
+ StorageReads: map[common.Hash]struct{}{
+ common.BytesToHash([]byte{1, 2, 3, 4, 5, 6, 7, 8}): {},
+ },
+ BalanceChanges: map[uint16]*uint256.Int{
+ 2: uint256.NewInt(100),
+ 3: uint256.NewInt(500),
+ },
+ NonceChanges: map[uint16]uint64{
+ 1: 2,
+ },
+ },
+ },
+ }
+}
+
+// TestBALEncoding tests that a populated access list can be encoded/decoded correctly.
+func TestBALEncoding(t *testing.T) {
+ var buf bytes.Buffer
+ bal := makeTestConstructionBAL()
+ err := bal.EncodeRLP(&buf)
+ if err != nil {
+ t.Fatalf("encoding failed: %v\n", err)
+ }
+ var dec BlockAccessList
+ if err := dec.DecodeRLP(rlp.NewStream(bytes.NewReader(buf.Bytes()), 10000000)); err != nil {
+ t.Fatalf("decoding failed: %v\n", err)
+ }
+ if dec.Hash() != bal.toEncodingObj().Hash() {
+ t.Fatalf("encoded block hash doesn't match decoded")
+ }
+ if !equalBALs(bal.toEncodingObj(), &dec) {
+ t.Fatal("decoded BAL doesn't match")
+ }
+}
+
+func makeTestAccountAccess(sort bool) AccountAccess {
+ var (
+ storageWrites []encodingSlotWrites
+ storageReads [][32]byte
+ balances []encodingBalanceChange
+ nonces []encodingAccountNonce
+ )
+ for i := 0; i < 5; i++ {
+ slot := encodingSlotWrites{
+ Slot: testrand.Hash(),
+ }
+ for j := 0; j < 3; j++ {
+ slot.Accesses = append(slot.Accesses, encodingStorageWrite{
+ TxIdx: uint16(2 * j),
+ ValueAfter: testrand.Hash(),
+ })
+ }
+ if sort {
+ slices.SortFunc(slot.Accesses, func(a, b encodingStorageWrite) int {
+ return cmp.Compare[uint16](a.TxIdx, b.TxIdx)
+ })
+ }
+ storageWrites = append(storageWrites, slot)
+ }
+ if sort {
+ slices.SortFunc(storageWrites, func(a, b encodingSlotWrites) int {
+ return bytes.Compare(a.Slot[:], b.Slot[:])
+ })
+ }
+
+ for i := 0; i < 5; i++ {
+ storageReads = append(storageReads, testrand.Hash())
+ }
+ if sort {
+ slices.SortFunc(storageReads, func(a, b [32]byte) int {
+ return bytes.Compare(a[:], b[:])
+ })
+ }
+
+ for i := 0; i < 5; i++ {
+ balances = append(balances, encodingBalanceChange{
+ TxIdx: uint16(2 * i),
+ Balance: [16]byte(testrand.Bytes(16)),
+ })
+ }
+ if sort {
+ slices.SortFunc(balances, func(a, b encodingBalanceChange) int {
+ return cmp.Compare[uint16](a.TxIdx, b.TxIdx)
+ })
+ }
+
+ for i := 0; i < 5; i++ {
+ nonces = append(nonces, encodingAccountNonce{
+ TxIdx: uint16(2 * i),
+ Nonce: uint64(i + 100),
+ })
+ }
+ if sort {
+ slices.SortFunc(nonces, func(a, b encodingAccountNonce) int {
+ return cmp.Compare[uint16](a.TxIdx, b.TxIdx)
+ })
+ }
+
+ return AccountAccess{
+ Address: [20]byte(testrand.Bytes(20)),
+ StorageWrites: storageWrites,
+ StorageReads: storageReads,
+ BalanceChanges: balances,
+ NonceChanges: nonces,
+ Code: []CodeChange{
+ {
+ TxIndex: 100,
+ Code: testrand.Bytes(256),
+ },
+ },
+ }
+}
+
+func makeTestBAL(sort bool) BlockAccessList {
+ list := BlockAccessList{}
+ for i := 0; i < 5; i++ {
+ list.Accesses = append(list.Accesses, makeTestAccountAccess(sort))
+ }
+ if sort {
+ slices.SortFunc(list.Accesses, func(a, b AccountAccess) int {
+ return bytes.Compare(a.Address[:], b.Address[:])
+ })
+ }
+ return list
+}
+
+func TestBlockAccessListCopy(t *testing.T) {
+ list := makeTestBAL(true)
+ cpy := list.Copy()
+ cpyCpy := cpy.Copy()
+
+ if !reflect.DeepEqual(list, cpy) {
+ t.Fatal("block access mismatch")
+ }
+ if !reflect.DeepEqual(cpy, cpyCpy) {
+ t.Fatal("block access mismatch")
+ }
+
+ // Make sure the mutations on copy won't affect the origin
+ for _, aa := range cpyCpy.Accesses {
+ for i := 0; i < len(aa.StorageReads); i++ {
+ aa.StorageReads[i] = [32]byte(testrand.Bytes(32))
+ }
+ }
+ if !reflect.DeepEqual(list, cpy) {
+ t.Fatal("block access mismatch")
+ }
+}
+
+func TestBlockAccessListValidation(t *testing.T) {
+ // Validate the block access list after RLP decoding
+ enc := makeTestBAL(true)
+ if err := enc.Validate(); err != nil {
+ t.Fatalf("Unexpected validation error: %v", err)
+ }
+ var buf bytes.Buffer
+ if err := enc.EncodeRLP(&buf); err != nil {
+ t.Fatalf("Unexpected encoding error: %v", err)
+ }
+
+ var dec BlockAccessList
+ if err := dec.DecodeRLP(rlp.NewStream(bytes.NewReader(buf.Bytes()), 0)); err != nil {
+ t.Fatalf("Unexpected RLP-decode error: %v", err)
+ }
+ if err := dec.Validate(); err != nil {
+ t.Fatalf("Unexpected validation error: %v", err)
+ }
+
+ // Validate the derived block access list
+ cBAL := makeTestConstructionBAL()
+ listB := cBAL.toEncodingObj()
+ if err := listB.Validate(); err != nil {
+ t.Fatalf("Unexpected validation error: %v", err)
+ }
+}
diff --git a/go.mod b/go.mod
index c3b27d405ef..6b63450a91b 100644
--- a/go.mod
+++ b/go.mod
@@ -24,7 +24,7 @@ require (
github.com/ethereum/c-kzg-4844/v2 v2.1.0
github.com/ethereum/go-verkle v0.2.2
github.com/fatih/color v1.16.0
- github.com/ferranbt/fastssz v0.1.2
+ github.com/ferranbt/fastssz v0.1.4
github.com/fjl/gencodec v0.1.0
github.com/fsnotify/fsnotify v1.6.0
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff
@@ -101,6 +101,7 @@ require (
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/deepmap/oapi-codegen v1.6.0 // indirect
github.com/dlclark/regexp2 v1.7.0 // indirect
+ github.com/emicklei/dot v1.6.2 // indirect
github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 // indirect
github.com/getsentry/sentry-go v0.27.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
diff --git a/go.sum b/go.sum
index 6f31f96ec21..db59c742299 100644
--- a/go.sum
+++ b/go.sum
@@ -108,14 +108,16 @@ github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3 h1:+3HCtB74++ClLy8GgjU
github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4=
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM=
+github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A=
+github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s=
github.com/ethereum/c-kzg-4844/v2 v2.1.0 h1:gQropX9YFBhl3g4HYhwE70zq3IHFRgbbNPw0Shwzf5w=
github.com/ethereum/c-kzg-4844/v2 v2.1.0/go.mod h1:TC48kOKjJKPbN7C++qIgt0TJzZ70QznYR7Ob+WXl57E=
github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8=
github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
-github.com/ferranbt/fastssz v0.1.2 h1:Dky6dXlngF6Qjc+EfDipAkE83N5I5DE68bY6O0VLNPk=
-github.com/ferranbt/fastssz v0.1.2/go.mod h1:X5UPrE2u1UJjxHA8X54u04SBwdAQjG2sFtWs39YxyWs=
+github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY=
+github.com/ferranbt/fastssz v0.1.4/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg=
github.com/fjl/gencodec v0.1.0 h1:B3K0xPfc52cw52BBgUbSPxYo+HlLfAgWMVKRWXUXBcs=
github.com/fjl/gencodec v0.1.0/go.mod h1:Um1dFHPONZGTHog1qD1NaWjXJW/SPB38wPv0O8uZ2fI=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@@ -316,8 +318,8 @@ github.com/protolambda/zrnt v0.34.1 h1:qW55rnhZJDnOb3TwFiFRJZi3yTXFrJdGOFQM7vCwY
github.com/protolambda/zrnt v0.34.1/go.mod h1:A0fezkp9Tt3GBLATSPIbuY4ywYESyAuc/FFmPKg8Lqs=
github.com/protolambda/ztyp v0.2.2 h1:rVcL3vBu9W/aV646zF6caLS/dyn9BN8NYiuJzicLNyY=
github.com/protolambda/ztyp v0.2.2/go.mod h1:9bYgKGqg3wJqT9ac1gI2hnVb0STQq7p/1lapqrqY1dU=
-github.com/prysmaticlabs/gohashtree v0.0.1-alpha.0.20220714111606-acbb2962fb48 h1:cSo6/vk8YpvkLbk9v3FO97cakNmUoxwi2KMP8hd5WIw=
-github.com/prysmaticlabs/gohashtree v0.0.1-alpha.0.20220714111606-acbb2962fb48/go.mod h1:4pWaT30XoEx1j8KNJf3TV+E3mQkaufn7mf+jRNb/Fuk=
+github.com/prysmaticlabs/gohashtree v0.0.4-beta h1:H/EbCuXPeTV3lpKeXGPpEV9gsUpkqOOVnWapUyeWro4=
+github.com/prysmaticlabs/gohashtree v0.0.4-beta/go.mod h1:BFdtALS+Ffhg3lGQIHv9HDWuHS8cTvHZzrHWxwOtGOs=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=