Skip to content

Commit 931fd59

Browse files
committed
ethclient: add GetBlockAccessList
1 parent 3703c08 commit 931fd59

2 files changed

Lines changed: 155 additions & 0 deletions

File tree

ethclient/block_access_list.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright 2026 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package ethclient
18+
19+
import (
20+
"context"
21+
22+
"github.com/ethereum/go-ethereum"
23+
"github.com/ethereum/go-ethereum/common"
24+
"github.com/ethereum/go-ethereum/common/hexutil"
25+
"github.com/ethereum/go-ethereum/rpc"
26+
)
27+
28+
// BlockAccessListEntry describes the state changes of a single account within
29+
// a block access list, keyed by the transaction that caused them.
30+
type BlockAccessListEntry struct {
31+
Address common.Address `json:"address"`
32+
BalanceChanges []BalanceChangeEntry `json:"balanceChanges"`
33+
CodeChanges []CodeChangeEntry `json:"codeChanges"`
34+
NonceChanges []NonceChangeEntry `json:"nonceChanges"`
35+
StorageChanges []StorageChangesEntry `json:"storageChanges"`
36+
StorageReads []common.Hash `json:"storageReads"`
37+
}
38+
39+
// BalanceChangeEntry records the post-state balance of an account at a block
40+
// access index.
41+
type BalanceChangeEntry struct {
42+
Index hexutil.Uint64 `json:"index"`
43+
Value *hexutil.Big `json:"value"`
44+
}
45+
46+
// CodeChangeEntry records the code deployed to an account at a block access
47+
// index.
48+
type CodeChangeEntry struct {
49+
Index hexutil.Uint64 `json:"index"`
50+
Code hexutil.Bytes `json:"code"`
51+
}
52+
53+
// NonceChangeEntry records the post-state nonce of an account at a block
54+
// access index.
55+
type NonceChangeEntry struct {
56+
Index hexutil.Uint64 `json:"index"`
57+
Value hexutil.Uint64 `json:"value"`
58+
}
59+
60+
// StorageChangeEntry records the post-state value of a storage slot at a
61+
// block access index.
62+
type StorageChangeEntry struct {
63+
Index hexutil.Uint64 `json:"index"`
64+
Value common.Hash `json:"value"`
65+
}
66+
67+
// StorageChangesEntry pairs a storage key with the writes performed on it.
68+
type StorageChangesEntry struct {
69+
Key common.Hash `json:"key"`
70+
Changes []StorageChangeEntry `json:"changes"`
71+
}
72+
73+
// GetBlockAccessList returns the block access list of the given block.
74+
func (ec *Client) GetBlockAccessList(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]BlockAccessListEntry, error) {
75+
var r []BlockAccessListEntry
76+
err := ec.c.CallContext(ctx, &r, "eth_getBlockAccessList", blockNrOrHash)
77+
if err == nil && r == nil {
78+
return nil, ethereum.NotFound
79+
}
80+
return r, err
81+
}

ethclient/ethclient_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"github.com/ethereum/go-ethereum"
3030
"github.com/ethereum/go-ethereum/accounts/abi"
3131
"github.com/ethereum/go-ethereum/common"
32+
"github.com/ethereum/go-ethereum/common/hexutil"
3233
"github.com/ethereum/go-ethereum/consensus/beacon"
3334
"github.com/ethereum/go-ethereum/consensus/ethash"
3435
"github.com/ethereum/go-ethereum/core"
@@ -1058,3 +1059,76 @@ func genesisAlloc() types.GenesisAlloc {
10581059
alloc[revertContractAddr] = types.Account{Code: revertCode}
10591060
return alloc
10601061
}
1062+
1063+
type blockAccessListTestService struct {
1064+
calls chan rpc.BlockNumberOrHash
1065+
}
1066+
1067+
func (s *blockAccessListTestService) GetBlockAccessList(ctx context.Context, block rpc.BlockNumberOrHash) ([]ethclient.BlockAccessListEntry, error) {
1068+
s.calls <- block
1069+
return []ethclient.BlockAccessListEntry{
1070+
{
1071+
Address: common.HexToAddress("0x000f3df6d732807ef1319fb7b8bb8522d0beac02"),
1072+
BalanceChanges: []ethclient.BalanceChangeEntry{},
1073+
CodeChanges: []ethclient.CodeChangeEntry{},
1074+
NonceChanges: []ethclient.NonceChangeEntry{},
1075+
StorageChanges: []ethclient.StorageChangesEntry{
1076+
{
1077+
Key: common.HexToHash("0x152f"),
1078+
Changes: []ethclient.StorageChangeEntry{
1079+
{Index: 0, Value: common.HexToHash("0x01")},
1080+
},
1081+
},
1082+
},
1083+
StorageReads: []common.Hash{},
1084+
},
1085+
{
1086+
Address: common.HexToAddress("0x8943545177806ed17b9f23f0a21ee5948ecaa776"),
1087+
BalanceChanges: []ethclient.BalanceChangeEntry{
1088+
{Index: 1, Value: (*hexutil.Big)(big.NewInt(1e18))},
1089+
},
1090+
CodeChanges: []ethclient.CodeChangeEntry{},
1091+
NonceChanges: []ethclient.NonceChangeEntry{{Index: 1, Value: 1}},
1092+
StorageChanges: []ethclient.StorageChangesEntry{},
1093+
StorageReads: []common.Hash{},
1094+
},
1095+
}, nil
1096+
}
1097+
1098+
func TestGetBlockAccessList(t *testing.T) {
1099+
srv := rpc.NewServer()
1100+
service := &blockAccessListTestService{calls: make(chan rpc.BlockNumberOrHash, 1)}
1101+
if err := srv.RegisterName("eth", service); err != nil {
1102+
t.Fatalf("failed to register service: %v", err)
1103+
}
1104+
defer srv.Stop()
1105+
1106+
client := rpc.DialInProc(srv)
1107+
defer client.Close()
1108+
1109+
ec := ethclient.NewClient(client)
1110+
defer ec.Close()
1111+
1112+
hash := common.HexToHash("0x01")
1113+
ref := rpc.BlockNumberOrHashWithHash(hash, true)
1114+
1115+
entries, err := ec.GetBlockAccessList(context.Background(), ref)
1116+
if err != nil {
1117+
t.Fatalf("GetBlockAccessList returned error: %v", err)
1118+
}
1119+
if len(entries) != 2 {
1120+
t.Fatalf("expected 2 entries, got %d", len(entries))
1121+
}
1122+
if entries[0].Address != common.HexToAddress("0x000f3df6d732807ef1319fb7b8bb8522d0beac02") {
1123+
t.Fatalf("unexpected first entry address: %s", entries[0].Address)
1124+
}
1125+
if len(entries[0].StorageChanges) != 1 || entries[0].StorageChanges[0].Changes[0].Index != 0 {
1126+
t.Fatalf("unexpected storage changes: %+v", entries[0].StorageChanges)
1127+
}
1128+
if entries[1].BalanceChanges[0].Value.ToInt().Cmp(big.NewInt(1e18)) != 0 {
1129+
t.Fatalf("unexpected balance change: %v", entries[1].BalanceChanges[0].Value)
1130+
}
1131+
if entries[1].NonceChanges[0].Value != 1 {
1132+
t.Fatalf("unexpected nonce change: %+v", entries[1].NonceChanges)
1133+
}
1134+
}

0 commit comments

Comments
 (0)