Merge remote-tracking branch 'origin/master' into eof-opcodes

This commit is contained in:
Marius van der Wijden 2025-03-13 19:19:11 +01:00
commit 0e115ad457
321 changed files with 66592 additions and 10506 deletions

View file

@ -29,7 +29,7 @@ jobs:
- name: Run linters
run: |
go run build/ci.go lint
go run build/ci.go check_tidy
go run build/ci.go check_generate
go run build/ci.go check_baddeps
build:

View file

@ -25,7 +25,7 @@ linters:
- gocheckcompilerdirectives
- reassign
- mirror
- tenv
- usetesting
### linters we tried and will not be using:
###
# - structcheck # lots of false positives

View file

@ -16,7 +16,7 @@ archives are published at https://geth.ethereum.org/downloads/.
For prerequisites and detailed build instructions please read the [Installation Instructions](https://geth.ethereum.org/docs/getting-started/installing-geth).
Building `geth` requires both a Go (version 1.22 or later) and a C compiler. You can install
Building `geth` requires both a Go (version 1.23 or later) and a C compiler. You can install
them using your favourite package manager. Once the dependencies are installed, run
```shell

View file

@ -24,7 +24,6 @@ for:
- image: Ubuntu
build_script:
- go run build/ci.go lint
- go run build/ci.go check_tidy
- go run build/ci.go check_generate
- go run build/ci.go check_baddeps
- go run build/ci.go install -dlgo

View file

@ -158,6 +158,7 @@ func (s *beaconBlockSync) updateEventFeed() {
s.chainHeadFeed.Send(types.ChainHeadEvent{
BeaconHead: optimistic.Attested.Header,
Block: execBlock,
ExecRequests: headBlock.ExecutionRequestsList(),
Finalized: finalizedHash,
})
}

View file

@ -100,6 +100,11 @@ func (ec *engineClient) callNewPayload(fork string, event types.ChainHeadEvent)
params = []any{execData}
)
switch fork {
case "electra":
method = "engine_newPayloadV4"
parentBeaconRoot := event.BeaconHead.ParentRoot
blobHashes := collectBlobHashes(event.Block)
params = append(params, blobHashes, parentBeaconRoot, event.ExecRequests)
case "deneb":
method = "engine_newPayloadV3"
parentBeaconRoot := event.BeaconHead.ParentRoot
@ -135,7 +140,7 @@ func (ec *engineClient) callForkchoiceUpdated(fork string, event types.ChainHead
var method string
switch fork {
case "deneb":
case "deneb", "electra":
method = "engine_forkchoiceUpdatedV3"
case "capella":
method = "engine_forkchoiceUpdatedV2"

View file

@ -43,7 +43,6 @@ var (
)
type CommitteeUpdate struct {
Version string
Update types.LightClientUpdate
NextSyncCommittee types.SerializedSyncCommittee
}
@ -81,9 +80,9 @@ func (u *CommitteeUpdate) UnmarshalJSON(input []byte) error {
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
u.Version = dec.Version
u.NextSyncCommittee = dec.Data.NextSyncCommittee
u.Update = types.LightClientUpdate{
Version: dec.Version,
AttestedHeader: types.SignedHeader{
Header: dec.Data.Header.Beacon,
Signature: dec.Data.SyncAggregate,
@ -206,7 +205,7 @@ func (api *BeaconLightApi) GetOptimisticUpdate() (types.OptimisticUpdate, error)
func decodeOptimisticUpdate(enc []byte) (types.OptimisticUpdate, error) {
var data struct {
Version string
Version string `json:"version"`
Data struct {
Attested jsonHeaderWithExecProof `json:"attested_header"`
Aggregate types.SyncAggregate `json:"sync_aggregate"`
@ -259,7 +258,7 @@ func (api *BeaconLightApi) GetFinalityUpdate() (types.FinalityUpdate, error) {
func decodeFinalityUpdate(enc []byte) (types.FinalityUpdate, error) {
var data struct {
Version string
Version string `json:"version"`
Data struct {
Attested jsonHeaderWithExecProof `json:"attested_header"`
Finalized jsonHeaderWithExecProof `json:"finalized_header"`
@ -289,6 +288,7 @@ func decodeFinalityUpdate(enc []byte) (types.FinalityUpdate, error) {
}
return types.FinalityUpdate{
Version: data.Version,
Attested: types.HeaderWithExecProof{
Header: data.Data.Attested.Beacon,
PayloadHeader: attestedExecHeader,
@ -355,6 +355,7 @@ func (api *BeaconLightApi) GetCheckpointData(checkpointHash common.Hash) (*types
// See data structure definition here:
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientbootstrap
type bootstrapData struct {
Version string `json:"version"`
Data struct {
Header jsonBeaconHeader `json:"header"`
Committee *types.SerializedSyncCommittee `json:"current_sync_committee"`
@ -374,6 +375,7 @@ func (api *BeaconLightApi) GetCheckpointData(checkpointHash common.Hash) (*types
return nil, fmt.Errorf("invalid checkpoint block header, have %v want %v", header.Hash(), checkpointHash)
}
checkpoint := &types.BootstrapData{
Version: data.Version,
Header: header,
CommitteeBranch: data.Data.CommitteeBranch,
CommitteeRoot: data.Data.Committee.Root(),
@ -395,7 +397,7 @@ func (api *BeaconLightApi) GetBeaconBlock(blockRoot common.Hash) (*types.BeaconB
}
var beaconBlockMessage struct {
Version string
Version string `json:"version"`
Data struct {
Message json.RawMessage `json:"message"`
}

View file

@ -39,10 +39,10 @@ func GenerateTestUpdate(config *params.ChainConfig, period uint64, committee, ne
var attestedHeader types.Header
if finalizedHeader {
update.FinalizedHeader = new(types.Header)
*update.FinalizedHeader, update.NextSyncCommitteeBranch = makeTestHeaderWithMerkleProof(types.SyncPeriodStart(period)+100, params.StateIndexNextSyncCommittee, merkle.Value(update.NextSyncCommitteeRoot))
attestedHeader, update.FinalityBranch = makeTestHeaderWithMerkleProof(types.SyncPeriodStart(period)+200, params.StateIndexFinalBlock, merkle.Value(update.FinalizedHeader.Hash()))
*update.FinalizedHeader, update.NextSyncCommitteeBranch = makeTestHeaderWithMerkleProof(types.SyncPeriodStart(period)+100, params.StateIndexNextSyncCommittee(""), merkle.Value(update.NextSyncCommitteeRoot))
attestedHeader, update.FinalityBranch = makeTestHeaderWithMerkleProof(types.SyncPeriodStart(period)+200, params.StateIndexFinalBlock(""), merkle.Value(update.FinalizedHeader.Hash()))
} else {
attestedHeader, update.NextSyncCommitteeBranch = makeTestHeaderWithMerkleProof(types.SyncPeriodStart(period)+2000, params.StateIndexNextSyncCommittee, merkle.Value(update.NextSyncCommitteeRoot))
attestedHeader, update.NextSyncCommitteeBranch = makeTestHeaderWithMerkleProof(types.SyncPeriodStart(period)+2000, params.StateIndexNextSyncCommittee(""), merkle.Value(update.NextSyncCommitteeRoot))
}
update.AttestedHeader = GenerateTestSignedHeader(attestedHeader, config, committee, attestedHeader.Slot+1, signerCount)
return update
@ -63,7 +63,7 @@ func GenerateTestSignedHeader(header types.Header, config *params.ChainConfig, c
}
func GenerateTestCheckpoint(period uint64, committee *types.SerializedSyncCommittee) *types.BootstrapData {
header, branch := makeTestHeaderWithMerkleProof(types.SyncPeriodStart(period)+200, params.StateIndexSyncCommittee, merkle.Value(committee.Root()))
header, branch := makeTestHeaderWithMerkleProof(types.SyncPeriodStart(period)+200, params.StateIndexSyncCommittee(""), merkle.Value(committee.Root()))
return &types.BootstrapData{
Header: header,
Committee: committee,

View file

@ -41,7 +41,8 @@ var (
AddFork("ALTAIR", 50, []byte{144, 0, 0, 112}).
AddFork("BELLATRIX", 100, []byte{144, 0, 0, 113}).
AddFork("CAPELLA", 56832, []byte{144, 0, 0, 114}).
AddFork("DENEB", 132608, []byte{144, 0, 0, 115})
AddFork("DENEB", 132608, []byte{144, 0, 0, 115}).
AddFork("ELECTRA", 222464, []byte{144, 0, 0, 116})
HoleskyLightConfig = (&ChainConfig{
GenesisValidatorsRoot: common.HexToHash("0x9143aa7c615a7f7115e2b6aac319c03529df8242ae705fba9df39b79c59fa8b1"),
@ -52,5 +53,6 @@ var (
AddFork("ALTAIR", 0, []byte{2, 1, 112, 0}).
AddFork("BELLATRIX", 0, []byte{3, 1, 112, 0}).
AddFork("CAPELLA", 256, []byte{4, 1, 112, 0}).
AddFork("DENEB", 29696, []byte{5, 1, 112, 0})
AddFork("DENEB", 29696, []byte{5, 1, 112, 0}).
AddFork("ELECTRA", 115968, []byte{6, 1, 112, 0})
)

View file

@ -36,11 +36,39 @@ const (
StateIndexBlockRoots = 37
StateIndexStateRoots = 38
StateIndexHistoricRoots = 39
StateIndexFinalBlock = 105
StateIndexSyncCommittee = 54
StateIndexNextSyncCommittee = 55
StateIndexFinalBlockOld = 105
StateIndexFinalBlockElectra = 169
StateIndexSyncCommitteeOld = 54
StateIndexSyncCommitteeElectra = 86
StateIndexNextSyncCommitteeOld = 55
StateIndexNextSyncCommitteeElectra = 87
StateIndexExecPayload = 56
StateIndexExecHead = 908
BodyIndexExecPayload = 25
)
func StateIndexFinalBlock(forkName string) uint64 {
switch forkName {
case "bellatrix", "capella", "deneb":
return StateIndexFinalBlockOld
default:
return StateIndexFinalBlockElectra
}
}
func StateIndexSyncCommittee(forkName string) uint64 {
switch forkName {
case "bellatrix", "capella", "deneb":
return StateIndexSyncCommitteeOld
default:
return StateIndexSyncCommitteeElectra
}
}
func StateIndexNextSyncCommittee(forkName string) uint64 {
switch forkName {
case "bellatrix", "capella", "deneb":
return StateIndexNextSyncCommitteeOld
default:
return StateIndexNextSyncCommitteeElectra
}
}

View file

@ -17,16 +17,21 @@
package types
import (
"bytes"
"encoding/json"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/protolambda/zrnt/eth2/beacon/capella"
zrntcommon "github.com/protolambda/zrnt/eth2/beacon/common"
"github.com/protolambda/zrnt/eth2/beacon/deneb"
"github.com/protolambda/zrnt/eth2/configs"
"github.com/protolambda/ztyp/codec"
"github.com/protolambda/ztyp/tree"
// beacon forks
"github.com/protolambda/zrnt/eth2/beacon/capella"
"github.com/protolambda/zrnt/eth2/beacon/deneb"
"github.com/protolambda/zrnt/eth2/beacon/electra"
)
type blockObject interface {
@ -43,10 +48,12 @@ type BeaconBlock struct {
func BlockFromJSON(forkName string, data []byte) (*BeaconBlock, error) {
var obj blockObject
switch forkName {
case "deneb":
obj = new(deneb.BeaconBlock)
case "capella":
obj = new(capella.BeaconBlock)
case "deneb":
obj = new(deneb.BeaconBlock)
case "electra":
obj = new(electra.BeaconBlock)
default:
return nil, fmt.Errorf("unsupported fork: %s", forkName)
}
@ -63,6 +70,8 @@ func NewBeaconBlock(obj blockObject) *BeaconBlock {
return &BeaconBlock{obj}
case *deneb.BeaconBlock:
return &BeaconBlock{obj}
case *electra.BeaconBlock:
return &BeaconBlock{obj}
default:
panic(fmt.Errorf("unsupported block type %T", obj))
}
@ -75,6 +84,8 @@ func (b *BeaconBlock) Slot() uint64 {
return uint64(obj.Slot)
case *deneb.BeaconBlock:
return uint64(obj.Slot)
case *electra.BeaconBlock:
return uint64(obj.Slot)
default:
panic(fmt.Errorf("unsupported block type %T", b.blockObj))
}
@ -84,9 +95,12 @@ func (b *BeaconBlock) Slot() uint64 {
func (b *BeaconBlock) ExecutionPayload() (*types.Block, error) {
switch obj := b.blockObj.(type) {
case *capella.BeaconBlock:
return convertPayload(&obj.Body.ExecutionPayload, &obj.ParentRoot)
return convertPayload(&obj.Body.ExecutionPayload, &obj.ParentRoot, nil)
case *deneb.BeaconBlock:
return convertPayload(&obj.Body.ExecutionPayload, &obj.ParentRoot)
return convertPayload(&obj.Body.ExecutionPayload, &obj.ParentRoot, nil)
case *electra.BeaconBlock:
requests := b.ExecutionRequestsList()
return convertPayload(&obj.Body.ExecutionPayload, &obj.ParentRoot, requests)
default:
panic(fmt.Errorf("unsupported block type %T", b.blockObj))
}
@ -99,6 +113,8 @@ func (b *BeaconBlock) Header() Header {
return headerFromZRNT(obj.Header(configs.Mainnet))
case *deneb.BeaconBlock:
return headerFromZRNT(obj.Header(configs.Mainnet))
case *electra.BeaconBlock:
return headerFromZRNT(obj.Header(configs.Mainnet))
default:
panic(fmt.Errorf("unsupported block type %T", b.blockObj))
}
@ -108,3 +124,38 @@ func (b *BeaconBlock) Header() Header {
func (b *BeaconBlock) Root() common.Hash {
return common.Hash(b.blockObj.HashTreeRoot(configs.Mainnet, tree.GetHashFn()))
}
// ExecutionRequestsList returns the execution layer requests of the block.
func (b *BeaconBlock) ExecutionRequestsList() [][]byte {
switch obj := b.blockObj.(type) {
case *capella.BeaconBlock, *deneb.BeaconBlock:
return nil
case *electra.BeaconBlock:
r := obj.Body.ExecutionRequests
return marshalRequests(configs.Mainnet,
&r.Deposits,
&r.Withdrawals,
&r.Consolidations,
)
default:
panic(fmt.Errorf("unsupported block type %T", b.blockObj))
}
}
func marshalRequests(spec *zrntcommon.Spec, items ...zrntcommon.SpecObj) (list [][]byte) {
var buf bytes.Buffer
list = [][]byte{}
for typ, data := range items {
buf.Reset()
buf.WriteByte(byte(typ))
w := codec.NewEncodingWriter(&buf)
if err := data.Serialize(spec, w); err != nil {
panic(err)
}
if buf.Len() == 1 {
continue // skip empty requests
}
list = append(list, bytes.Clone(buf.Bytes()))
}
return list
}

View file

@ -33,6 +33,27 @@ func TestBlockFromJSON(t *testing.T) {
wantBlockHash common.Hash
}
tests := []blocktest{
{
file: "block_electra_withdrawals.json",
version: "electra",
wantSlot: 151850,
wantBlockNumber: 141654,
wantBlockHash: common.HexToHash("0xf6730485a38be5ada3e110990a2c7adaabd2e8d4a49782134f1a8bfbc246a5d7"),
},
{
file: "block_electra_deposits.json",
version: "electra",
wantSlot: 151016,
wantBlockNumber: 140858,
wantBlockHash: common.HexToHash("0x1f2637170986346c7993d5adbadbebbf4c9ed89c6a4d2dff653db99c8c168076"),
},
{
file: "block_electra_consolidations.json",
version: "electra",
wantSlot: 151717,
wantBlockNumber: 141529,
wantBlockHash: common.HexToHash("0xc8807f7a1f96b0a073ff27065776dd21eff6b7e64079c60bffd33f690efbb330"),
},
{
file: "block_deneb.json",
version: "deneb",

View file

@ -22,10 +22,12 @@ import (
"github.com/ethereum/go-ethereum/beacon/merkle"
"github.com/ethereum/go-ethereum/common"
"github.com/protolambda/zrnt/eth2/beacon/capella"
zrntcommon "github.com/protolambda/zrnt/eth2/beacon/common"
"github.com/protolambda/zrnt/eth2/beacon/deneb"
"github.com/protolambda/ztyp/tree"
// beacon chain forks
"github.com/protolambda/zrnt/eth2/beacon/capella"
"github.com/protolambda/zrnt/eth2/beacon/deneb"
)
type headerObject interface {
@ -43,7 +45,7 @@ func ExecutionHeaderFromJSON(forkName string, data []byte) (*ExecutionHeader, er
switch forkName {
case "capella":
obj = new(capella.ExecutionPayloadHeader)
case "deneb":
case "deneb", "electra": // note: the payload type was not changed in electra
obj = new(deneb.ExecutionPayloadHeader)
default:
return nil, fmt.Errorf("unsupported fork: %s", forkName)

View file

@ -34,7 +34,7 @@ type payloadType interface {
}
// convertPayload converts a beacon chain execution payload to types.Block.
func convertPayload[T payloadType](payload T, parentRoot *zrntcommon.Root) (*types.Block, error) {
func convertPayload[T payloadType](payload T, parentRoot *zrntcommon.Root, requests [][]byte) (*types.Block, error) {
var (
header types.Header
transactions []*types.Transaction
@ -62,7 +62,10 @@ func convertPayload[T payloadType](payload T, parentRoot *zrntcommon.Root) (*typ
default:
panic("unsupported block type")
}
if requests != nil {
reqHash := types.CalcRequestsHash(requests)
header.RequestsHash = &reqHash
}
block := types.NewBlockWithHeader(&header).WithBody(types.Body{Transactions: transactions, Withdrawals: withdrawals})
if hash := block.Hash(); hash != expectedHash {
return nil, fmt.Errorf("sanity check failed, payload hash does not match (expected %x, got %x)", expectedHash, hash)

View file

@ -36,6 +36,7 @@ type HeadInfo struct {
// together with a proof through a beacon header and corresponding state.
// Note: BootstrapData is fetched from a server based on a known checkpoint hash.
type BootstrapData struct {
Version string
Header Header
CommitteeRoot common.Hash
Committee *SerializedSyncCommittee `rlp:"-"`
@ -47,7 +48,7 @@ func (c *BootstrapData) Validate() error {
if c.CommitteeRoot != c.Committee.Root() {
return errors.New("wrong committee root")
}
return merkle.VerifyProof(c.Header.StateRoot, params.StateIndexSyncCommittee, c.CommitteeBranch, merkle.Value(c.CommitteeRoot))
return merkle.VerifyProof(c.Header.StateRoot, params.StateIndexSyncCommittee(c.Version), c.CommitteeBranch, merkle.Value(c.CommitteeRoot))
}
// LightClientUpdate is a proof of the next sync committee root based on a header
@ -59,6 +60,7 @@ func (c *BootstrapData) Validate() error {
// See data structure definition here:
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientupdate
type LightClientUpdate struct {
Version string
AttestedHeader SignedHeader // Arbitrary header out of the period signed by the sync committee
NextSyncCommitteeRoot common.Hash // Sync committee of the next period advertised in the current one
NextSyncCommitteeBranch merkle.Values // Proof for the next period's sync committee
@ -79,11 +81,11 @@ func (update *LightClientUpdate) Validate() error {
if update.FinalizedHeader.SyncPeriod() != period {
return errors.New("finalized header is from different period")
}
if err := merkle.VerifyProof(update.AttestedHeader.Header.StateRoot, params.StateIndexFinalBlock, update.FinalityBranch, merkle.Value(update.FinalizedHeader.Hash())); err != nil {
if err := merkle.VerifyProof(update.AttestedHeader.Header.StateRoot, params.StateIndexFinalBlock(update.Version), update.FinalityBranch, merkle.Value(update.FinalizedHeader.Hash())); err != nil {
return fmt.Errorf("invalid finalized header proof: %w", err)
}
}
if err := merkle.VerifyProof(update.AttestedHeader.Header.StateRoot, params.StateIndexNextSyncCommittee, update.NextSyncCommitteeBranch, merkle.Value(update.NextSyncCommitteeRoot)); err != nil {
if err := merkle.VerifyProof(update.AttestedHeader.Header.StateRoot, params.StateIndexNextSyncCommittee(update.Version), update.NextSyncCommitteeBranch, merkle.Value(update.NextSyncCommitteeRoot)); err != nil {
return fmt.Errorf("invalid next sync committee proof: %w", err)
}
return nil
@ -194,6 +196,7 @@ func (u *OptimisticUpdate) Validate() error {
// See data structure definition here:
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientfinalityupdate
type FinalityUpdate struct {
Version string
Attested, Finalized HeaderWithExecProof
FinalityBranch merkle.Values
// Sync committee BLS signature aggregate
@ -223,7 +226,7 @@ func (u *FinalityUpdate) Validate() error {
if err := u.Finalized.Validate(); err != nil {
return err
}
return merkle.VerifyProof(u.Attested.StateRoot, params.StateIndexFinalBlock, u.FinalityBranch, merkle.Value(u.Finalized.Hash()))
return merkle.VerifyProof(u.Attested.StateRoot, params.StateIndexFinalBlock(u.Version), u.FinalityBranch, merkle.Value(u.Finalized.Hash()))
}
// ChainHeadEvent returns an authenticated execution payload associated with the
@ -232,5 +235,6 @@ func (u *FinalityUpdate) Validate() error {
type ChainHeadEvent struct {
BeaconHead Header
Block *ctypes.Block
Finalized common.Hash
ExecRequests [][]byte // execution layer requests (added in Electra)
Finalized common.Hash // latest finalized block hash
}

View file

@ -0,0 +1,194 @@
{
"slot": "151717",
"proposer_index": "20165",
"parent_root": "0x0b968237e4cd877e1b5f146da849d5a228cba9b9020c10095cdf177ff0cbdca9",
"state_root": "0xc99af23f66c2b3c964b5301f314ffae1add20b1d88b3a9d8a95c72110b06b2fd",
"body": {
"randao_reveal": "0x81126bf1b4491dd0906c2d82615b34b8d3f779096bcf0a5f0ddda2a6da2b6c1858d3359a0b70cf66af69fc649bf6683e0520b3b14edcde3d6bd75b394d0812c5fd232350e6e3b5e9e6b262219c788ca2e74fa15a1e02e034b091d6c694aaeccd",
"eth1_data": {
"deposit_root": "0x8654042dec994aa6fc1a36fd0f4ebb2a49d1b27ada9460b045e2ea2a15718cc2",
"deposit_count": "10010",
"block_hash": "0xb358182abdc706e4a7ca709816043ebb23f3d93e943ca0becdfb9202abecd2d3"
},
"graffiti": "0x6c69676874686f7573652d676574682d33000000000000000000000000000000",
"proposer_slashings": [],
"attester_slashings": [],
"attestations": [
{
"aggregation_bits": "0xfefffffffffff7ffbfefffdff7ff77ffffffffffdfe7ffffffffbfffffff7fbfffeffffffffffffffffffffffffffffffbffffffffffeffffeffffffffffeffffffbfffbfffffffffffffffffebfffffffff7ffffffdffffffffffffffffffffffffbfff7fffffffffffffffffffffefffffffffff7ffffffffeffffffffffffff7fffeffffffdfffffffff7ff7fffff7bffffffffbfffffffffffffdfff7ffffffffeffffffffffffffffffffdffffbffffffffffffff7ff6fffffffffffffffffffffffffffffefffff7fffefffeffffdffbffffff3fffdfdfbfffffffffffffffffdffdfffffffffffffffffffffff7ffffffffeffd7ffffffffffffffffffbff7ffeffffffffffbfffdfffffffffffffffffffff7fff0f",
"data": {
"slot": "151716",
"index": "0",
"beacon_block_root": "0x0b968237e4cd877e1b5f146da849d5a228cba9b9020c10095cdf177ff0cbdca9",
"source": {
"epoch": "4740",
"root": "0x75198e06e7a0fe301a524212e6376d2222e421fb3cfd1ef0dcb637bf6d20deac"
},
"target": {
"epoch": "4741",
"root": "0x8bb6fe4f7ea104312914c88ac84534e4da2ff8207790060f4ba903aaf231678e"
}
},
"signature": "0x9138d56149037ae150d8c544c8b609a250fe5e3d66cb36e5d2dd618a642dd79bfa45ec1da014f24872410ddf9ecba28907bec2ea0e5581694871746fbeba9c4e5a553725b03feba05272a11a327b02bae60da8144d333eb019568cf64eb8a43d",
"committee_bits": "0xffff010000000000"
},
{
"aggregation_bits": "0xbdfffffffdffffffffffffffffffefffffffffffffffffffffffbffffffffffffffffffffffffffbffffdfffffffbfffffffffffffffffff7efffffffffffffffffffdffffffffff7ffffff3fffffffffffffbfdffff7ff7ffffffffffffffff7fffdffffffffffffdfffffffffffffffbffffffffffffffffbfffffffffdfffffffffffffffffffffffffffeffeffffffffffefffffffffffffffffffffdfdffefffffffffffffdfffffffffffffffffeffefffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffefffffffdfffffffffffdffffffffffbffffffffffffffeffffffffffd9ffffdfffffffffffffffffffffffffbfffdfffffffffffffffffffffffffdfffffff07",
"data": {
"slot": "151715",
"index": "0",
"beacon_block_root": "0x3fe0c30d4be1a2e83c5109a35fd811fe20f783952199e0d10b485985510edafa",
"source": {
"epoch": "4740",
"root": "0x75198e06e7a0fe301a524212e6376d2222e421fb3cfd1ef0dcb637bf6d20deac"
},
"target": {
"epoch": "4741",
"root": "0x8bb6fe4f7ea104312914c88ac84534e4da2ff8207790060f4ba903aaf231678e"
}
},
"signature": "0xb1653ef5666f3ec1ef9ba39036cc6a361b96b3b82ae9ccd6aa7c83c63e358843c054de6022915421360c04ab343b4cee104f2d5d357454f0ca5d86bc24f2524520f27621624c3e78b0e0915a59cd3ba583eb380dbf984265ffe720eef9201b04",
"committee_bits": "0xffff010000000000"
}
],
"deposits": [],
"voluntary_exits": [],
"sync_aggregate": {
"sync_committee_bits": "0xf7f7fffffffbff6ffffffbfffffffdffffffeff7ffdf7fffffffffffffffffffffffffffffffffffffffffffef777dffffffff7ffeffffebffbf9ffffbffffff",
"sync_committee_signature": "0x99a90d385ef2a1c8d7c8d96bdfc9aa03065532f4415709efbade179de42a8b1c9b736a5d08377c3f9d3a9c220dcd95c61986ba9cc0b735b2c23ac6b8128fc5851e4057c7c469b94ae25c2d0c560793b12021dfb29d34d78e085b8cb8a952f4ba"
},
"execution_payload": {
"parent_hash": "0xdeab769383aabae234218751c24b5c286c54b7e8545308767217c48ee8a66a03",
"fee_recipient": "0xb9e79d19f651a941757b35830232e7efc77e1c79",
"state_root": "0xb1a9669102c2f7d49af01c4831923cb6f030c6a98b334ecb4d307b6c0c7698a7",
"receipts_root": "0xcd85cea85d138342fef326c1c73eb4fc4479f1fc567e7687c5757c39b5a20c34",
"logs_bloom": "0x00200000000000000000000080000000000000000000100000000000200000000000000000800000000080000000000000000000000010000000000000000000000020000000010001800408000000200000040000000000000000000000000000000000000000000000000000000008000000000000000000000810000040000000000000000000008000000000000000000000000000080000004410000000800000000000000000000000000000000000000001000000000000000000000000000002000000000000000000000000000000000000001000000000000000000000000000000000010200000000000000000000000000000000000000000000",
"prev_randao": "0x1e00579f0d5b1861c9e0978a213dc67e0fc4296458f392e378d06b4b85b4a8c3",
"block_number": "141529",
"gas_limit": "30000000",
"gas_used": "207715",
"timestamp": "1740424464",
"extra_data": "0xf09f90bce29aa1f09fa496",
"base_fee_per_gas": "7",
"block_hash": "0xc8807f7a1f96b0a073ff27065776dd21eff6b7e64079c60bffd33f690efbb330",
"transactions": [
"0x02f9017b8501a588771083013b2285012a05f2008512a05f2000830249f094d27d57804f09a93989e290cf12cb872c39ad2ad280b901040cc7326300000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000005693ed58622afdb000000000000000000000000b3db4f6329df01ac317a70200f6614e1cd0db6f7000000000000000000000000fc7360b3b28cf4204268a8354dbec60720d155d200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000d0ada425f6835193b8507d7de3a77ec1bd6c5377000000000000000000000000c8ae6c2d3f6695e41b5cb149beae76600f4ac97dc080a0ee5a62cc99129e5aad3d6b2de4f169dbcfc3b17878bfa9e944879f2899f5d16ba013bc53a251af668e24bfd2157c52a7168e5cbc235cd3615b6f5f733e0bdfc5c2",
"0x02f8d18501a5887710038459682f008459682f0e8301395c940000bbddc7ce488642fb579f8b00f3a59000725101b860aa01b02b16b7a56850cc9b7e1275e8d49e16fc12bd30b4bdf2ef68b5543822b2466101cb541b8815b5ca1721120e4f9da3dc91086418a5680fe3037dba62dda3de79dd22bb41036719c3771f140b419586ae7d9bdf3b10d88850909d4556b19bc001a07ce67cc0fe3ce5e16083414de6c422bbb8d61e2f345b25fc60c8bf58af8a52d3a024b28ae0bae9e75bb588466a4a0ec84d27b4df8127dc58a50cf80b2edc5add54",
"0x02f8708501a58877108219a7800782520894f97e180c050e5ab072211ad2c213eb5aee4df13487025494cb85b9c880c080a08a3568a5f66c85d336f3b98586760753bf3960dc22b5a7edb7551fed54637727a033e49b70c561443b3cccc06e8656e4ac23820f1911800473776d92c0a6014f1b"
],
"withdrawals": [
{
"index": "491304",
"validator_index": "71416",
"address": "0x7bf8bca0ccd13d04fd466539989efe2adcb0ca7e",
"amount": "67541"
},
{
"index": "491305",
"validator_index": "71417",
"address": "0x7bf8bca0ccd13d04fd466539989efe2adcb0ca7e",
"amount": "67541"
},
{
"index": "491306",
"validator_index": "71418",
"address": "0x7bf8bca0ccd13d04fd466539989efe2adcb0ca7e",
"amount": "67541"
},
{
"index": "491307",
"validator_index": "71419",
"address": "0x7bf8bca0ccd13d04fd466539989efe2adcb0ca7e",
"amount": "67541"
},
{
"index": "491308",
"validator_index": "71420",
"address": "0x7bf8bca0ccd13d04fd466539989efe2adcb0ca7e",
"amount": "67541"
},
{
"index": "491309",
"validator_index": "71421",
"address": "0x7bf8bca0ccd13d04fd466539989efe2adcb0ca7e",
"amount": "67541"
},
{
"index": "491310",
"validator_index": "71422",
"address": "0x7bf8bca0ccd13d04fd466539989efe2adcb0ca7e",
"amount": "59212"
},
{
"index": "491311",
"validator_index": "71423",
"address": "0x7bf8bca0ccd13d04fd466539989efe2adcb0ca7e",
"amount": "67541"
},
{
"index": "491312",
"validator_index": "71424",
"address": "0x7bf8bca0ccd13d04fd466539989efe2adcb0ca7e",
"amount": "59263"
},
{
"index": "491313",
"validator_index": "71425",
"address": "0x7bf8bca0ccd13d04fd466539989efe2adcb0ca7e",
"amount": "67541"
},
{
"index": "491314",
"validator_index": "71426",
"address": "0x7bf8bca0ccd13d04fd466539989efe2adcb0ca7e",
"amount": "67541"
},
{
"index": "491315",
"validator_index": "71427",
"address": "0x7bf8bca0ccd13d04fd466539989efe2adcb0ca7e",
"amount": "67541"
},
{
"index": "491316",
"validator_index": "71428",
"address": "0x7bf8bca0ccd13d04fd466539989efe2adcb0ca7e",
"amount": "67541"
},
{
"index": "491317",
"validator_index": "71429",
"address": "0x7bf8bca0ccd13d04fd466539989efe2adcb0ca7e",
"amount": "67541"
},
{
"index": "491318",
"validator_index": "71430",
"address": "0x7bf8bca0ccd13d04fd466539989efe2adcb0ca7e",
"amount": "67541"
},
{
"index": "491319",
"validator_index": "71431",
"address": "0x7bf8bca0ccd13d04fd466539989efe2adcb0ca7e",
"amount": "67541"
}
],
"blob_gas_used": "0",
"excess_blob_gas": "69468160"
},
"bls_to_execution_changes": [],
"blob_kzg_commitments": [],
"execution_requests": {
"deposits": [],
"withdrawals": [],
"consolidations": [
{
"source_address": "0xb57a360b34e22c598a9b0da37c5b9a7825da4db6",
"source_pubkey": "0xaa01b02b16b7a56850cc9b7e1275e8d49e16fc12bd30b4bdf2ef68b5543822b2466101cb541b8815b5ca1721120e4f9d",
"target_pubkey": "0xa3dc91086418a5680fe3037dba62dda3de79dd22bb41036719c3771f140b419586ae7d9bdf3b10d88850909d4556b19b"
}
]
}
}
}

View file

@ -0,0 +1,305 @@
{
"slot": "151016",
"proposer_index": "27017",
"parent_root": "0x2f135d2fe887c6012e78b25b8adecc33bc268c8057e444422f9fbdbb02730a30",
"state_root": "0x62a65efc3b24c02a29c4fdab5edf076329024a05dfc944b35f920a38d07b24ac",
"body": {
"randao_reveal": "0xb765dc7a976fb26b7cf8df404055cbab5e7665dfc1106ed4373709840d99312fb5f782a44c25294cece2f55e0cde6d1515504693574ddfb9bbcfb47c21763efed46b91cb47cefb5a14c78dd53f491d51e40b36eaf26e23e1edc49875e8c734cc",
"eth1_data": {
"deposit_root": "0x8654042dec994aa6fc1a36fd0f4ebb2a49d1b27ada9460b045e2ea2a15718cc2",
"deposit_count": "10010",
"block_hash": "0x28f59400e7becd79cfe3b14f36dd58fc27826849247dbfd1e4d09448806f8955"
},
"graffiti": "0x6c69676874686f7573652d6e65746865726d696e642d33000000000000000000",
"proposer_slashings": [],
"attester_slashings": [],
"attestations": [
{
"aggregation_bits": "0xff7bffdffbffffffffffffefffffffdfffffffffffffffffffffffffffffffbfffffffffffffffffffffffffffffff7ff7efffffffffffdfffffffffefffbf7fffffffffffffffdfffffeffffff7ffff7efffffffbfffdffffdfffffffffffbffffbffffffffffffffefffbfefffffffffffffffffffffeffbfffffffffff7fff7bffbfffffffffffffffff7fffffdfffffffffffb7fffffffffffffffffbffffffffffdf7fffffffefbeffffffeffffffff7fffffdfb5dff7fffffffdffefffffffd7ffffeffeffffffffffdfffffffffffffffff5fff7ffffffffffbffffdf7ff77ffff9fffffffffffffffffffff7fcfcffffffffffff7ffffffff7ffffffdffffffffffffffefffffffdfbfffffffffafbfbf7fffdff0f",
"data": {
"slot": "151015",
"index": "0",
"beacon_block_root": "0x2f135d2fe887c6012e78b25b8adecc33bc268c8057e444422f9fbdbb02730a30",
"source": {
"epoch": "4718",
"root": "0x6567f31ab5ccc3a0b0cd5d27abf183ed36704f817d09cb9dbe183da83cf07bf2"
},
"target": {
"epoch": "4719",
"root": "0x49ce68a9103d485d81d74d0c744a3fde20657c4cb81fb13b12cee75a2a29804f"
}
},
"signature": "0xb7ad39b499b0e5b8a22849b068c98a08fe611d45a2f43e30fa449e657436e685e9e2a9bfae3a3128baf939f4394c0f2717835b0756e3177cb38d58775618ff6607fb68f362d60b9f865daff0ac239da49690e8fe439aa0dc27e2d4196beac98a",
"committee_bits": "0xffff010000000000"
},
{
"aggregation_bits": "0x0108000000201040400000000000000208",
"data": {
"slot": "151015",
"index": "0",
"beacon_block_root": "0x2f135d2fe887c6012e78b25b8adecc33bc268c8057e444422f9fbdbb02730a30",
"source": {
"epoch": "4718",
"root": "0x6567f31ab5ccc3a0b0cd5d27abf183ed36704f817d09cb9dbe183da83cf07bf2"
},
"target": {
"epoch": "4719",
"root": "0x49ce68a9103d485d81d74d0c744a3fde20657c4cb81fb13b12cee75a2a29804f"
}
},
"signature": "0x833659ce0cf0ea303c1df6d0754f5303392789c91498635bcd339f0056c496e770072f025eb962793105137c424d80af05a6ebe8d6e7f9de48121fbc362b91d1644f23ce3728ddfab680ff2c064fc73a049389116a56409ae35d675b37c59e92",
"committee_bits": "0x4000000000000000"
}
],
"deposits": [],
"voluntary_exits": [],
"sync_aggregate": {
"sync_committee_bits": "0xf7f7fffffffbff6ffffffbfffffffdffffffeff7ffdf7ffffeffffffffffffffffffffffffffffffffffffffef777dffffffff7ffeffffebffbf9ffffbffffff",
"sync_committee_signature": "0xa7be0f119fee5d9f9409d508b2291e1d8d81d3b9edd64d7a68d12f9b1a84bd50230618e4db78ef6e7f1e2f3e6aa56e2807985b25d52750a56b9d1b087ae3bca80c3a0b091e52c3797d82778acdf877e42b8fb18598d9fc56e9e469ebdd5c15e1"
},
"execution_payload": {
"parent_hash": "0x52ad968c44fe260e5bb67b63c3ede2ade269a23641d27fcceba237065784c89e",
"fee_recipient": "0xf97e180c050e5ab072211ad2c213eb5aee4df134",
"state_root": "0x34ecb1a20d718e06f69e4ec6b6ad86c75603149a207480b03a077d0231668805",
"receipts_root": "0xd2da2f149a53d2c26982187652ecdf1114ae5301359c0ee6752c2b78dd97ea02",
"logs_bloom": "0x10200000000000000000000080000000000000000000100000000000200000000000000000800000000080000000000000000000000010000000000000000000000020000000010001800408000000200000000000000000000004000000000000000000000000000000000000000008000000000000000000000810000040000000000000000000008000000000000000000000000000080000004010400000800000000000000000020000000000000000000000000000000000000000000000000002000000000000000000000000000000000000001000000000000000000000000000000000010200000000000000000008000000000000000000000000",
"prev_randao": "0xb45479ddbad8fc0733b7762aed1a5b5861712b29bc20c2c0e34dc5e6722e82c9",
"block_number": "140858",
"gas_limit": "30029295",
"gas_used": "2383293",
"timestamp": "1740416052",
"extra_data": "0x4e65746865726d696e64",
"base_fee_per_gas": "7",
"block_hash": "0x1f2637170986346c7993d5adbadbebbf4c9ed89c6a4d2dff653db99c8c168076",
"transactions": [
"0x03f901198501a5887710821dd184773594008477359407830186a0946e8d9c2108be0894bd30c8525c71d1bbcb31912e80b8808b177fd73255e0d6bd6e8a142b0572ba8f34d66075cf829e3fab19009fb1a8a5d057276080527fd8fff3f3f4d3b02314aa185d801e8d378d88992aa540c8b093c1e4264b68777c60a0527f69df553e04f0a6a7435a5650668958d47f9d4187d2ff2e72a91d8d9f74928c8c60c0527f8d8f0c90c68115c209dcec644ae93d4d05c0830f4240e1a001359c559c4114b7919abca725d032f48ab4519d2857567795ca4f49345e65a280a091007fbb893a18315106cbcb53bf044d6a5c0ea7098e0b6198727c3983ebc628a06c9abb0a1ded37b6305a5711831f03fce5e467a70dbc9c0c724ebd18b200a202",
"0x03f901198501a5887710821ae084773594008477359407830186a0949ae53a8e1ac8eeab1feece735b3d840634e30c4980b8807fb45278a2777c8ecd11eb2c2052276fd12c86bdb2373aa59d293c6b0ae1e8df076051527f8c9bb0742b467c164d5aad3087f92355c3f8cd0c291ca87b086e86fffa73780e6071527f8b11001c065ced40db4a5b2c43de47fde7a3b22f8919b39acd8d0b309df8a40a6091527fa49bf4a805c16f59556bec2665b647e02cee4fc0830f4240e1a0019da1702b651bd92e079ca2e801671cf22f3d3b6c097a185275dca50784123f80a039e483afa3751d7d758f2273fb19ac2143e5ce225eec49244f0aa358dc9fcb13a02e4a552312d68db400acc3898835e0aa32f46b8ef786ad6e2fbb0d933c51c551",
"0x03f901198501a5887710821dd284773594008477359407830186a0943c9f26a8f3c71bd76665e89a50f5f70d9953901e80b880605c60fe5360ce60ff53606f61010053604b6101015360fc6101025360396101035360e36101045360b36101055360926101065360e061010753601461010853605261010953607361010a53609561010b5360c861010c53608a61010d5360d261010e5360bd61010f5360ff61011053603461011153603761011253609e6101c0830f4240e1a001f8b21450b076ae1037e2e42f8a6899f01f793a2bee9f59e00ee520af66d56a80a0e97e75f469a7478d725c078e7d5b6979018753556850a61cfcd01cd2c87f02c8a024a8247013e502c09f4d7981f912995c44978f9680c261829bb3f0ec86627ea6",
"0x03f901198501a588771082398384773594008477359407830186a09401abea29659e5e97c95107f20bb753cd3e09bbbb80b880600060335dc61f7ffede24c98d6f2e886373c1dd99c824ad17b93cd6cca65a8856cd72972eea737960665260b3608653601a60875360f660885360ab6089536024608a536043608b536001608c536038608d537f15db390ca3613b231b40c801210901ec898c67f53aa7864f903944c3e3568817601b527f23a5a30cb6c97aa2c0830f4240e1a001c543c4e8d14cbdfb690f66b63b8b4c4fbd3963b1a555c6ab3ae43b08367b7880a065b7869dd9655457013e3e88c7866205124bec055f51b41e1fa92acce78b994aa07b83ebb8f12ba28df90ebd3eda8858204f2ff0eccb7ad8be10fafcb249bb3fdc",
"0x03f901198501a5887710821ae184773594008477359407830186a0946a5e2c588bb18c17cdadb44f82d22a895da0d62480b8806000632a674ca35d600060f15d60e249603c49604d496097497f8474c6950d31f88091bd7bed171350e494e797be9c933f1288dabacbbb84810e60c1527f84f66da36ef2af9007aa316630acc16ee30260d66c55ec449fdf03fb20860c8660e1527fba2768ed15416619a048e1b79c81583af46d1f3ff8141279fd98cb2326e0c0830f4240e1a0018ddeb1837db4291ad48990f90ac5bca82d10b9d3d73eebf1aa5c314cf91abd01a010004635691cfef330e4bb27701074e719e65c037e4182be65becc5e1945522ca02cb00278eb15a555f9ce56dcdf783135e8aa6c852816497d389190e63ae304e6",
"0x03f901198501a5887710821dd384773594008477359407830186a094000f3df6d732807ef1319fb7b8bb8522d0beac0280b8807f9a8968ca7a94768406d68b36a066e74dcadc49bc3605fdb95370ae9040470a4860eb527f0d8c1c38ee4b5dbf2b28f10ecc705f9cd2a9a36cfeff6c433efb8bd7c99626e061010b527fbbc1026522b0e497dc9722fc477fb7344969fbb16d66e31cc6e03de9a23178e361012b527f63ed37d8ac0e69b5609744b3ada8384a1dc0830f4240e1a0014cd492e6c4d129d4d3b22182ea5a5a632f3320aebbb6251d0ad724a747391a80a0c07736c5db2bc49a29ac00df7b32abf94978bcea15e65423b5cdd7d35df126dfa06b3f8c10978fc3e1cd60753975ed21762bf99a59fb29cd8e0a970321ee72ba9d",
"0x03f901198501a588771082398484773594008477359407830186a094dc547f9b829e446d70566195aecc6a5977e5860a80b880600060335dc61f7ffede24c98d6f2e886373c1dd99c824ad17b93cd6cca65a8856cd72972eea737960665260b3608653601a60875360f660885360ab6089536024608a536043608b536001608c536038608d537f15db390ca3613b231b40c801210901ec898c67f53aa7864f903944c3e3568817601b527f23a5a30cb6c97aa2c0830f4240e1a00150e9783b2dae0fabae7243e90ddc1dbab17e6a0934ba2434aac62f27cdb2d001a0ad1f8b4409159784a76bc7abe3a89003139274ff8215bfb50da2ffaa8b312cc8a032aa53063bbae96713f93714c830c536949c843cc09a3dde8cb2ebdfcfe5d613",
"0x03f901198501a588771082398584773594008477359407830186a0947a40026a3b9a41754a95eec8c92c6b99886f440c80b880600060335dc61f7ffede24c98d6f2e886373c1dd99c824ad17b93cd6cca65a8856cd72972eea737960665260b3608653601a60875360f660885360ab6089536024608a536043608b536001608c536038608d537f15db390ca3613b231b40c801210901ec898c67f53aa7864f903944c3e3568817601b527f23a5a30cb6c97aa2c0830f4240e1a001bae560947db14e3ed438ca8504783d40c9047191f49933b1d5e1ec8208583a80a0237ae7f13f17c0f994513c83636e1ac7e4d7c722221b51778352dfab41864310a036cba8ec5a2d31e6342f6afc914e3ba9dbfad0db22d29cb74e46e170ae2b09a9",
"0x03f901198501a58877108236ff84773594008477359407830186a094000f3df6d732807ef1319fb7b8bb8522d0beac0280b8807fc51dcb985e3563ad803460f85ed4746c57bf060d8e2259116646a0002c898fb360ea52606561010a5360a761010b53600561010c53604861010d53600761010e5360a661010f5360986101105360a96101115360b06101125360dc6101135360e6610114536071610115536060610116536091610117536084610118536041c0830f4240e1a00118b3d58c9a58f76bb27cff8ee97664f8b88ea8ec955abc1463805d2b96428d01a00eb390be8ce5900249b939af5bf2ca243ed1902ea6b1bc3bf372f8028b95861fa048dadfa661c274595edc2cf2797b9ec4dc873cd6a75d7329293f873c4335438a",
"0x02f9017b8501a58877108301391785012a05f2008512a05f2000830249f094d27d57804f09a93989e290cf12cb872c39ad2ad280b901040cc7326300000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000ee86442fcd06c0000000000000000000000000000b3db4f6329df01ac317a70200f6614e1cd0db6f7000000000000000000000000fc7360b3b28cf4204268a8354dbec60720d155d200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c8ae6c2d3f6695e41b5cb149beae76600f4ac97d000000000000000000000000d0ada425f6835193b8507d7de3a77ec1bd6c5377c080a0bb753e1df11f4e15f7b1f44c04d738b30e0849b804bb786f7fba10edc3d7668ca05d7151817aca075c8d7bf9ba2b74e9294d28de1c4b8d3e5b138669e89324885c",
"0xf873836181cc847735940782520894fcb6e353ad4f79245c7cb704abcffe2f4868424188058d15e1762800008085034b10ee43a031323a053c2013914ed32e251907c3311e4218561cdd8c25eaf8672355e66720a046900db9b5826610a5208c61a6e0c9f64178a76caefd64037db69b997295bf5c",
"0xf873836181cd8477359407825208940d3de4256d6322683fdea9ee23765ccbfcb83da488058d15e1762800008085034b10ee44a0cde1811bcf855221edb3b337e6bdd6bf26b11c9387768fba8597daff950ad5cca07867720bce30276937c2d806024791f158a64862ec35a036ff1dbb01d16be959",
"0xf873836181ce8477359407825208946021752d8d9b2f221d4fea4349dea34ddbcfce5088058d15e1762800008085034b10ee43a0f833125f6025471a4f351c2777d5841a23e4f6002c51161855004820fb91eaafa01722b78eba514ac450c0459568b4dc9521bb797a7c601c5c8825cd5e006f71a7",
"0xf873836181cf84773594078252089461e296d527edc89e831cf593ec341f16197eeafb88058d15e1762800008085034b10ee44a0151e6c4bedcf29fafb9ef50f81f7e573d75ba42e00c46e91b9761de3e13cdc37a047bc54be6c3493ceb0949a3324ee1cb93c8058c9db71fbfb5c16ffdb472c0fed",
"0xf873836181d0847735940782520894cf7317ee7a3b497ecf634b94bff60ff91b92574788058d15e1762800008085034b10ee44a0c6e018c4eb28bab20355104705e0570a371c157dc592e503713bcce730953d76a0279412202acdd26ab9d03c2edb129aa974fc39c86a0ecbef410c0a69d27789d9",
"0xf873836181d18477359407825208947e7b519df31f77ced83eea1b16aedb6dcb0f0b2488058d15e1762800008085034b10ee44a08fe9e5cd6238d8e3e21ab280bca46ce43c23620b1f81b386265efbcbdb106385a018945e95e62a6148cbf35649e1e2ec4835491b4e4b6216e316c4932c96220ecb",
"0xf873836181d284773594078252089488a075e0fb1c9309a200a8bf0a88b214bf7ceb8d88058d15e1762800008085034b10ee43a090b175e1335dc2c4450dd3a0543e19e2635aebea5675c94eac1cce52c7ce3d64a021aab7cccb861a1c30d876526a4ef0b7e217d784fc89cbd02e8c6d63ad35b0ba",
"0xf873836181d3847735940782520894c8d7cfb58f3ac02568e6505bf3fb5eb6f080703988058d15e1762800008085034b10ee43a06d530baafa0a899792978df61bf5adc6028be85be12f55c687e44f1740dc22ffa06d4196c744ecfed500c4ad0fa17947c12238227155acea265edb7c7461571259",
"0xf873836181d4847735940782520894e0132e8d7b1b766e0ade5543d6c6c0b2d5a2f01d88058d15e1762800008085034b10ee43a007e42562bf3a9a058fb1803d7c423ea0d7113fc98788be2a6b4d982210066139a02a24a9efb9f88cc53ad464b62601af3e54fedd8305e1b98670bfc8dab223c596",
"0xf873836181d5847735940782520894eb674c0411db79654afdc1e131f3b6e734baee6c88058d15e1762800008085034b10ee44a0e197786db41e4d21914f319e669aa504f1e821d2cd17b39a396b1b85b5348025a00b1efe1355fcb919642f14f7609893afe70017eaa999d531e6f77fbdc5d28029",
"0xf873836181d6847735940782520894dc07c60993cf689438b8c85f86b0ed938dca77ea88058d15e1762800008085034b10ee43a0f00a06c5148f52c55b2f65668a757c9d0011e7924032b88c771e3934e0bab6a4a0480e37e4cd116cedecd7de44cdb91c698ad896abbd3b08e2651773dea497e8e6",
"0xf873836181d7847735940782520894110ddc93db59ed31a03518510221ec2f35d28f2f88058d15e1762800008085034b10ee43a047c9b1fb51b626b40cb2707269a8ad6a7391c467864e4423efa3b4ede8bf6d3ea028314d010ef79d04027fec9c0efb0bbad7ef386e110bc8e07797e78f484cb51d",
"0xf873836181d8847735940782520894b599a876aaac824cfce21bdf15627c9fd8634c3088058d15e1762800008085034b10ee43a0630d66f57eae656309cdcf02dbf20db3bf960c77fa0c66b72de13b7852b0c8d7a00541603caf792d410387983650ec5799d9eaac5761296b3ad09f26fbe40fe251",
"0xf873836181d9847735940782520894d36e5540dd71acbd6416d60252c4d7c34a3c824588058d15e1762800008085034b10ee43a0dd6a775844cc76e89ab1b2bd43d266b3b1b6e34a8d129cc1c1f7ce28c44b2439a01ab09f383573d80274ed64053a079902796c2e6e4cc19abf0271d37e2db4a0d0",
"0xf873836181da8477359407825208943adeca35af56206a74987a8fe13c669365c770cf88058d15e1762800008085034b10ee43a0a8f36c6870413fdea217d52c0b6bb3d6b55a837e7907ad2a3979da577f9f1763a0646ed9c93233cae748988a3a33debd3411706a2a88c3349d2702da1b5a728cea",
"0xf873836181db847735940782520894d77b95acd12f7b4b5692b55717b7bbca1165195488058d15e1762800008085034b10ee43a052069ecf399b7323e46892858d2d99668d7554b581245656d97768972f533683a009824b000ffa65ec5acc5210674b12ec2b92f45214e2af2c541a9da825baa9f6",
"0xf873836181dc847735940782520894f388bf5766b5ed5d4e1cbf15772e677dbfa80b0088058d15e1762800008085034b10ee44a0532fcb541538ee4041bd97e09c0f86fde22760d4d3e3922b48d2fec22c41503fa00aab5d1fc4438ea787d42f08508c82cc8c9940583217eaf25e9cd12e53241b4b",
"0xf873836181dd84773594078252089435d4996296e58560e6ef47787d51b55f1e2bd92a88058d15e1762800008085034b10ee43a01c2172f632447d91029c0bb197b6f29c6f16be40ddbc45637132aae1734dfbf2a06c7e9868cb7e95d72edd545e173887a45d405e3597da99067985d8e7343ba224",
"0xf873836181de847735940782520894a4c3b77b898e53d6095f11c53a1ce272cff9af3188058d15e1762800008085034b10ee43a02ba7caf4ef662c4a459f11d2ff90c970c8b4d5ab27e201b8f80e12d763a46f57a02e1f114037a153d9ad303e5633753785bd7c73c5cb0b9f3bb8159312eb0692a4",
"0xf873836181df8477359407825208946e84f6113fc1919714f0266705813fb81a17181f88058d15e1762800008085034b10ee44a07ac801acd1375d7bc7d5a7e54553be58976519169de1e2e14186494087a0b4baa043a9a4311283cbcbca29255de9e3bf1e688d73a0e4e20ff8d8b5a2e16b6e630c",
"0xf873836181e0847735940782520894e9ae1a806004e1452baae0493920815aadd8479888058d15e1762800008085034b10ee43a02fb8319f5613dbf37419d177b132306a7c55b2d72d04522d6eceab27ddba4f82a03f1b125a7c93ff5e6c278df26ebf8411fd9317112424a26dabc5a23557712272",
"0xf873836181e1847735940782520894fe1905d8ebd20e037274eef441283c811ea82c1688058d15e1762800008085034b10ee43a0224ee510dbb1e0ef70a7a016bd1512039fcf3af0af9a34b9b1cc2ee90a002339a02b7a11a1ff4a20c1253069e4b899ef0aa2f2702c3c4460d478f92a9a43e00b67",
"0xf873836181e28477359407825208946adece88e477f53a143a4c29d97940df2ec768e088058d15e1762800008085034b10ee43a08590ee9e8715492a2e5c1390f4e389143c410e1d416f5202994b516f51e7eafba03159c4853aeaf69bf1c44c4e6cc8e449bc7d42eba79384cd8b133659d38159e5",
"0xf873836181e38477359407825208940d34d140a7376892c4593fcea3ae26f5d6f202d788058d15e1762800008085034b10ee43a01a115564422f4e9fe3d1e51202e25ad90705509c2b827423bd9a766d67e6c6d1a00fbc0b850c9835c6a75a4baef78f29458f592ca83447dbe1c5f14c38724a86d0",
"0xf873836181e4847735940782520894d1c7fa75b9bc55d041fcdf215f3e3a351c9f9edc88058d15e1762800008085034b10ee44a0f1bcf05bfdd6d0d2a4345f86c2875dd7bfa685745427754fd3b524a69664114ea0674e3c62d1e0b5ac4d79cbc972ea51e2183201f879e17b9061671107089fcdda",
"0xf873836181e5847735940782520894418ebe350a8c6387bf5e42f3502742af8e0781f188058d15e1762800008085034b10ee44a02b5935a36f3240d235d2574f6a0d97f432aa4443da752e1101479a9392b124d3a0674f43bb33455d22e08224c584a2f206cb6ac4a86aa312f8d63b75f99fef5764",
"0xf873836181e684773594078252089484914d2770c711d27888c775c547b1d933b48c4788058d15e1762800008085034b10ee44a0c94d55923ef4bd2e4c0a83f31e36dc49f2ef84bda5a4dba429795ffe33bb6574a04101d4d64e212019cdeb93c8e2ec7488405c2f716acb64e5ab70ca4689c4a370",
"0xf873836181e78477359407825208948f51e560b85edf2e653c689c4e9fac02ce0556b888058d15e1762800008085034b10ee43a081e8fb2724b676b50ed6a9dd02a3dfdbeb1cd715cbe485cfa98a204c5a7a35dea04d7f13607047239a65d798a199a66460d89f3334a79f49587fbb58ad499e1a1b",
"0xf873836181e8847735940782520894ee2503205c24dc66346e356f13f333fb8782d35888058d15e1762800008085034b10ee44a098b33a8231bf024845d5bf52add950edc4b1c53f23b03b8b5439ffcbfa034522a03dced7a7342c7d2eb201d8225009a41527dd98f7a6475a8a65660feb4c68d6d6",
"0xf873836181e9847735940782520894096ba6c59bd667a0fea9a356bcc988e4d9f2d8eb88058d15e1762800008085034b10ee44a0c3a8d920e1f80f5139f70822ca6c899edf0bc17c8fb3c7f3a1318ddea7d62c7aa054e14fb1325f2a23214fc6ba3abf11ea547e01a1d95a3e490c3b1f9af277ec90",
"0xf873836181ea847735940782520894da0adce4f1dc7debe7b2b52e8fe9ace6c7ea9c6688058d15e1762800008085034b10ee43a0755d9805b1d15d1392d860e86df05834fd94a61d18c22956907ac7c1bc982892a063de885856b515242c33c31f9cee81d23aee55fd632b88d86fcdb3f8566af3ef",
"0xf873836181eb847735940782520894af7d412aeab7525c0541dc3aa6c1085cfb8c909988058d15e1762800008085034b10ee44a0b91c06dd7e4010c5d7b3f66285f4c139e22480773fb053409950ffe5bce7b720a0397f1b6ef9424dd5e30b07c110fa5b009cf86bfe92a98e994427d595dd9586fe",
"0xf873836181ec8477359407825208943cf8c0d567261eaf4ac0872d33a9f48af361769f88058d15e1762800008085034b10ee44a0828618325c9586073c8199d866e9001a7b9ae129a29cc60cb43aca355175b13ea07f885bfcc7b096b6c47e0122bef7f0295712f0810a78ab4f0b53112561d2c84a",
"0xf873836181ed8477359407825208944779242587ba9e828999249eadd82984430f484388058d15e1762800008085034b10ee44a0dd150d77fe8d9073a20510992f5eb8c70380327f5483064a6f42fb547465ccb3a04cb3406733b1e7318315255edccbad06adf0e6a3fe13f01389c5c95245cc8150",
"0xf873836181ee847735940782520894ea531cfe2de357ecff3855b88dbd07f60b03cdca88058d15e1762800008085034b10ee43a08c72279cb318e4dacdfaf265959cd7f31002a1521113bc7e65851ab8ac2aed06a008340ab82a4a61f91d3e5f4594629b6a422fe11e8d6b39b2a145eabf15f3bbf8",
"0xf873836181ef847735940782520894d00b5f53ea2a66ad33c3fee304bb22857dfb8a8788058d15e1762800008085034b10ee44a0ebb567f7d111eb54667984476e3a271c99220942e20e96f713ef9b4ae5728404a040bae6c5b88e58b0e02d1218df7ae41d9b3b99d2180eddf8ba770976852de483",
"0xf873836181f08477359407825208947ead29f6616f78f21a951c9686dd257be7b8efe488058d15e1762800008085034b10ee43a0f749f26b7f700cb7bfbbb6e37206553e64ae70307f33cb60455bee1da135d4a6a072467439050b5a0d411d90c92fe6fb17676189d3fa8a0ad27ec4dbcdc03c14cb",
"0xf873836181f1847735940782520894d503c13ee55c1ea128357d4018ec58d0d5e5c3db88058d15e1762800008085034b10ee44a0adbe9e31e5a740ee0b52a69b0cc85a718c799309df72991bc856c364f9b85f28a051a55333b441a7255a63e0fe6918aff4b6269576ef99763190c784624f8f7e45",
"0xf873836181f28477359407825208944ac670d8760faf780468638ef80034876ed8918d88058d15e1762800008085034b10ee43a0636ee52b3e021114085c7413cbaab77013b432fd41cbf0a88dbd8d145e8a1125a02d15ffe159ec762142907b2e8b5e04b804361a399b23ff335ee4b996f7ee0a81",
"0xf873836181f384773594078252089424ffb8c97ce443f8d3265ba3316defcfc07c659c88058d15e1762800008085034b10ee44a03c4c9d42c3a76c275f8f167cc846e62a16e2d4f54418b0303f64f0a1f2122fcda034d0cd15b9d14b118eb7a2e5bf9a464043e5b1935e740ca557fd05b47328b092",
"0xf873836181f48477359407825208940c5cafc547ab98c9ceaa1c07fdd6bf7820aeb95488058d15e1762800008085034b10ee44a064728de799b66a56f3f634bca37944e0027ab9be12419d2e976bacfe295c5854a03a4b830c50b7c1cf39511b592ac20634bb54e6afb795dddbfc6c322bf6ac85fb",
"0xf873836181f5847735940782520894db8d964741c53e55df9c2d4e9414c6c96482874e88058d15e1762800008085034b10ee44a0d56eac7a793978c4a8356bf94024e3fe292d9f61f594c3bc847a49c4c94df56fa059b143fedac3f768e936b63291180c1fe34415d27888e1b90326d28b4567fd53",
"0xf873836181f6847735940782520894ba85bb35ae6ff7a34745993fcf92b9afd34124f188058d15e1762800008085034b10ee44a0b368c25c6edfe52749b1377664c2886a5c09f89d12c63078ba635dad22ae3ec7a004d02d8cec691a288092fd337f4a9c18eb4d15493cd512cb963a3be9cd9c0b04",
"0xf873836181f784773594078252089458871015f5a2d3948264f7c16ad194c80ffd531d88058d15e1762800008085034b10ee44a0c6ecc87a4c18d2476757ca03080992de68e24b16826e35d0a26b6d137a4b6be8a065defc8e1b910d228675e323898baf81d24633cf57549021e64678e4e41bb78c",
"0xf873836181f88477359407825208942a90af45df70b0031f218cc122598ddf3e10469f88058d15e1762800008085034b10ee43a010c9e461dbdac507e73c5c2092f83c780fd91de482b1db746544dca8adbabb9da05f1c695251e7fcb4fa0a655f5d64845e597fb3f2a948326dda2e7a87fa74ea50",
"0xf873836181f9847735940782520894761bbaaea6ceb265f5262c3b559adc2ad3ed2f0988058d15e1762800008085034b10ee44a01e238f26c7ed2a54504a1ff05ab2cdffd5306c80281f61e3a93f950b2c965014a00739c6e4e75c3c312636bef62ff6fb8a1c0d0c71dd34f117e19bc58fd9353e1d",
"0xf873836181fa847735940782520894dfe86f51c5e603f1420d1f0ab366bd3bfe23d2a788058d15e1762800008085034b10ee43a06994565ae218ce6ce28078aa4924a01207584026a892fd82b4bc8c187d8bf1c2a02b44cbe2120bbf57c777b2321e2b54e3d8015a6484e109bf761e07e38b22c9d9",
"0xf873836181fb847735940782520894d616547158b05ab5079106dc0336d72763a7287188058d15e1762800008085034b10ee43a01559edc6eb87baba522ba21cd45da85f76be8adceeca311a378035d723efa31da063253dd048dbe7e7c7285a5b1b23dfce7655e060c5b9523f56c6de6b7fa30ec0",
"0xf873836181fc847735940782520894dc68cd278cb7f5f666ce7b0a3a214a8540ed4dfa88058d15e1762800008085034b10ee43a067f63eb982417af7de349aa9cf07889ed0d7423407995926b8044fe7525154eaa00417db33f88ae9c142950ef84ae18f47445fa7c26b6a482df6ffb5e13ca89cad",
"0xf873836181fd84773594078252089411f8107da05b6905e8cc0227ca3b0c6eb764fac088058d15e1762800008085034b10ee43a06594ca7cfdb0ee036f1735bbd3f2971be1da00edefc122a0214807828a7ead31a075a11a79283bd39456153246494d408c61f18f27022c4bb888b5742e1c66e3ef",
"0xf873836181fe84773594078252089404da906545679850a7ee0ef6836e183031bedc8888058d15e1762800008085034b10ee43a062c55b09cc71c0c3e34bb97dc8e3d9e9918929f0c9490309090bc430bf6eb722a05f733500640bd0f6e61952424a3d9bd5dfde0ba3cda5be598489f0859df6c919",
"0xf873836181ff8477359407825208948bdc25c43c010fd3db6281fcd8f7a0bed18838e388058d15e1762800008085034b10ee43a02518ed9188048e9a9666de8a3983dd208c2e582a18720a2605baaaffdb2ef4aaa0510893d599ed7e303b7027e9c9453a8f25409735ae782faad9fb3afefe4e458a",
"0xf87383618200847735940782520894af16f746b8a834a383fd0597d941fee52b7791eb88058d15e1762800008085034b10ee43a0d8b4839aed9b334066a24ef5295f456fba1d0fc6d783b7d18012cdb2c18861e2a07a74bd5a40d360c32bceb00be00d3e6e608f39b04a13ecc3ed4672900f5c54b3",
"0xf873836182018477359407825208940c5c736600f8ea58ccb89aa72e3f3634651fd55188058d15e1762800008085034b10ee44a033c53fbdc9c86cf60777c5d2bd192f57d36628c672a7ca61352effbe7fa781f4a01943da2502b48ee6b3b936769270da655c8d955c9a390e0c186ee5d3646d1d27",
"0xf873836182028477359407825208946f475e0f0e9eda58556fddc04de9b1a9b6a4cfb488058d15e1762800008085034b10ee43a00a00563e58ee9203cba306e432af000938b1e2e2daebbb7d06210c0b3cdac601a05d0f1492b63df3df5ac1310fccbdc3e0863d8c3c7231186df265d32abe00f87f",
"0xf873836182038477359407825208949b2e76498a695c4dc7d0890069cffa84a9581d2488058d15e1762800008085034b10ee44a0cb83570f01c24cdef394144b462fd9b7753337794a84fbb89c68e40038491487a03cb5dbc916553e4534921cc0c0d7e9d3c12afc7bb345ecf88e030c8d6f275a1c",
"0xf87383618204847735940782520894e2d2b2069f4a54fcc171223ff0c17adbd743c28588058d15e1762800008085034b10ee43a01e6d349911a4261a944c289ec6594cb1d6414fbc96240ea5d0a134cb75cf5ff2a042dcc7cfe31d1260bc5093627e0fbace0f87812d37cfbcffe7beff500dfff385",
"0xf87383618205847735940782520894386bd49f04322544f3c7178fa5ae1a24b947b45488058d15e1762800008085034b10ee44a06e76a8890665419b746548fd24bf999efa9eda82b509a2a13dff3c8d8774d239a00c54e75e6280c2368a3cc742f1c6c6d4853cdff6aa71afb3b59939283a70ba70",
"0xf8738361820684773594078252089400af839c3fc067fafc2e0a205858d6957f0dd18d88058d15e1762800008085034b10ee44a06e90bb215dedf42450c3529ecacc852df6fa5c4d76b04b673d2dee5162f77afda061c0f17a6fb4eaafd5c1792bd780e256ae93b8197eba1f739d3483b6018b34c8",
"0xf87383618207847735940782520894ebb6d32a650afa9221b55a11c6a6de52b6f07cd788058d15e1762800008085034b10ee44a0bb5b7a794279609699dcfef3544af96e2f36e1a080d9df5ae2f0d065d897e7f6a020b02a03622daf550239dbffa1d81c3637dfcc4267f19ec86deccf77005c7b3c",
"0xf87383618208847735940782520894011d26a3a9adc9203c8943a6a77aa8657af5242088058d15e1762800008085034b10ee43a0033c85b6bc49bc1122163ed03061ffd5a580e70621a77877108c975f6280374da03a2ebd53fa8a5b2c0676e05e87ce8ae0f64f2a1304c1c01fb0a353ca084123f6",
"0xf873836182098477359407825208949c85bc61a89fb5abd957e6c819c653fc1aa0d11b88058d15e1762800008085034b10ee44a02c511d53e802a2653fe1284802c56362ce7f36936030da8f2afabc9369812b8fa02ca196683edf5a07038d65d12d23b607d331558f8e8bbe1bb3638b3f87ed1e5d",
"0xf8738361820a847735940782520894bd8e8435b7897d87cf7cedb5cf8c5dd865dbf72088058d15e1762800008085034b10ee44a0dc65a971d20e6b631f9ce091c891068600846b80a519b897dc90d85cf493b67ea075b4159b6bb958d789c05bfbdab2f58bffe006f1bc6481f5e5fbd0c0de87e0ab",
"0xf8738361820b847735940782520894adebee2e3ff041078b62380d001c6e51b4f1559888058d15e1762800008085034b10ee44a0dc5c31ab6d48576b337145ca3480f20403342d4632db7e3c58a5c2a3722aad36a0088bee3e8da14156e74abd4b830ce7dadd9aa5acc682b0a5f441e2c579930dd5",
"0xf8738361820c84773594078252089471e94c459c9f05085fc0d34b5f21e648e05dc6b388058d15e1762800008085034b10ee44a0cc7dd758523571aa417ca7e92ce18fa344eb09ae4030cbe67a20749a5f25cfa6a079260f268e88f5349a96d339a543c81800ddac65ded5bf66ff69bb8d2cf6ac8a",
"0xf8738361820d8477359407825208947c1fe317db82c9298b87c56c3194178271b621e188058d15e1762800008085034b10ee43a03670f304b0cfec63bbb48f6adea22daabf97cb5066464b66dc8b0c5062547c94a046ff008a93f3ce2964b77be87730cd5e4fe706832955148580869189edd18d1e",
"0xf8738361820e847735940782520894e069d1c9abf5127bdc3a164fb93b96bfa9f74ce088058d15e1762800008085034b10ee43a0118c5332b8e96e0938c49205cd48c34651dac16e06b177fe9ccb3c4a5714a818a0335ee8a457a31e10f6ece6faa85587e2527e69e7287f9cbfcbe977033091d5cb",
"0xf8738361820f847735940782520894b9bbddd1eb6ef8fb1bdc6a853d5ad7486a9487dd88058d15e1762800008085034b10ee44a0fd2a39184707e76817b5132d4f680f58b814ae07053bff5444341964029676bfa03a96b5d6be262592e72f19bc64ca930f1e76a6655d89ac3640aa927ea066f679",
"0xf87383618210847735940782520894a804387cdaf986d45831e8074efb2115af053f7a88058d15e1762800008085034b10ee44a03809b662c3a9d240d7b7f9e2cb4fb8d50b1e421b4548180704aedea809b2218ea063c9d8136461e93058f75ae91000d4e6f0cf21ff74d5c91c340209c1d04f0bff",
"0xf87383618211847735940782520894f23501d784a041fc911b4c86c2bfb1f63ec170ea88058d15e1762800008085034b10ee44a02af9b021ce8467c713e9ba2dd9a948cdc1d67dd04117868b6897831a4c9b2448a06bb0aa527519e251ec40b747f25dc7c7c2ed689022ac4dd80c4a40c37efa06b3",
"0xf873836182128477359407825208943928be2a7058088313c0fb3294014e88a3c5ed4a88058d15e1762800008085034b10ee44a098ea195f842b013d76e62b19cb989e3d853ca5b84602bad0b777ecdfebc2f0dba0540fdd9b2a2ddcb4a634bf63186c80987bcb9c19d0025ca0cbefba565e4afbbb",
"0xf87383618213847735940782520894196aa07204141478459c14106ef5e5282efe995788058d15e1762800008085034b10ee43a0b817d9e63a606c505074d41ce01c53612ba083cd4b39adaff884c869d539fd6ca06dd3bd03aefe17446e3354dcf4864a712e4ac2e4c59701a41a4f7ffbc937adcd",
"0xf87383618214847735940782520894763cbf89560e2da270000822abda9584db693fa388058d15e1762800008085034b10ee43a09b856c0c79b5f977db2d9c26992f9e2a113e86dfde2fa684ae4a28a45fdf7dd3a078ac54eacbdc28a7b98176c5c79becb1ebee800186c48b664a643ae760cf0318",
"0xf873836182158477359407825208947feaea0ff70ffc9eec2104f57f7136aff4dea68088058d15e1762800008085034b10ee43a0a5d30ceced18dbf6684a5ce409f654f359ad2d6105ca8cfb6c66d0ad18fa840ca06a3f64c8d043f06d2b0d26fde83f003aeb24904226fea3712af38e2c7c805a96",
"0xf87383618216847735940782520894e5466aacd9dd6d3bb35060a1ccc76a438de88ca188058d15e1762800008085034b10ee44a0b2646589ce46644bddfb0bbe5f5dfe9700a82accbc4ef29997094913c1ad85a1a01b65a1ac0f935af862b93dd79559b685d2c6afdf93795e70fc3cf174f95a7d9f",
"0xf87383618217847735940782520894f670980415cfe8c4f8d10645ecf974c9a2fea00e88058d15e1762800008085034b10ee43a06f2d0bca0b319a77accb02cf01e2edf5fff3f5cb38fc99d25a2d19642027e359a05223e3bce8700a92b7770fe92d7c50527505230cea7a5ab05e90c5a7959e0cc1",
"0xf87383618218847735940782520894a29115bce7829ffdd989b7cf1bdd1eac06a2cb3688058d15e1762800008085034b10ee44a09377c8d9a2e30c4c08b6d81fb5b645b6c4938d4047858668cbb6ddd3d67b2433a079e9b9182f4c0c6291a470f6dde8846b0aa729676224081d29940f0c319c54a0",
"0xf873836182198477359407825208948f528aa67dc1846c893465fa1c8c26556bc5fe1988058d15e1762800008085034b10ee43a00e35ce7ac258b4230bbc01045e9b5e1789543dbd324e975504c0c60e9cb5cbcfa057879da45fe069aa050ff7734b6e59b5e127db0f1921b973885ff3ef6aebe3db",
"0xf8738361821a8477359407825208944dc4ec6ac43c8c45777292db987203c0248e17b788058d15e1762800008085034b10ee43a03b63fd365340ceebecdb997d2b4bb41b336683c02eb1139cb1791d286ee13246a003ea3888ed23e1f986d8d3b9d560281d3484dd2fe974c25f7216c90568208b45",
"0xf8738361821b8477359407825208940d2f39f251cb547cba567a31e5e9f93c19dffa8588058d15e1762800008085034b10ee44a050f14998905eb10dc8e53395da51b21b0eefde3bc103b615d6f97d7b44f26e36a008ae2d148621eefa13ef0e3fb6ce1d259c62c8e057180ed49d761fab9d312b93",
"0xf8738361821c8477359407825208949eb31fb94ce5111e2a04cb9d156b513887ccbd0088058d15e1762800008085034b10ee44a07e88781530f943cd64ca63c2b7133bf8ed704356622be4b54f08751b54bf1d2ba00fa89ce3cc8f773cc83a72b7d58327740c41c95679e7003522c489ab9f66f88f",
"0xf8738361821d84773594078252089404b88ef83f8c41b1465d360a1e82f07ae190892a88058d15e1762800008085034b10ee43a096a28917edd16daee0707b0459a228fb5e006a5b9714a5d9cdac681a6960836ca02bce226c902887909a8bba3ad618153e90ff2d93e3825a42b72181bd18154d87",
"0xf8738361821e847735940782520894af23e04b04fbe15630eadd32a6f27a5a65ea554a88058d15e1762800008085034b10ee44a01dd4f73c1317ed5bd565999aa5fc130e388378ccd9f9641ed6a213d357d7bb7ea03e94fdad16c029234002c3ee24d382fae242f60bb58340c2a45488e57ad1bcb1",
"0xf8738361821f847735940782520894746cdff371e3f1e905b3ac52280078bac2dec7dd88058d15e1762800008085034b10ee43a02a4b086a715feb49bb892f026c3995f4235ced4da4a500bd3c960af2c3853ecfa03c14793e2606d25e7b5469a04bca55f50af24abe997afa154ef276ebfc7a6e6c",
"0xf87383618220847735940782520894c33e5155bdbf1a0a7ceb1b80f8586c5cda5c378188058d15e1762800008085034b10ee44a0b23359e18485b91d7599425b986ebb19cee52960533f993c24998636ec169a82a028d4cd7d4bc768fe29a6a3f32065b0a59188239808fbe5bb39e90ccd8eb43989",
"0xf87383618221847735940782520894e7fdef5f5219068f3d0f88a7445005574c66279888058d15e1762800008085034b10ee43a0101fbd3be6b8fc9c5a7f5c8f3c9fec2abcedbac8ab3c7247df2c98e6b968af38a019c11ee7058d8a6eeceb2e8cb45492f442eaaaa7fff37cc7fdd9d17e92e3bfaa",
"0xf87383618222847735940782520894f0a81a63c5e09b0bd08e027de48058e377d3732d88058d15e1762800008085034b10ee44a0f0e999e581df579e31fae928c52a7df96a1524d38f2e41bf4893ae7c4a083a48a022e1d07f18385e6d2f9fce94a25d042f4dee7eabb2df53d9ef694ba27869bb29",
"0xf873836182238477359407825208949878ab34dc3b4a63c80fdb733491472c11d59a5688058d15e1762800008085034b10ee44a0d719bfbbfbdbdab38420d454322ccf418defe93e5d81dffc354e0324d4ebd5dda05859b730b918a46f7e58bc0858aee845eeb07e64a6be8df0357a18b519b3c00b",
"0xf87383618224847735940782520894912859bebae3086ac7a062dee5d68aa8ed2d71ec88058d15e1762800008085034b10ee43a0a73cddf5a57016d0cf7e7f91e0e6e419f1a3e6485159868817ff0383fee03b4ba02a0cbafef77b725e6e3adf60c14463316b2d6780536e29d411044bf9d4a0518f",
"0xf873836182258477359407825208945a0b737ed85049410e5ea61f444d07d5c8c0359f88058d15e1762800008085034b10ee44a06cea82c78f91a7e463a6493ae9aed4039cb220686ed4b650c89e4160835d1a30a062d3191502b312090301ea3dd63b6b29f4e64622639a6e080d4a15a1b2b22ae0",
"0xf87383618226847735940782520894305a5dfd46e6128abce28c03b3ad971f4e4915ff88058d15e1762800008085034b10ee44a06ee9ca5a35966737addbde05c3702020db87737e2f3fe6f783023e69ef49c84da067ac4e87f6f02258d33cc1507006ed81501d1398428016f0b99eac2bd23f54d2",
"0x02f9021e8501a5887710808477359407847735940782dfad9442424242424242424242424242424242424242428901bc16d674ec800000b901a422895118000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001200eabd7137c45a0f283d7839ca69f6bbcee6adc41416bc9e01c31e8ca86b767390000000000000000000000000000000000000000000000000000000000000030a3dc91086418a5680fe3037dba62dda3de79dd22bb41036719c3771f140b419586ae7d9bdf3b10d88850909d4556b19b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020010000000000000000000000bf3da697ab02552a5da95f267075cbe495d1ecb30000000000000000000000000000000000000000000000000000000000000060896bccd536b4a30c4c3ce5877544574c624dff09f100abcb2df38df7c797069aed3213f1320fe77e4cf89907fb23fe4601a9008fdbac478412f8d6a5eb4c53b12c3848c29ced5ded2a7e3fbeb1e1dc4f58a563d761f7ea80c06088126e0dd9eac001a0ccca64755c8eae02419698251ccdc9e3691d2caaf5a104cf9542383c1a548d2da00ac0d62ff682d5edf5746aa633e34a5e8e789ec3eccdbd63b8d12fb80862d80c"
],
"withdrawals": [
{
"index": "481571",
"validator_index": "71016",
"address": "0x7bf8bca0ccd13d04fd466539989efe2adcb0ca7e",
"amount": "34197"
},
{
"index": "481572",
"validator_index": "71017",
"address": "0x7bf8bca0ccd13d04fd466539989efe2adcb0ca7e",
"amount": "34197"
},
{
"index": "481573",
"validator_index": "71018",
"address": "0x7bf8bca0ccd13d04fd466539989efe2adcb0ca7e",
"amount": "34197"
},
{
"index": "481574",
"validator_index": "71019",
"address": "0x7bf8bca0ccd13d04fd466539989efe2adcb0ca7e",
"amount": "34197"
},
{
"index": "481575",
"validator_index": "71020",
"address": "0x7bf8bca0ccd13d04fd466539989efe2adcb0ca7e",
"amount": "34197"
},
{
"index": "481576",
"validator_index": "71021",
"address": "0x7bf8bca0ccd13d04fd466539989efe2adcb0ca7e",
"amount": "34197"
},
{
"index": "481577",
"validator_index": "71022",
"address": "0x7bf8bca0ccd13d04fd466539989efe2adcb0ca7e",
"amount": "34197"
},
{
"index": "481578",
"validator_index": "71023",
"address": "0x7bf8bca0ccd13d04fd466539989efe2adcb0ca7e",
"amount": "34197"
},
{
"index": "481579",
"validator_index": "71024",
"address": "0x7bf8bca0ccd13d04fd466539989efe2adcb0ca7e",
"amount": "34197"
},
{
"index": "481580",
"validator_index": "71025",
"address": "0x7bf8bca0ccd13d04fd466539989efe2adcb0ca7e",
"amount": "34197"
},
{
"index": "481581",
"validator_index": "71026",
"address": "0x7bf8bca0ccd13d04fd466539989efe2adcb0ca7e",
"amount": "34197"
},
{
"index": "481582",
"validator_index": "71027",
"address": "0x7bf8bca0ccd13d04fd466539989efe2adcb0ca7e",
"amount": "34197"
},
{
"index": "481583",
"validator_index": "71028",
"address": "0x7bf8bca0ccd13d04fd466539989efe2adcb0ca7e",
"amount": "34197"
},
{
"index": "481584",
"validator_index": "71029",
"address": "0x7bf8bca0ccd13d04fd466539989efe2adcb0ca7e",
"amount": "34197"
},
{
"index": "481585",
"validator_index": "71030",
"address": "0x7bf8bca0ccd13d04fd466539989efe2adcb0ca7e",
"amount": "34197"
},
{
"index": "481586",
"validator_index": "71031",
"address": "0x7bf8bca0ccd13d04fd466539989efe2adcb0ca7e",
"amount": "34197"
}
],
"blob_gas_used": "1179648",
"excess_blob_gas": "67895296"
},
"bls_to_execution_changes": [],
"blob_kzg_commitments": [
"0x92549576706cd5a855ea6a82537d1c7190c8d7d439f4dcbcf4b9b2bd34c4712d14f4ed041644bca5659e1826570e58b8",
"0xac58961e240bb13fd49d4cd60ace53bf3b6205ab7aee98987f3e589680fbe9d3febf3991150dd2ce0a785b9c7ceb42e2",
"0x83945613f3af6994e24c48b02a39a7c0e9c98f168c0c12ce3bf1d5bc267236ed6770c9c8a1fd7d7a9152066ccfa7147e",
"0x9334f6af7b93b7c1ee1cbac56bd38e9d9d6e50dd110bcee546333ec3ba0702c4aabe16b332ebc657967c66e9789a3a69",
"0xa43089b05e5987070bdbc3b8742bb30d7c34ea0e81518dc26ccb3f3a97c23df6d96f2cf1000ff985b494cc04743c5e2a",
"0xb45aefd32b12b74523a1082102de20e6965b31fb493228a12247e85165346087be8e54f435a60e9c96c1af716d4477c9",
"0x93c5be3093d114302d2dbd86f132c925d7de78bf977c0fd47e0e2c2dff528af381989fe0e50dc82e48d7f5c34d02f6ed",
"0x8a6a84e0ad21a30bacb75e8d0d5c3cf9fa89bb4d72a3cb4462a771cc533227aee35a21030258bb6b5869bebb3075be69",
"0xb54c1dd86ee3f132994ed666988cbdfd9a2b775697ce3e17139850cfe6ab2a5f250a1e2e9103fbe241582eca35d26b25"
],
"execution_requests": {
"deposits": [
{
"pubkey": "0xa3dc91086418a5680fe3037dba62dda3de79dd22bb41036719c3771f140b419586ae7d9bdf3b10d88850909d4556b19b",
"withdrawal_credentials": "0x010000000000000000000000bf3da697ab02552a5da95f267075cbe495d1ecb3",
"amount": "32000000000",
"signature": "0x896bccd536b4a30c4c3ce5877544574c624dff09f100abcb2df38df7c797069aed3213f1320fe77e4cf89907fb23fe4601a9008fdbac478412f8d6a5eb4c53b12c3848c29ced5ded2a7e3fbeb1e1dc4f58a563d761f7ea80c06088126e0dd9ea",
"index": "10011"
}
],
"withdrawals": [],
"consolidations": []
}
}
}

View file

@ -0,0 +1,232 @@
{
"slot": "151850",
"proposer_index": "38060",
"parent_root": "0xcedd94fbf2ebaf371384911b85bb3073eadcca25eeb4ab29d14acd95cd88bcfb",
"state_root": "0x611fa0d96bedd90a2474b2e67f93f5a5edf82af93443079b58658c6739207a30",
"body": {
"randao_reveal": "0xa69041c990d2c6cab84d979be3c9db5081026874c8e37750c4274a355a7acf06f7fbc88857db3c20c84309f61026b20917a99fc826123ae930dba33dc92ced07ebdd57b45b1760fa08309c67fef843ee33ffef518e2ce9cb3c41da9e65d6f6f6",
"eth1_data": {
"deposit_root": "0xd70a234731285c6804c2a4f56711ddb8c82c99740f207854891028af34e27e5e",
"deposit_count": "0",
"block_hash": "0x1b60b6c9500355aa0ff7e1654482fea15eee29f2d52a25aeae859df953c0f7d4"
},
"graffiti": "0x74656b752d676574682d3420544b656466343463616147456230323761393061",
"proposer_slashings": [],
"attester_slashings": [],
"attestations": [
{
"aggregation_bits": "0xfffffffffffffffff7fefeffffffffffffffffffffff7fffffffffffffffffffbffffffffffffbfffffbf7ffbfffffbdfff7ffdfffffffffffffffbff3fefffffffff7fffffdfbfafffffffffffffffffffffffdffffffffffdff7ffffffffffffffffffffdffffdffffffffffffdfffffffffbf7ffffffffffdfffffffffffffffeffffffeffffffffbfefffffefefdffffffffeff7fffffffd7ffbfffffffffffdfffffffdffdbfffffdfdfefffffffffffffffffffff7d9ffffffffdffffffeffffffffeffffffffaf3ffffffffffffff7bfffff7ffffffeffffdffdfffffffffffffffffffffdffffeffefffffffffffbfffffffffffffffd7ffbffffeffdffffbfffffffffffdfffffffefbfffffffffbfffdfdff7f0f",
"data": {
"slot": "151849",
"index": "0",
"beacon_block_root": "0xcedd94fbf2ebaf371384911b85bb3073eadcca25eeb4ab29d14acd95cd88bcfb",
"source": {
"epoch": "4744",
"root": "0x810ed886bb9706fc2df193e2272e3a864eae69b43ca5797789f865b562cc3456"
},
"target": {
"epoch": "4745",
"root": "0x9a8e0a72cf9f99379750c1c8b403c6daed6138c4577610e36d4b405f4f7e307b"
}
},
"signature": "0xaffbe8cdd9c06cb23046969189e66e3c825ac6d0d7170334d9a5d8e7797d47069dbf96f391a86f0eb4b1c6d2eca55b34123567907c3342b4bbe31e520a0e0be813d5986f2d679a2b1f9ee0072bdbde35bf56affc060fd33556733e57103ca4bd",
"committee_bits": "0xffff010000000000"
},
{
"aggregation_bits": "0x00000000002000000000000000100000000000800000000000000000000000000001",
"data": {
"slot": "151849",
"index": "0",
"beacon_block_root": "0x2bafda2b58819919eb49363290dfc50050f3f57cd6a0112ab4b5d4a42ba7c821",
"source": {
"epoch": "4744",
"root": "0x810ed886bb9706fc2df193e2272e3a864eae69b43ca5797789f865b562cc3456"
},
"target": {
"epoch": "4745",
"root": "0x9a8e0a72cf9f99379750c1c8b403c6daed6138c4577610e36d4b405f4f7e307b"
}
},
"signature": "0xa56ae375f437178909c0bf4ea632b94dff322f63bf383c816693c6f9841fb24fa7de3440fbc749cfc0c0d55e341c466b0f019dc77758dae97362c23ce1f8f71d8aab12f7491874d2d7c3e197037f313c840646da75c40577ebc0cdd239524f1a",
"committee_bits": "0x8010000000000000"
},
{
"aggregation_bits": "0x0000000000000000000000000000000400000000000000000000000000000001000000000000000000000000000040000010",
"data": {
"slot": "151848",
"index": "0",
"beacon_block_root": "0x85f3e44f8ea07968ddbca17eafd5b6ae402560a120ce96ae8ab9f06fe9bc2deb",
"source": {
"epoch": "4744",
"root": "0x810ed886bb9706fc2df193e2272e3a864eae69b43ca5797789f865b562cc3456"
},
"target": {
"epoch": "4745",
"root": "0x9a8e0a72cf9f99379750c1c8b403c6daed6138c4577610e36d4b405f4f7e307b"
}
},
"signature": "0x93c1542a02328cda2a26b7a2bdf02480fc244608c232d8239a6519ab5899588b531cc383f1a6462e37167ab6b85eead40296ceda75a9da55805bffd8631642b2197cc3c2684070b68f2543ad406ff992a9f9a3f852f9a4cce664400f4a0850da",
"committee_bits": "0xd000000000000000"
}
],
"deposits": [],
"voluntary_exits": [],
"sync_aggregate": {
"sync_committee_bits": "0xf7f7fffffffbff6ffffffbfffffffdffffffeff7ffdf7ffffeffffffffffffffffffffffffffffffffffffffef777dffffffff7ffeffffebffbf9ffffbffffff",
"sync_committee_signature": "0x86b38c811f1415024af4bdc7b60bbea76e938f326595aa7238ca047bf336ff74707e9da3052d3d376766901b2d097fd5140de9bd4a9c0a5fbd4f12e0ac6ae472d8493a87e6e9bd69590d39ef6f142f7b2990938cd56e54e48bd9a9a55e1a40a1"
},
"execution_payload": {
"parent_hash": "0xef54f75df413929ddfa60638b93feab47a0ad57e7585069308dc3b31beb42e05",
"fee_recipient": "0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134",
"state_root": "0xa694a1983a7427b1ee0524a1619573db4e8f48368d13dde2a1103142e1e77cbb",
"receipts_root": "0x72a0eed2e520b8f791fc8dcafa8a94c3e411cba82097af3fb0287d3698c7bf0a",
"logs_bloom": "0x00200000008000000000000080000040000000000000100000000000200000000000000000800000000080000000000000000000000010000000000000000000000020000000010001800408000000220000000000000000000000000000000000000000000000000000000000000008000000000000000000000810000040000000000000000000008000000000000000000000000000080000004010000000800000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000001000000000000000000000000000000000010200000000000000000000000000000000000000000000",
"prev_randao": "0xbad3a8687ee866a509e9b22c4bd16d16ac2fc5a134fe8ce3477552604e5870c6",
"block_number": "141654",
"gas_limit": "30000000",
"gas_used": "2716694",
"timestamp": "1740426060",
"extra_data": "0xd883010f01846765746888676f312e32332e36856c696e7578",
"base_fee_per_gas": "7",
"block_hash": "0xf6730485a38be5ada3e110990a2c7adaabd2e8d4a49782134f1a8bfbc246a5d7",
"transactions": [
"0x02f9017b8501a588771083013b8485012a05f2008512a05f2000830249f094d27d57804f09a93989e290cf12cb872c39ad2ad280b901040cc7326300000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000056e915d8da78e50000000000000000000000000b3db4f6329df01ac317a70200f6614e1cd0db6f7000000000000000000000000fc7360b3b28cf4204268a8354dbec60720d155d200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000d0ada425f6835193b8507d7de3a77ec1bd6c5377000000000000000000000000c8ae6c2d3f6695e41b5cb149beae76600f4ac97dc080a0212407d77d016985f42a043805a755a10b29c905c7e104c4c0f4bc00f0128ab0a0153258c0796acf19214c28c4554d82022c0f5b14c324068acc895d6e365a43fc",
"0xf873836202478477359407825208946177843db3138ae69679a54b95cf345ed759450d88058d15e1762800008085034b10ee44a0797cebc48b0b84885aae6d7be2e45bb148b09f6a58c7bc35f6d4b0ed4218d4c1a0671bebb2c27f37350c18bc626f7096e22233e8243828efb5831d8de85e230f15",
"0xf87383620248847735940782520894687704db07e902e9a8b3754031d168d46e3d586e88058d15e1762800008085034b10ee43a003a4493dbfa34810445419bea223d56ef71737f28a1c821a46862b173c3a06e8a01fe9f6643b5054a85e45ae0413118dc1c0de71ead60704241fa40ed8ecb432b9",
"0xf8738362024984773594078252089415e6a5a2e131dd5467fa1ff3acd104f45ee5940b88058d15e1762800008085034b10ee44a0c8c7a7b76c6525400c1ce5d7dba14548060d36d2bc4bd846a7a7425217d66957a025449e02d5bcf26d422dfbf41bae2de5e8468d7399043c12da8bbb7f5fb02eed",
"0xf8738362024a84773594078252089480c4c7125967139acaa931ee984a9db4100e0f3b88058d15e1762800008085034b10ee43a01ff07f88e3ef809a7326f7912323ddbed12184b573230d4f269969a48602a5aca052831c32b936474c7e200297135e951b8948130a61f0064ef5bce0fd8ff421f2",
"0xf8738362024b847735940782520894d08a63244fcd28b0aec5075052cdce31ba04fead88058d15e1762800008085034b10ee44a01fd39c5fc58bff2729959025ef99dda0614160f06289efc56e6ccb0b67bf209aa002ec26712f5595db24e57dbef108c3f4e8b1863d71dc0a6746d944c10353f209",
"0xf8738362024c8477359407825208940b06ef8be65fcda88f2dbae5813480f997ee8e3588058d15e1762800008085034b10ee44a0095291ad41e987063ba52692f06dbb29612393dcb40ae89ee3571e660c65db81a07a59182d51c5d07fcd8fc48adf3667a50d9b39185619c52938c741303603609e",
"0xf8738362024d8477359407825208941cb96c5809da5977f99f69c11ee58bae5711c5f188058d15e1762800008085034b10ee44a065c2371b3cded7aee9ffbd9383f86970b06b191d93b1485991f3d25686d86c38a0067905277c21e7402e8af63e6ae6b577d9c669db4a78f61c887cb7bd4c8b117a",
"0xf8738362024e8477359407825208942aa48ee899410a6d97c01b0bb0eeaf1771cc435b88058d15e1762800008085034b10ee43a099694c115ac14278bbc982204081457ec7b12404a86010dfc6877b87a3571bffa0117d6a284e25bda56b7fa75d26ac4429c38659a8f339280f1b7df051fcea84ee",
"0xf8738362024f84773594078252089407b9d920dd8e8d83dc1125c94fc0b3cdcdf602fb88058d15e1762800008085034b10ee43a0b7068fc0a17b7f6dbef5a97f10202748f9f45be2d47a54658d0c4d7da0f0d653a03362d98c11472ab5dad42e39de5175cf756d9fe022a8d0628f5d6da7b5b7525c",
"0xf87383620250847735940782520894fcb6e353ad4f79245c7cb704abcffe2f4868424188058d15e1762800008085034b10ee43a0ed028a4990f492396d8e86f9b0b58f934a8ee54938cbed8f83d68c29752f0bc3a062ce81c4edca14179ec5ce4d046e5d612c23d59f6744599bfd6ec71deb4ce2cf",
"0xf873836202518477359407825208940d3de4256d6322683fdea9ee23765ccbfcb83da488058d15e1762800008085034b10ee44a026d858e4cee789dfedcce3d7f52c8b1e5553a59c41894652dad1fec3f2dfe7f6a04935cb6ee45e295ecbd0d8ece2860f6697786d439c1f8e066e8ceca802bc705b",
"0xf873836202528477359407825208946021752d8d9b2f221d4fea4349dea34ddbcfce5088058d15e1762800008085034b10ee43a0d68f1b33afc775f5ee8e35d72a2abfa5966f6fc6f24cbc74498cc49163554898a02c8fbf6de58c657a47db2deb478659bcc31d92ab1697b336169010d3d937409d",
"0xf8738362025384773594078252089461e296d527edc89e831cf593ec341f16197eeafb88058d15e1762800008085034b10ee44a0cbf42d8ee53861132879567876c2d3d4361a2bde5a8a03d673e8a9e02e2f2198a0315ae79bef63a5105373ab4bc82f4d981674ddf22f555a414394252de873bc7a",
"0xf87383620254847735940782520894cf7317ee7a3b497ecf634b94bff60ff91b92574788058d15e1762800008085034b10ee44a033f4bfc7c5f798824317332a8e1cbc5dc6c3008aa09cb66dd090aa3c2a5cca1da013d43f1901fe898eab79bf8491a7eeb84373a187ec4d181ff1d2e5815cdce8f7",
"0xf873836202558477359407825208947e7b519df31f77ced83eea1b16aedb6dcb0f0b2488058d15e1762800008085034b10ee43a0268a7ead7124d86048cc864fc1ae417c5bc72d569c506a08bfd95abd1a3869eea02ca3d7363cb6f6507d81f070784cb74edb060228c93f740815fe96aa7d884574",
"0xf8738362025684773594078252089488a075e0fb1c9309a200a8bf0a88b214bf7ceb8d88058d15e1762800008085034b10ee43a064a8c228105a6a32632607e256ebb266a1b4ee37fd624ffc1f1ba939cef76066a0221b7fdbf2e6eba0742990b2b6d715f813599ec7ff01cd839901424230e5b0eb",
"0xf87283620257847735940782520894c8d7cfb58f3ac02568e6505bf3fb5eb6f080703988058d15e1762800008085034b10ee449f9df05b5989b41018aae438d7cf6eaab197ac212e434f3c4d97b08a2490b802a02732feacef45e05a73bbf32caf062f0d84bc2daaef88a9527b1ffbcb44a17a21",
"0xf87383620258847735940782520894e0132e8d7b1b766e0ade5543d6c6c0b2d5a2f01d88058d15e1762800008085034b10ee43a029a831ae211d616087569490c670fe44a681465a30144626e9787ea7174d0e01a05fc1c4037e8b7ffc15ced7750833ec8771f1b4d65d1e68381c68cf9523e44c98",
"0xf87383620259847735940782520894eb674c0411db79654afdc1e131f3b6e734baee6c88058d15e1762800008085034b10ee44a01064ff856cbb0bfbcc61383cae3aed541f6fe1e011e86bb842efd25e88429539a046a0b8db35289c2285d917ec68ca78cc2ee7492d8ef7c1e11e63b64d8530c121",
"0xf8738362025a847735940782520894dc07c60993cf689438b8c85f86b0ed938dca77ea88058d15e1762800008085034b10ee44a06f9e81bd4cdb2847e729b1ef1a331a663fc7531ed74a09e59ad02920db531f2da058e52df9092b08b45e4a9b961f431af4dfbb0bd86a48cc162c3e2f0d56d5658a",
"0xf8738362025b847735940782520894110ddc93db59ed31a03518510221ec2f35d28f2f88058d15e1762800008085034b10ee43a02eade5940ccc8290dc553ef3027d086985b03a19cd97928e258c3f4f7b1523aca0549d6ef116c9932490e49144b0287b6bbb702584a17576291241ac15ffc94bb5",
"0xf8738362025c847735940782520894b599a876aaac824cfce21bdf15627c9fd8634c3088058d15e1762800008085034b10ee44a03cd476a5d252e0b18d8d0809f26fe3871b62091cfb43d08dabd0c5874dce3590a063b5231c6c7190ac4e87985d997d46ef4c8bf3177953272fd1d0df9de95a506c",
"0xf8738362025d847735940782520894d36e5540dd71acbd6416d60252c4d7c34a3c824588058d15e1762800008085034b10ee44a07d9bd59a5a4480d84722885e29cd6d33f1cf04a98dcd8e3dcf7321f0fbcbdd74a0354eadbc5e2cac079487f00d57f5fccbcea7936010a7509e6712f8ce39806dbd",
"0xf8738362025e8477359407825208943adeca35af56206a74987a8fe13c669365c770cf88058d15e1762800008085034b10ee43a0a7df3bd9baf87afa5894782cf23385baebd8128d2ff7a1a971234ecf3e6ac7dfa03c811122700ebe14becfa56791483c58a1810dcb7cdd7b3ac3e2ff0b0e1e042c",
"0xf8738362025f847735940782520894d77b95acd12f7b4b5692b55717b7bbca1165195488058d15e1762800008085034b10ee43a0d1b54826d1311a0903886ef61521910679cdaa3cfa70f14b672ea803a955c15fa05e881bdf984a03ba04042f26b46c82cb55662cb3eb23c40618e17cfe1e91d80a",
"0xf87383620260847735940782520894f388bf5766b5ed5d4e1cbf15772e677dbfa80b0088058d15e1762800008085034b10ee43a0ab360b7a78ca54508f199cce95ea4c443482716205c32237b2be1dcfd1cb011aa02fc48513039fd6fa6bd608c9dfbb1965d2e15e70ab6cd67b781ba486bab2d093",
"0xf8738362026184773594078252089435d4996296e58560e6ef47787d51b55f1e2bd92a88058d15e1762800008085034b10ee43a0621eee5a891fdd819305e26ee2a6152e752381b29f7e2af5597bca6ed0f9c441a0747cdd3922479fd516c01172d6562a238576486d0fa084b9f3e603f304b4edea",
"0xf87383620262847735940782520894a4c3b77b898e53d6095f11c53a1ce272cff9af3188058d15e1762800008085034b10ee43a0a73f452201074e6cb8fb75a74da059a7e0928fed9ded27dd17bc4cfd93a22443a0674b3c5006a65687aa6802133fd29c77e68cef8a315175f677e9b6b0dbab56b6",
"0xf873836202638477359407825208946e84f6113fc1919714f0266705813fb81a17181f88058d15e1762800008085034b10ee44a0f054fd8871770486554e18096985f02ee95c032b9be137a8e0026f619b55d2c1a02a4855957e29e3d20223cc41194d0be22f7ef7cf436df2783754147bff8a5c04",
"0xf87383620264847735940782520894e9ae1a806004e1452baae0493920815aadd8479888058d15e1762800008085034b10ee43a0b475580a478780d91e262e2b1697d1faa216effee65d00d2b6357acb47b1ac54a015abc88732edd4be13376b6f910c5b5c6c1929210bf25f384a49fcb48aeb069f",
"0xf87383620265847735940782520894fe1905d8ebd20e037274eef441283c811ea82c1688058d15e1762800008085034b10ee43a089466e0fb9624d16c977ac2d64276e4700c47ef9ff4c8cfce8c801109e8b0b77a05c6b093850d7e34da5d445e203900087275573e83681eda656e3638ec3661547",
"0xf873836202668477359407825208946adece88e477f53a143a4c29d97940df2ec768e088058d15e1762800008085034b10ee44a071f1a5b69f6c8ae0393c4390adb7a3271f99af0c01524e277690ea199dcc2da1a03e887397647eea176bb4d7f7743a0f812a1223da3b465134e4f7b47a267379ef",
"0xf873836202678477359407825208940d34d140a7376892c4593fcea3ae26f5d6f202d788058d15e1762800008085034b10ee43a044af0ef032fe68acae258743e7a913b65bb1bcecd04e3ca23ff4b1c35f8ea248a0180e9622e1cbc136d45607eed1371a21adec0f1e76c9f60ce48de13798462ba4",
"0xf87383620268847735940782520894d1c7fa75b9bc55d041fcdf215f3e3a351c9f9edc88058d15e1762800008085034b10ee44a08b91903cf64c5ea1ef70728d84f3d6c3460e9b0373e39e25e5601a859578388fa059180173244f606fb86eb64187a39db3e6eea697b8eb725d44a348523e1a48c3",
"0xf87383620269847735940782520894418ebe350a8c6387bf5e42f3502742af8e0781f188058d15e1762800008085034b10ee44a084bd185a547656a0e4d8cc84cb47b77509f857bd5de7d6c56182c143665bb7b5a068b10a9e4d24f54d7af772976d4c9806828af12e763207efa4c47e1352590719",
"0xf8738362026a84773594078252089484914d2770c711d27888c775c547b1d933b48c4788058d15e1762800008085034b10ee43a0dd2129a3c3b56301435d67d5da0e8b4e5eaff46e6d30f49608c5b5d50c5e9dfba037b1663d9a8a20bf14f205817728934e3c6f0ec10642c2d24b54e46c0e3160b5",
"0xf8738362026b8477359407825208948f51e560b85edf2e653c689c4e9fac02ce0556b888058d15e1762800008085034b10ee43a009ca07e74aa8d418f7888718ed9014b539c60efb053116aa8e02b0cfc9930e7da05eb1a866237575f7f7de2051f622a8e45192cf7ef57c143eeffac1a54dda4082",
"0xf8738362026c847735940782520894ee2503205c24dc66346e356f13f333fb8782d35888058d15e1762800008085034b10ee43a0f02a886b4bd63232f0c1247c969d3d80c4881b5ea9fc1419564e38dd67f1831aa067f136cfa69edd0fced8be030956974d94a08e3ddbdaf29129e65ebb719dbdb9",
"0xf8728362026d847735940782520894096ba6c59bd667a0fea9a356bcc988e4d9f2d8eb88058d15e1762800008085034b10ee43a0a6b9529f9e732c69e23688f6c5644dd7a73162e427eb9356a1dcf1759a6fee3b9fc127e4986c286784fde59cad6c3f1efd1c7f6d4f7e5088f5c7a74d386b0949",
"0xf8738362026e847735940782520894da0adce4f1dc7debe7b2b52e8fe9ace6c7ea9c6688058d15e1762800008085034b10ee44a0734e8f035d7479da4857ce0e09202d497d831594ec24ff722106c8c7608963fea00a1f8a360c43f0b5fd58a71a2c2c5afe895aa5a15320b052baf1b44434cfe6ac",
"0xf8738362026f847735940782520894af7d412aeab7525c0541dc3aa6c1085cfb8c909988058d15e1762800008085034b10ee43a099e288225e6dbbdc77753aa08f3efbb9f0b96d7def768d9854e855fe01610cd8a034f2d3aae0864b7afd5e691f33bc2d28083110f0111112936da8f45d98319630",
"0xf873836202708477359407825208943cf8c0d567261eaf4ac0872d33a9f48af361769f88058d15e1762800008085034b10ee44a00f20fba4abb6ee417a1f0bdbf776b0d5dbaabc81ecaaba9c58f45360208e33c7a05dde3f3f6b92919e408a0aa72937c33523734a0a2cca772fa2147273d7be1c3b",
"0xf873836202718477359407825208944779242587ba9e828999249eadd82984430f484388058d15e1762800008085034b10ee43a09b5e17f912c9fb7f803b43c2c1397b01b8ab205ff682a4423f0a8214a2e79197a0521997071cda90e04ca276220e6f7dcff723c5d27224528fa3601ef3a2221494",
"0xf87383620272847735940782520894ea531cfe2de357ecff3855b88dbd07f60b03cdca88058d15e1762800008085034b10ee43a0022fc439ceef6778c85bcb9b86eca5cbfa6ac349baa54887159c8585af0e6ecba01080d90e6b555cccd88b693af79881d7f17247b01b3ca62c281c7ddf200ecc79",
"0xf87383620273847735940782520894d00b5f53ea2a66ad33c3fee304bb22857dfb8a8788058d15e1762800008085034b10ee43a042e9e53473b2553d37080c3a8836b183ec8f3b41f47e9bbec02e3aebb7dcdfe1a070bafb99da6e92cf4090bff7eca3c3c8044cb46a9f05f544a0b56a521255d324",
"0xf873836202748477359407825208947ead29f6616f78f21a951c9686dd257be7b8efe488058d15e1762800008085034b10ee43a0a04cae14d43584a0fed384fc6d981238e3cd2302a7fbdbc5245192068abed008a05aef32306b51b2320199e8f3f11280bae5f677b99907b6244ba892d1d441ec12",
"0xf87383620275847735940782520894d503c13ee55c1ea128357d4018ec58d0d5e5c3db88058d15e1762800008085034b10ee43a0e280df73654670e3cf5ae5aeee1e1931e98cf29ff874ce7d32790f8233d5b287a01892f9b5e6902f7dde4411566c1ed3c499703b0801a0c4b1a9dc1b023d6443a8",
"0xf873836202768477359407825208944ac670d8760faf780468638ef80034876ed8918d88058d15e1762800008085034b10ee43a0514dcddc1910947d09fa8a6c0e6f6cf7d05fb4d5bf158bda354d2e6c6348d581a0047981492e9b9eff0ea70bacfaecba069aa2b2c5269c8469cac42d9b11d19383",
"0xf8738362027784773594078252089424ffb8c97ce443f8d3265ba3316defcfc07c659c88058d15e1762800008085034b10ee44a07fc741180d071dead819a84ba1be1f2eeb518caf707ceadeaad03ea9edecc80aa060eccb7fc130827b061d57c5fb3e168a30dccc1fd4ba6b69df65cf82f78a6bba",
"0xf873836202788477359407825208940c5cafc547ab98c9ceaa1c07fdd6bf7820aeb95488058d15e1762800008085034b10ee44a0787e06914cf15a0d5c805c92ee953eba7efc3f9ba09ca62d0ed2f3b6167ffa60a014fa025423178902bb480a1823bd9cd17a55f7c2fedac8a42f81b834c4f5b547",
"0xf87383620279847735940782520894db8d964741c53e55df9c2d4e9414c6c96482874e88058d15e1762800008085034b10ee43a0ee3e494f464ab6384f2a856e3e4a82b581b60dff1494c5d31d8ffac33133c4dda00fe28dc8352736880aeb0df3b34c846d9e48e056f0ee34115866abd2e4cb9cc9",
"0xf8738362027a847735940782520894ba85bb35ae6ff7a34745993fcf92b9afd34124f188058d15e1762800008085034b10ee44a0697c551925faba103e19343af9b1373c15a915c94c2c2b2248754707ba6753efa0195134d1de635393b68258bbc3a6130da2f6f28e3a21f53cf7c0d309e8604a91",
"0xf8738362027b84773594078252089458871015f5a2d3948264f7c16ad194c80ffd531d88058d15e1762800008085034b10ee44a065fd5136b42302cd12e08ce81d4bbbac0bedb774eb07df3ec89e10d1bd0e3eb3a053ef3a9a4b6b0fc0afc2aa8f0fd6eeeb0f8d79df5010395e68401b34a9c1b932",
"0xf8738362027c8477359407825208942a90af45df70b0031f218cc122598ddf3e10469f88058d15e1762800008085034b10ee44a0b8318c8dc899f7a1c0c57fbe9199901c927788d51460a971328870dd05516748a02811349e21979f97393c1c0707f508bcc9edf7e8932a2058c62fc503c6e298f9",
"0xf8738362027d847735940782520894761bbaaea6ceb265f5262c3b559adc2ad3ed2f0988058d15e1762800008085034b10ee43a0ee810426bf67a8873cc6dc0f41887bc6dcaff093a620b980db51343e80b828a6a05fdaf2729c1dee2029a5bc9174920d1bd16a253e2a20ab69fb4cce9a22bbcb44",
"0xf8738362027e847735940782520894dfe86f51c5e603f1420d1f0ab366bd3bfe23d2a788058d15e1762800008085034b10ee43a053c6702a09db8bfbc3560bc380816ed7c119a95700fc9cc7cb44c631637876aba02bddd96beafe8592027d38a9596efed58b08361c863a905fabf4dac4b6fc22da",
"0xf8738362027f847735940782520894d616547158b05ab5079106dc0336d72763a7287188058d15e1762800008085034b10ee44a05fce463f3ede6e6ea82f59b0e2f1356aa6cc9e4a2092186ff4eeac6144f3d70ea05870f706fea0bb3e813e3ecdacdaa82ad6234cf8cc051d15b0aa5fc041943aff",
"0xf87383620280847735940782520894dc68cd278cb7f5f666ce7b0a3a214a8540ed4dfa88058d15e1762800008085034b10ee43a0e376459fbce80ca4941fdeb313efbe587a7567cf3a34c477df38b7409b043e69a03ba4f66d19838df4276344d1c44253d83789e1c77ee8a40d4c8c447789723386",
"0xf8738362028184773594078252089411f8107da05b6905e8cc0227ca3b0c6eb764fac088058d15e1762800008085034b10ee44a03bcb59ba309b004c55ba3df3eec064e22fcfb5a57c8e5ca0ff146bda02774a75a07ed6d73ad8a0daa1f1c98430ad5e5fbdb4748554c8ffa0233638077e79deac03",
"0xf8738362028284773594078252089404da906545679850a7ee0ef6836e183031bedc8888058d15e1762800008085034b10ee43a0094da8f36bc9c6ebb5f92ed23a48067f0efc5307094da7c5890a942f654748c2a05883676b2416160acf153351fc86c771a56bf4251aa134897a73cea3c3f4f7b2",
"0xf873836202838477359407825208948bdc25c43c010fd3db6281fcd8f7a0bed18838e388058d15e1762800008085034b10ee44a0dac96e3d17998ae7886f80455c92444fc078f832b424e31c7c4d1cc1c09d5d2ba07104abf0bbd8433c357d0ae0d9ddfaa3e7d8dea15644912d51931b448bbfdada",
"0xf87383620284847735940782520894af16f746b8a834a383fd0597d941fee52b7791eb88058d15e1762800008085034b10ee44a0294814d951013410c28b152df0361a6bdf245cc987ab30a7304251188641d609a05a530ee23d27f69768191325ffe65582ece63791bf09fe197f504fa57bfd5146",
"0xf873836202858477359407825208940c5c736600f8ea58ccb89aa72e3f3634651fd55188058d15e1762800008085034b10ee44a0c1e93262c0f669d8dcafbe0b69d6fa444595b9991e0ab2d643ca82c32bb95779a07890187282502da60706b81047b42099ec2fcf12f967cada17ea595b8d2d50bf",
"0xf873836202868477359407825208946f475e0f0e9eda58556fddc04de9b1a9b6a4cfb488058d15e1762800008085034b10ee43a02954b5ef28644f34235fa8a78138f8a20f451ee904bbcb557ee0d6a57aadebbda01e380d4925b3eeafe722cffec0811d4b546f2962dab2415247a609d017aa6830",
"0xf873836202878477359407825208949b2e76498a695c4dc7d0890069cffa84a9581d2488058d15e1762800008085034b10ee43a0c4c1ad80c72b77662bdc60e5d9d2f8a7d4f7d5e591f7d36bf75792603c350a48a075948e3ef779497e000693a717ebd926dc2f1040a9fe108067b827470250cb5f",
"0xf87383620288847735940782520894e2d2b2069f4a54fcc171223ff0c17adbd743c28588058d15e1762800008085034b10ee44a0c081205b252b273039c2e97df9831d3f3660a5d9c6432de2d9005d24bc1c949fa015a60d2b5a33cbc1d41626d844ab093695cffb1d445a7059bc30a0ee70130bba",
"0xf87383620289847735940782520894386bd49f04322544f3c7178fa5ae1a24b947b45488058d15e1762800008085034b10ee44a02a76823c284c6d383ce1605f433ee24e9d25755d9914355e7ec0a2e747a31aaca023bfe68b343ec5da856c221a08e42325e2fd465cafa52872885e471e87a26783",
"0xf8738362028a84773594078252089400af839c3fc067fafc2e0a205858d6957f0dd18d88058d15e1762800008085034b10ee43a02dd6ac183d86166f2d48f9e5798998beb2492203130dc29a8ccd60d682c68241a060f45d781a16584462adde0ca311cdd99a0c46c8e96c772e10dec6e21958ca64",
"0xf8738362028b847735940782520894ebb6d32a650afa9221b55a11c6a6de52b6f07cd788058d15e1762800008085034b10ee44a0d7d9588987d423ddf1180b35560d0cc8c350c4eddf3b4f04d491bd952d1ee939a07b95e674b777c204738e34c332a4c96e067d1fd530ca4e17196c9c8a2a84428b",
"0xf8738362028c847735940782520894011d26a3a9adc9203c8943a6a77aa8657af5242088058d15e1762800008085034b10ee44a0780c40f1a71ed2631a01df292f842ee8a8dc98d1b80d0685d92ac22dfc2fffe2a051a4e8f7bae167c3124d47938c54639fff25e9b159507881f6b9d891eb50d805",
"0xf8738362028d8477359407825208949c85bc61a89fb5abd957e6c819c653fc1aa0d11b88058d15e1762800008085034b10ee43a0f041d0b9537468ebf005b0fad79296b1b74e63af050e13f31999737130346deba02d50794faca4b4e1515287ae2690e4d7b7855f29a56a6d644f8c828b6d30a997",
"0xf8728362028e847735940782520894bd8e8435b7897d87cf7cedb5cf8c5dd865dbf72088058d15e1762800008085034b10ee439fcc0821fc19eab1c1e6e15d7447aed84f079a3d178cbc9e865e6009a58b558ea006d296a4d03301ff9663336813b95368dd3553f88d2635ec7559416445f214b4",
"0xf8738362028f847735940782520894adebee2e3ff041078b62380d001c6e51b4f1559888058d15e1762800008085034b10ee44a0e67f178fbd1a91014a87c50bf50898474c1fd80cc782aa05cc2d7efc427d5c13a045ddd298df67dfac72dab9b255066e81b4a1a7988e61b6c3b0318b788733e0fd",
"0xf8738362029084773594078252089471e94c459c9f05085fc0d34b5f21e648e05dc6b388058d15e1762800008085034b10ee44a0f16ec0330912b815ea3ad0d955766eebe92031c7a2d64bf786b7f55bb4f63798a03d03f718b4de0f491a08315e6fac7fe9ee98098ceb3d27ec4128da1f4b4f8153",
"0xf873836202918477359407825208947c1fe317db82c9298b87c56c3194178271b621e188058d15e1762800008085034b10ee43a089edd1266f626652a65dddd34bcecd33cf0975ad6664cec15b706910d41d23b0a055f7752a91aa4759e5c84a4bb2180ac78810ef99246e52e8974fea996efaf8d4",
"0xf87383620292847735940782520894e069d1c9abf5127bdc3a164fb93b96bfa9f74ce088058d15e1762800008085034b10ee43a00a809e015ed5159d60be66d8b65efa57f72be941e9928d889c6be888096896e6a0724239814c01e730b19dd093cee6384864964e59d34d84cf1e9a8116a11a7155",
"0xf87383620293847735940782520894b9bbddd1eb6ef8fb1bdc6a853d5ad7486a9487dd88058d15e1762800008085034b10ee44a08717c8d1194e516264fb1ea3cb78b73f5dd507d17823eff675c97bf304a90b2da05a96d1bfd29677fa4e97fd37bc4e4962a9ab6f99fcf1c74bf4749a97adaf0e3c",
"0xf87383620294847735940782520894a804387cdaf986d45831e8074efb2115af053f7a88058d15e1762800008085034b10ee44a00d92c86dad2165ddae28c5081638c3eea7a54bf7c15f75d2e3a0d52af40d3c97a043c833c190d2e78ce01fa96a331e475fc41f7b51622bf4f081e4831de49ba07e",
"0xf87383620295847735940782520894f23501d784a041fc911b4c86c2bfb1f63ec170ea88058d15e1762800008085034b10ee43a05d27e028f1ef0555f022f214e222a33266971b98098643ba84578f70598781f0a0548bd8b2ba26f7f4b96bea6187981f5af5c36cbe0c57558d6144eb7590668a70",
"0xf873836202968477359407825208943928be2a7058088313c0fb3294014e88a3c5ed4a88058d15e1762800008085034b10ee44a092c573d072e4fbd59fbe85d740fb99b1cc94a97c0896020b1eb4c50a0c1194b2a07edd4babced9ec70bb660fefae771aaf3244341da1e3123b42e6885a933c3d2d",
"0xf87383620297847735940782520894196aa07204141478459c14106ef5e5282efe995788058d15e1762800008085034b10ee43a08d6b3fa7173e77e69d0c9a851396dd8b457d32e15a916082002f367acc0605bea06c7319e1eabe15b1887633080dac1be3e3611861e2ead80487a0858b24f2f9ee",
"0xf87383620298847735940782520894763cbf89560e2da270000822abda9584db693fa388058d15e1762800008085034b10ee44a06a92950880b902296ea1c6d8ade6011d7249107a520c4935dee51dd59de41389a04701288ac10e1ede1468e197258e7b38a5157ed96cffdde2c99314fc9e64e260",
"0xf873836202998477359407825208947feaea0ff70ffc9eec2104f57f7136aff4dea68088058d15e1762800008085034b10ee44a04d90fec69d1b3ffa4e21a81a12bde21223baf92be9c972ec983454d8271870a0a0159b7c2b5238c515755b3f7812afbb5d4726a957a9c9767c3d4c044f83274185",
"0xf8738362029a847735940782520894e5466aacd9dd6d3bb35060a1ccc76a438de88ca188058d15e1762800008085034b10ee43a0837538d8e417194bed46f36eeba282fecbbb58bcbefc6f7e018c42a73ae81345a038b1d65f0d27ade5bf1d263fb67c758e76d1ef797cca936f3412e355074fb994",
"0xf8738362029b847735940782520894f670980415cfe8c4f8d10645ecf974c9a2fea00e88058d15e1762800008085034b10ee43a0ac3f8bba9b19c4d9a3a0d03ed515f81da94b6ac72284b4d8a27c4922aae659c0a078c566cccfde53296c238d408e0ab3dd4a07df1199c12b673be05e45195bbc4e",
"0xf8738362029c847735940782520894a29115bce7829ffdd989b7cf1bdd1eac06a2cb3688058d15e1762800008085034b10ee44a01edd6db1f63d1c384edf6bfc0c3253fb8bdf175c913b62fc5d0c76dd2c7bd411a004a5ceb0c53728dbefccd21e5342f25e90fc4e3de69661a96a2b6e8eabe99580",
"0xf8738362029d8477359407825208948f528aa67dc1846c893465fa1c8c26556bc5fe1988058d15e1762800008085034b10ee44a0b8eaa2b524d2970d341996da7babffdaf92160c755e9b4ba6e453e800a93c5fea05b0bf41c26aefc1fa29539c34d32f42174fe27846ef4ea8427f9f64414ab2994",
"0xf8738362029e8477359407825208944dc4ec6ac43c8c45777292db987203c0248e17b788058d15e1762800008085034b10ee43a061f4bd7053ee4bcc8d1a3edf748d9d3a585134c1fa61129951a094d5069705a6a03e45012ff902405e6c78e0e7d8be2abbc079b8c79af32022f3f6ded85a023a22",
"0xf8738362029f8477359407825208940d2f39f251cb547cba567a31e5e9f93c19dffa8588058d15e1762800008085034b10ee44a0bcc76559b8384c5f31678d1efe17f1c7c9267ee4e50c688421fe269a18ff8ab5a07ededd086d4f210c05ac901c987a1b30106cd76e7cef0a6723bb1dc1b20ccd30",
"0xf873836202a08477359407825208949eb31fb94ce5111e2a04cb9d156b513887ccbd0088058d15e1762800008085034b10ee44a0deb981e565672e01dcc8cd6514c459fd3936380d87f7fadb2660f7c350ac2e51a014849dcb352073dc9663c0a289710145efd7428cbe0c88213611432761ad93b9",
"0xf873836202a184773594078252089404b88ef83f8c41b1465d360a1e82f07ae190892a88058d15e1762800008085034b10ee44a07e3e84ca883d7d8d4e51f36454609b4e6b313a98ea8d226d47682612ae87f812a052687dd82b4f62e8449f7c0034b950e82c78aab8a5518592ba5b652c72b36017",
"0xf873836202a2847735940782520894af23e04b04fbe15630eadd32a6f27a5a65ea554a88058d15e1762800008085034b10ee43a0b90c86c8c6d5254ebdb8b82640ab629ccd6eb9acebfcf53b931949872d9aa6d9a02cbee4611a01bc2d694b3a3b86070ef762b497fbb2b9a9c3bca6b8c0112f97d4",
"0xf873836202a3847735940782520894746cdff371e3f1e905b3ac52280078bac2dec7dd88058d15e1762800008085034b10ee44a0751ea5aa4636525bec891fd848390f6e032587d1bbda675c0e393e410844a1d6a032fdeac6961819450d3059c61f97b60f5cd50e27a18c392539719af3f194f53f",
"0xf873836202a4847735940782520894c33e5155bdbf1a0a7ceb1b80f8586c5cda5c378188058d15e1762800008085034b10ee43a02fb25f3cb803a573ca57d7782a90267f5e2e92fd018f01fd7c5152beb3e2e179a0732710f218d2b8fa87143dd9c10e239aa7269da3dd08ed081cdef99fc28f2561",
"0xf873836202a5847735940782520894e7fdef5f5219068f3d0f88a7445005574c66279888058d15e1762800008085034b10ee43a0f06655172c1d09a30c06930a24caadcd736beba5b63aa137ef0701e57820bceaa01821754fd8180b0ff357c500232f2008e703d2ac6b6fe0a14c202a1c039bdcc7",
"0xf873836202a6847735940782520894f0a81a63c5e09b0bd08e027de48058e377d3732d88058d15e1762800008085034b10ee43a0443ce407a5a49665e5b7847293b62446a2bf6d5885309b75c72c93cc715f8202a05151017b926df1bcfff2c3309e4771f2310b5ed0ded4503e6c4e57d78cc2b380",
"0xf873836202a78477359407825208949878ab34dc3b4a63c80fdb733491472c11d59a5688058d15e1762800008085034b10ee43a084ae376d4ecb7d0e23df6dd426999005f05fbcfffb8e1916d13e53f492fa058ca03b42dfaf1fcff5912e20d3bc9d0e26f61ca37a0c2647a62d20e443db23ebf400",
"0xf873836202a8847735940782520894912859bebae3086ac7a062dee5d68aa8ed2d71ec88058d15e1762800008085034b10ee44a0e9172415691d94476adc2c2fb0b5d68285ce83ab024dc53166f870decec3a4eba07e5f2f8a52145948789e57e3311da5edcbf563307ff0987342424f4e23055673",
"0xf873836202a98477359407825208945a0b737ed85049410e5ea61f444d07d5c8c0359f88058d15e1762800008085034b10ee43a0db10a0538cfe66a2152e6ed531eb5be016ec5f1cead127b91b8f123e54efd8e3a046c6477c90bae429b65e594b254a396bfddfdc15e1e3cec9ffa26a46a4884ccf",
"0xf873836202aa847735940782520894305a5dfd46e6128abce28c03b3ad971f4e4915ff88058d15e1762800008085034b10ee43a074cf4ff900d6170b4266deced030c14cb92d9872837e9cbae3b768c3f561d1c8a0252ba383431d7108ac2402bc6f8f807177496bd9997643994a9c33dfeda303b4",
"0x03f901198501a58877108216c784773594008477359407830186a094dba34be211bf539e27107cbe7e6893770280aec580b880297fb846f12a980aebe1d5b1187b2e6660f3e68f457379b59395a0411b9859c25842606f527f16e565e886e3afcd0ede18e04e3c827adf9e5c54db37870db32c31a18813d44c608f5260b660af53605b60b053605760b153603060b25360f060b35360e760b453600060425d600060c95d600060895d600060e95d7f25818248c0830f4240e1a001b7f74a7d5bae21aaf16e7b2d2ca7f47c800636b2a3f8b519aabb4b3541708801a0faebdfbd5a7832abed65c7fe25ffdb498b0c19c70cd53a1ec53e3de6c25ca0eaa03690227cf2f99e38b48a44f739fd63b00482017bee063657b418d85e68cd2094",
"0x03f901198501a5887710823ba784773594008477359407830186a09409fc772d0857550724b07b850a4323f39112aaaa80b8800a7f9b44ddad8f17bce75dceb28c1eafa80fbfaf4e0d793fc9fc42489c913ffec7906023527f0d1debc2884eec1a57d882e7085ae952d5d76b3e168370cc9af6294ccb55a7ca604352606460635360d96064536042606553603a60665360f1606753609160685360cb606953600c606a536055606b536043606c53603b606d53c0830f4240e1a00159f296fad44019f3cfa307df01412afa1643a67ccafae96c83d43b6758ffe080a04b6af5cfdca885787b508751e15ebeddc4ae4f5e88035002a72fe720ed9c1808a07781ed6c6f1778c74f692c9b966d1c27185045042cf386eab20543852afd7d9f",
"0x03f901198501a5887710823ba884773594008477359407830186a094000000000000000000000000000000000000000080b8801660f660ce53604b60cf53601260d053600b63df83d68853600263df83d68953604f63df83d68a53601b63df83d68b5360b263df83d68c53601363df83d68d5360f663df83d68e537f93ad362c9daf922157be015981847c3d63de3a139a88e7824d2c62693233acba60e4527fb97203d3cc1f05cb5b3a18ba38b93d6eae2675c0830f4240e1a00125c14e94773c0f2504208d25925d0def274e4cd05be154b64484cd55068b9401a01f25239f9cf530e73210788333c06832ede4d25811fbd5288f1c6ec09626a848a0142054f5501636d093d1f8fd9a18991a035ff8272ccc4b732e618929c5ae60a1",
"0x03f901198501a5887710823ba984773594008477359407830186a094000000000000000000000000000000000000000080b8807f1c1e1707ec5a3c08643377106467f9d8cf7343cddde951134184de69c4a0ab5f60cd527f496d383985b30e2192e37082533c4040429f17d2d8066cbbb0df4ce5f1bcc72760ed527f934a6a5a54f24b814ed3df79092d3215e14b5fe7c1ae730e377b17777e7d3afc61010d527f324e451356f8780882a2abd81a56fa0ad656c0830f4240e1a0017fde88c70f2ed440c9dbdcee3973e4d7b85d3a65a7d3db7289a2a16db2f00480a0064a83db9cfa03f6af8b2e48c5b383d57f3de72c1a556bdb89f84ac5a33ef294a07a99426dd140e3f729bf681e9493c93b9f5466afab8bd43b08d44b83121e2c66",
"0x03f901198501a5887710823baa84773594008477359407830186a0947a40026a3b9a41754a95eec8c92c6b99886f440c80b8806000603d5d6000605e5d7f2a4466d55bb88fa289d012628a52864aca1c1e1707ec5a3c08643377106467f9606d527fd8cf7343cddde951134184de69c4a0ab5f496d383985b30e2192e37082533c40608d527f40429f17d2d8066cbbb0df4ce5f1bcc727934a6a5a54f24b814ed3df79092d3260ad527f15e14b5fe7c1ae730ec0830f4240e1a00122ad5c899f52d21eba94a21044753587639cf91301db410de58f73d740feef01a02d622199b408f02048f237a3cee8de1cbe7c79de5fe0342f1a6cba7d9d5c53cea012cae1d118329a17eb3cef8d2a24ce1a96afdb6ef7c0fd58f11041f9ebb458a6",
"0x03f901198501a5887710823bab84773594008477359407830186a09401abea29659e5e97c95107f20bb753cd3e09bbbb80b8807f1fcc4bfabba1d810c2e9750fe5f9f3c70adf76d7a54bdf886617f1a099584d2860d8527f410b6d3e2670b1d7b668f22f9b6d1e5bb1b4d12d4bc198e2b642cd9eef1ebf4e60f8527f753d66f7de6d60873b96cac74055a2c05f1822dd662fe1e18b5962e191d8cc88610118527ffd19563f4930341b9ae50f37f40c3519b4a8c0830f4240e1a001ff78fe5af972c7cce1be875aba37ff8b7367ab34a0879d0fde2873c1c34e3301a013580cd6864dabb7ae62de736772696075a507b26d352eecea97581662b11762a03a47f80d8b09c0ed5686c0082aeaa569bd7200bb6cd4a89507c391da4aae8882",
"0x03f901198501a5887710823bac84773594008477359407830186a09401abea29659e5e97c95107f20bb753cd3e09bbbb80b8807febcd375a4548f911bbf94fb20a71723f2bcdf90b1893dede0d86ea0086c94f4663ded9bec5527fa75fc1bbd45e9a2357080a88f65a12c678ad697d56a558ddc54d4bb74c6c6d2663ded9bee5527f75eaa5150042917e755fc935fc3a8af87bf91944c7e3f00eebbf3a6574e80f4663ded9bf05527f27014ac42049105c03fbc0830f4240e1a001466f18955590485e681c566fab7923ffc11c44febd37dfa460e4ae57b2bcc301a08ac71347df4693d6fd98be1cfbdc5b7cc1ddc75f2512d1b85df24c9e1c754563a02145852bc429244eb4311fa25b529d4245e0a1b79581e829e251f92edaaf209a",
"0x03f9013b8501a5887710823bad84773594008477359407830186a09409fc772d0857550724b07b850a4323f39112aaaa80b8807f9e664ccecd377f7cccdc8eacb44927d06ad21fa62b767c61a984a997b503e76a6021527f9884bb7e3c5262eabd6dc6f0daaf38581cc4e7387f25237ef70b3765d9f7f69d6041527f263c0b0f9697916d24f51e6443f9ddc9c00ee8795c6563b153305d4e889ff9336061527f3a7664aab6aa9ff375049c342739cf230af714c0830f4240f842a001148d696bf857b798a51d3cf76e1f30c0ff18dbdb67b4e4651ceb9a81bfe319a0014516a223e4dd099c56e3320d0f6b50e061e00a6548964a52ac15bba57028ed01a05077e103e2aabb0f6f8712c96fdee81a4a4b8b53404286535eb188b02be775dda021063c5abd0394b12c6935dd209bdc413472f558539469806e5baffeea1d00ef",
"0x02f8a98501a5887710078459682f008459682f0e830137a19400000961ef480eb55e80d19ad83579a64c00700201b83882586e0c10f84614733e5752428cbbe809e3699d9a3bc5bc604e81169e6a59304352237e39c8e6644acae5f595f1a8640000000001312d00c001a01d67ab7c6569f238adca00963a31b5bd856960affb9d738ceb78085800ba29e1a0495ad28ef784fb388e2145af0778bfc08386cb0695d1c0ca1022fabff8ccd9a9"
],
"withdrawals": [],
"blob_gas_used": "1179648",
"excess_blob_gas": "68812800"
},
"bls_to_execution_changes": [],
"blob_kzg_commitments": [
"0x82b6012a3307f01e0ec06ce5768f093c75b731e81aa16f08afe60e2e457e2f4370f13ea15baac9c10ad8956cb1599f47",
"0xa758a4d6c5dd35ec363c80f21dbee519db19aa0f69c1318b70dc01738555b97f7607793d072946019e4050b8b8e5105b",
"0xa49b025f2a1c657f4c07df7cb04d430aeafacdc1cf9afaf841cdcb019ba3f61d6151241fcda0b2851f9d5adcd2a67bdb",
"0xb909fcb4f35d2bdb684643f0dc3fbaae73024b7d2e771656cc0117fe34143aa29d882454285d340dbb1c6dcd1cff2d0c",
"0xb9c826af3dbc163bddc7bcd0230b87f57193bf567ee7fa35fc17f970dfe620dee074c61776fb37fc19958996e5cf585f",
"0x8ef3c784f8070d96f4a834640b6d76e589508ac121177a323f49b3a1073b9592d23464487a31a36974c8e571d8573d46",
"0xa8ddab3b3301512e74e7d83b472af200f35d4b8a4df6ea783363035e2250a90ba473f31ea382c4c275e9c37621098dd0",
"0xa33e2ae43573502a30c5f98a9a33c8a6879333f96d24fdff686544fd01b99130114d741e076ff873fb649fe44ab5e81d",
"0x8f01d73cad9ce715598a653f48e9b9003abf2d2084890fc722c1eab65a4678146e81618b84d227652513723f4088cda3"
],
"execution_requests": {
"deposits": [],
"withdrawals": [
{
"source_address": "0x4b6C4667921A132eE6eD26a2DE7654adaE481E4d",
"validator_pubkey": "0x82586e0c10f84614733e5752428cbbe809e3699d9a3bc5bc604e81169e6a59304352237e39c8e6644acae5f595f1a864",
"amount": "20000000"
}
],
"consolidations": []
}
}
}

View file

@ -2,126 +2,130 @@
# version:spec-tests pectra-devnet-6@v1.0.0
# https://github.com/ethereum/execution-spec-tests/releases
# https://github.com/ethereum/execution-spec-tests/releases/download/pectra-devnet-6%40v1.0.0/fixtures_pectra-devnet-6.tar.gz
# https://github.com/ethereum/execution-spec-tests/releases/download/pectra-devnet-6%40v1.0.0/
b69211752a3029083c020dc635fe12156ca1a6725a08559da540a0337586a77e fixtures_pectra-devnet-6.tar.gz
# version:golang 1.24.0
# version:golang 1.24.1
# https://go.dev/dl/
d14120614acb29d12bcab72bd689f257eb4be9e0b6f88a8fb7e41ac65f8556e5 go1.24.0.src.tar.gz
5d04588154d5923bd8e26b76111806340ec55c41af1b05623ea744fcb3d6bc22 go1.24.0.aix-ppc64.tar.gz
7af054e5088b68c24b3d6e135e5ca8d91bbd5a05cb7f7f0187367b3e6e9e05ee go1.24.0.darwin-amd64.tar.gz
dee0ea64411a00b47ded586d5a8e30cfe3acf51564aa1bb24e039a6dca807a29 go1.24.0.darwin-amd64.pkg
fd9cfb5dd6c75a347cfc641a253f0db1cebaca16b0dd37965351c6184ba595e4 go1.24.0.darwin-arm64.tar.gz
b19eb6b7ae87f8371c3e7a84d129db67779a2883d2fffa6bb90412b0167df133 go1.24.0.darwin-arm64.pkg
d0dc34ad86aea746abe245994c68a9e1ad8f46ba8c4af901cd5861a4dd4c21df go1.24.0.dragonfly-amd64.tar.gz
4ee02b1f3812aff4da79c79464ee4038ca61ad74b3a9619850f30435f81c2536 go1.24.0.freebsd-386.tar.gz
838191001f9324da904dece35a586a3156d548687db87ac9461aa3d38fc88b09 go1.24.0.freebsd-amd64.tar.gz
ce6ad4e84a40a8a1d848b7e31b0cddfd1cee8f7959e7dc358a8fa8b5566ea718 go1.24.0.freebsd-arm.tar.gz
511f7b0cac4c4ed1066d324072ce223b906ad6b2a85f2e1c5d260eb7d08b5901 go1.24.0.freebsd-arm64.tar.gz
a1e4072630dc589a2975ef51317b52c7d8599bf6f389fc59033c01e0a0fa705a go1.24.0.freebsd-riscv64.tar.gz
7593e9dcee9f07c3df6d099f7d259f5734a6c0dccc5f28962f18e7f501c9bb21 go1.24.0.illumos-amd64.tar.gz
90521453a59c6ce20364d2dc7c38532949b033b602ba12d782caeb90af1b0624 go1.24.0.linux-386.tar.gz
dea9ca38a0b852a74e81c26134671af7c0fbe65d81b0dc1c5bfe22cf7d4c8858 go1.24.0.linux-amd64.tar.gz
c3fa6d16ffa261091a5617145553c71d21435ce547e44cc6dfb7470865527cc7 go1.24.0.linux-arm64.tar.gz
695dc54fa14cd3124fa6900d7b5ae39eeac23f7a4ecea81656070160fac2c54a go1.24.0.linux-armv6l.tar.gz
a201e4c9b7e6d29ed64c43296ed88e81a66f82f2093ce45b766d2c526941396f go1.24.0.linux-loong64.tar.gz
f3ac039aae78ad0bfb08106406c2e62eaf763dd82ebaf0ecd539adadd1d729a6 go1.24.0.linux-mips.tar.gz
f2e6456d45e024831b1da8d88b1bb6392cca9500c1b00841f525d76c9e9553e0 go1.24.0.linux-mips64.tar.gz
b847893ff119389c939adc2b8516b6500204b7cb49d5e19b25e1c2091d2c74c6 go1.24.0.linux-mips64le.tar.gz
bd4aed27d02746c237c3921e97029ac6b6fe687a67436b8f52ff1f698d330bd9 go1.24.0.linux-mipsle.tar.gz
007123c9b06c41729a4bb3f166f4df7196adf4e33c2d2ab0e7e990175f0ce1d4 go1.24.0.linux-ppc64.tar.gz
a871a43de7d26c91dd90cb6e0adacb214c9e35ee2188c617c91c08c017efe81a go1.24.0.linux-ppc64le.tar.gz
620dcf48c6297519aad6c81f8e344926dc0ab09a2a79f1e306964aece95a553d go1.24.0.linux-riscv64.tar.gz
544d78b077c6b54bf78958c4a8285abec2d21f668fb007261c77418cd2edbb46 go1.24.0.linux-s390x.tar.gz
8b143a7edefbaa2a0b0246c9df2df1bac9fbed909d8615a375c08da7744e697d go1.24.0.netbsd-386.tar.gz
67150a6dd7bdb9c4e88d77f46ee8c4dc99d5e71deca4912d8c2c85f7a16d0262 go1.24.0.netbsd-amd64.tar.gz
446b2539f11218fd6f6f6e3dd90b20ae55a06afe129885eeb3df51eb344eb0f6 go1.24.0.netbsd-arm.tar.gz
370115b6ff7d30b29431223de348eb11ab65e3c92627532d97fd55f63f94e7a8 go1.24.0.netbsd-arm64.tar.gz
cbda5f15f06ed9630f122a53542d9de13d149643633c74f1dcb45e79649b788a go1.24.0.openbsd-386.tar.gz
926f601d0e655ab1e8d7f357fd82542e5cf206c38c4e2f9fccf0706987d38836 go1.24.0.openbsd-amd64.tar.gz
8a54892f8c933c541fff144a825d0fdc41bae14b0832aab703cb75eb4cb64f2c go1.24.0.openbsd-arm.tar.gz
ef7fddcef0a22c7900c178b7687cf5aa25c2a9d46a3cc330b77a6de6e6c2396b go1.24.0.openbsd-arm64.tar.gz
b3b5e2e2b53489ded2c2c21900ddcbbdb7991632bb5b42f05f125d71675e0b76 go1.24.0.openbsd-ppc64.tar.gz
fbcb1dbf1269b4079dc4fd0b15f3274b9d635f1a7e319c3fc1a907b03280348e go1.24.0.openbsd-riscv64.tar.gz
33b4221e1c174a16e3f661deab6c60838ac4ae6cb869a4da1d1115773ceed88b go1.24.0.plan9-386.tar.gz
111a89014019cdbd69c2978de9b3e201f77e35183c8ab3606fba339d38f28549 go1.24.0.plan9-amd64.tar.gz
8da3d3997049f40ebe0cd336a9bb9e4bfa4832df3c90a32f07383371d6d74849 go1.24.0.plan9-arm.tar.gz
b6069da21dc95ccdbd047675b584e5480ffc3eba35f9e7c8b0e7b317aaf01e2c go1.24.0.solaris-amd64.tar.gz
b53c28a4c2863ec50ab4a1dbebe818ef6177f86773b6f43475d40a5d9aa4ec9e go1.24.0.windows-386.zip
f07677013cd7861c5e16067b0a82144c23c4bf72c139c762e142440f4c926f61 go1.24.0.windows-386.msi
96b7280979205813759ee6947be7e3bb497da85c482711116c00522e3bb41ff1 go1.24.0.windows-amd64.zip
4e78016d889431eb16aa0f87868cf52479b90059791c94a4ff45872d0573089e go1.24.0.windows-amd64.msi
53f73450fb66075d16be9f206e9177bd972b528168271918c4747903b5596c3d go1.24.0.windows-arm64.zip
4f9780158fb7996dcbcbc7c7ef208f880b5e8c1f2792ba3ede0c75050c1bc23a go1.24.0.windows-arm64.msi
8244ebf46c65607db10222b5806aeb31c1fcf8979c1b6b12f60c677e9a3c0656 go1.24.1.src.tar.gz
8d627dc163a4bffa2b1887112ad6194af175dce108d606ed1714a089fb806033 go1.24.1.aix-ppc64.tar.gz
addbfce2056744962e2d7436313ab93486660cf7a2e066d171b9d6f2da7c7abe go1.24.1.darwin-amd64.tar.gz
58d529334561cff11087cd4ab18fe0b46d8d5aad88f45c02b9645f847e014512 go1.24.1.darwin-amd64.pkg
295581b5619acc92f5106e5bcb05c51869337eb19742fdfa6c8346c18e78ff88 go1.24.1.darwin-arm64.tar.gz
78b0fc8ddc344eb499f1a952c687cb84cbd28ba2b739cfa0d4eb042f07e44e82 go1.24.1.darwin-arm64.pkg
e70053f56f7eb93806d80cbd5726f78509a0a467602f7bea0e2c4ee8ed7c3968 go1.24.1.dragonfly-amd64.tar.gz
3595e2674ed8fe72e604ca59c964d3e5277aafb08475c2b1aaca2d2fd69c24fc go1.24.1.freebsd-386.tar.gz
47d7de8bb64d5c3ee7b6723aa62d5ecb11e3568ef2249bbe1d4bbd432d37c00c go1.24.1.freebsd-amd64.tar.gz
04eec3bcfaa14c1370cdf98e8307fac7e4853496c3045afb9c3124a29cbca205 go1.24.1.freebsd-arm.tar.gz
51aa70146e40cfdc20927424083dc86e6223f85dc08089913a1651973b55665b go1.24.1.freebsd-arm64.tar.gz
3c131d8e3fc285a1340f87813153e24226d3ddbd6e54f3facbd6e4c46a84655e go1.24.1.freebsd-riscv64.tar.gz
201d09da737ba39d5367f87d4e8b31edaeeb3dc9b9c407cb8cfb40f90c5a727a go1.24.1.illumos-amd64.tar.gz
8c530ecedbc17e42ce10177bea07ccc96a3e77c792ea1ea72173a9675d16ffa5 go1.24.1.linux-386.tar.gz
cb2396bae64183cdccf81a9a6df0aea3bce9511fc21469fb89a0c00470088073 go1.24.1.linux-amd64.tar.gz
8df5750ffc0281017fb6070fba450f5d22b600a02081dceef47966ffaf36a3af go1.24.1.linux-arm64.tar.gz
6d95f8d7884bfe2364644c837f080f2b585903d0b771eb5b06044e226a4f120a go1.24.1.linux-armv6l.tar.gz
19304a4a56e46d04604547d2d83235dc4f9b192c79832560ce337d26cc7b835a go1.24.1.linux-loong64.tar.gz
6347be77fa5359c12a5308c8ab87147c1fc4717b0c216493d1706c3b9fcde22d go1.24.1.linux-mips.tar.gz
1647df415f7030b82d4105670192aa7e8910e18563bb0d505192d72800cc2d21 go1.24.1.linux-mips64.tar.gz
762da594e4ec0f9cf6defae6ef971f5f7901203ee6a2d979e317adec96657317 go1.24.1.linux-mips64le.tar.gz
9d8133c7b23a557399fab870b5cf464079c2b623a43b214a7567cf11c254a444 go1.24.1.linux-mipsle.tar.gz
132f10999abbaccbada47fa85462db30c423955913b14d6c692de25f4636c766 go1.24.1.linux-ppc64.tar.gz
0fb522efcefabae6e37e69bdc444094e75bfe824ea6d4cc3cbc70c7ae1b16858 go1.24.1.linux-ppc64le.tar.gz
eaef4323d5467ff97fb1979c8491764060dade19f02f3275a9313f9a0da3b9c0 go1.24.1.linux-riscv64.tar.gz
6c05e14d8f11094cb56a1c50f390b6b658bed8a7cbd8d1a57e926581b7eabfce go1.24.1.linux-s390x.tar.gz
5dbb287d343ea00d58a70b11629f32ee716dc50a6875c459ea2018df0f294cd8 go1.24.1.netbsd-386.tar.gz
617aa3faee50ce84c343db0888e9a210c310a7203666b4ed620f31030c9fb32f go1.24.1.netbsd-amd64.tar.gz
59a928b7080c4a6ac985946274b7c65ce1cecc0b468ecd992d17b7c12fab9296 go1.24.1.netbsd-arm.tar.gz
28daa8d0feb4aef2af60cefa3305bb9314de7e8a05cbca41ac548964cdfe89b7 go1.24.1.netbsd-arm64.tar.gz
b7382b2f5d99813aeac14db482faa3bfbd47a68880b607fa2a7e669e26bab9cd go1.24.1.openbsd-386.tar.gz
2513b6537c45deead5e641c7ce7502913e7d5e6f0b21c52542fb11f81578690f go1.24.1.openbsd-amd64.tar.gz
853c1917d4fc7b144c55a02842aa48542d5cc798dde8db96dc0fdbc263200e04 go1.24.1.openbsd-arm.tar.gz
6bc207b91e6f6ae3347fb54616a8fb2f5c11983713846a4cef111ff3f4f94d14 go1.24.1.openbsd-arm64.tar.gz
4279260e2f2b94ee94e81470d13db7367f4393b061fee60985528fa0fa430df4 go1.24.1.openbsd-ppc64.tar.gz
6fc4023a0a339ee0778522364a127d94c78e62122288d47d820dba703f81dc07 go1.24.1.openbsd-riscv64.tar.gz
b5eb9fafd77146e7e1f748acfd95559580ecc8d2f15abf432a20f58c929c7cd2 go1.24.1.plan9-386.tar.gz
24dcad6361b141fc8cced15b092351e12a99d2e58d7013204a3013c50daf9fdd go1.24.1.plan9-amd64.tar.gz
a026ac3b55aa1e6fdc2aaab30207a117eafbe965ed81d3aa0676409f280ddc37 go1.24.1.plan9-arm.tar.gz
8e4f6a77388dc6e5aa481efd5abdb3b9f5c9463bb82f4db074494e04e5c84992 go1.24.1.solaris-amd64.tar.gz
b799f4ab264eef12a014c759383ed934056608c483e0f73e34ea6caf9f1df5f9 go1.24.1.windows-386.zip
db128981033ac82a64688a123f631e61297b6b8f52ca913145e57caa8ce94cc3 go1.24.1.windows-386.msi
95666b551453209a2b8869d29d177285ff9573af10f085d961d7ae5440f645ce go1.24.1.windows-amd64.zip
5968e7adcf26e68a54f1cd41ad561275a670a8e2ca5263bc375b524638557dfb go1.24.1.windows-amd64.msi
e28c4e6d0b913955765b46157ab88ae59bb636acaa12d7bec959aa6900f1cebd go1.24.1.windows-arm64.zip
6d352c1f154a102a5b90c480cc64bab205ccf2681e34e78a3a4d3f1ddfbc81e4 go1.24.1.windows-arm64.msi
# version:golangci 1.64.4
# version:golangci 1.64.6
# https://github.com/golangci/golangci-lint/releases/
# https://github.com/golangci/golangci-lint/releases/download/v1.64.4/
f31ef6e20c62acc31eb7b851d611b4bd10256d1b1d8be38f0cceec300b5b60db golangci-lint-1.64.4-darwin-amd64.tar.gz
bb4a9ece7ef66ef49bd6de873f5f5ec801cf93dacf58efb360cc939b4fd37c47 golangci-lint-1.64.4-darwin-arm64.tar.gz
fd8f9046150356e36ad8af31f99219515d5e0ff6e8e9f4204182312447018fcf golangci-lint-1.64.4-freebsd-386.tar.gz
bbae940caa31cb1d2d8d26abd867666cd92746d2ddc63d6917d128c456d715ef golangci-lint-1.64.4-freebsd-amd64.tar.gz
86eb00cbdc5e277857a380d556d78a42b3edf8aae5991de8767dce7da337d73b golangci-lint-1.64.4-freebsd-armv6.tar.gz
c0f7d50f4fcd39ae8bda181c127b078a79d87c885ac472c4cce5b01134dde5bc golangci-lint-1.64.4-freebsd-armv7.tar.gz
31d264fa26a73a583425bc9b39c18de70a330e346fe800e80c277ddb5f8477b6 golangci-lint-1.64.4-illumos-amd64.tar.gz
3dccc18a6b1a0f2518196190ed56277b1480b381231605936eca6b862426b83c golangci-lint-1.64.4-linux-386.deb
b95f3e757c59ada75144bd086478ab63bcda83c2b2a935e60d8f39aff20fbcd2 golangci-lint-1.64.4-linux-386.rpm
8fa58af0022ae0487162c02d08075e2d869c9b5f8613a64b85fd769a2a2d2b93 golangci-lint-1.64.4-linux-386.tar.gz
fa80d2ad920fc74b85a0b93ad1671fd27c9c4d6f22e167cc4e1da758d72dd893 golangci-lint-1.64.4-linux-amd64.deb
8bf2e3c82504a893a12e84ecbc8071f787075d2fdc1ef946deda3d29aae1afad golangci-lint-1.64.4-linux-amd64.rpm
7de8f80a12cd3052610e6be21f08f2220805b1f3e43b3b6ec843f14a12140de2 golangci-lint-1.64.4-linux-amd64.tar.gz
ade222b9ae997a2875e44ed53ca5a129f24944b232132d0a5898f101381d3bfa golangci-lint-1.64.4-linux-arm64.deb
8f9f8617ed0d8b8f62c8dfe69877932c0d85f0d83a9ec3250b04777c7133ada1 golangci-lint-1.64.4-linux-arm64.rpm
debf1f73e71f1dd288957574eab4e7515b1b04c4ac2f127192181da8a42f0e8e golangci-lint-1.64.4-linux-arm64.tar.gz
3f616caa4a16738114ad29076a333c62bb0650fe4846b438c341d235f78ee5aa golangci-lint-1.64.4-linux-armv6.deb
6a7c9d7b7c57d7d440e1e3f133e18e4b5b703604d4ae7f7862fb9ea461887a8b golangci-lint-1.64.4-linux-armv6.rpm
d7b9e3c89af93d98c6b5a555ef48e25bee24f84d7825a29e343e9966404d5b4d golangci-lint-1.64.4-linux-armv6.tar.gz
18179ecf04ace3051450852424b31a4d26644a3912e759cd595e624eee0174bd golangci-lint-1.64.4-linux-armv7.deb
f669fce85d3edc3d557d1e64e2241d895f1ee676f79d139874bf76693cc447a2 golangci-lint-1.64.4-linux-armv7.rpm
9bbaa13b474ecb1560fb05aee0a8cab3e9cc327d41632bee8b553b766f2bf756 golangci-lint-1.64.4-linux-armv7.tar.gz
b5a2725adba2a1b04a5160ffa0730ba30b88c1ee99ecbc65dd7075a8d70f3730 golangci-lint-1.64.4-linux-loong64.deb
28ac00afb3f6407808fe7c15a6002e5fe6b3b461df3fe0223603542e6f9b974a golangci-lint-1.64.4-linux-loong64.rpm
121170309fed82cb64d6fae5020ef2b194e591a53156370c221a80121cc4aa08 golangci-lint-1.64.4-linux-loong64.tar.gz
aadfaab41380f53ada8f7ad4fa9af784bb882eaaa1e3939e048b3a5ea8f2c13f golangci-lint-1.64.4-linux-mips64.deb
6fecbff69086e316dd028050d3ebf82fc466c7f4dc0f6084307054621101c202 golangci-lint-1.64.4-linux-mips64.rpm
430c48034527af028dc608c115166f47b6b67c8cb83a317d9d44876b9753292a golangci-lint-1.64.4-linux-mips64.tar.gz
432126af2bbbd639dbbcc2c265337f104cc613a7b4c5a8fb5e49f182b428f077 golangci-lint-1.64.4-linux-mips64le.deb
cfa663eee0fae721510d6dc31e61db4be51b4e4244eb7de2b6a9bc6d475551e5 golangci-lint-1.64.4-linux-mips64le.rpm
2bacfac65182893c5ceb67b61d5b9d2b24da47726d84cb87d734a6e32f040148 golangci-lint-1.64.4-linux-mips64le.tar.gz
66d86e47d68420c03045eff0698e9acf9f82d97cd1f07d7e52837a6e544eceed golangci-lint-1.64.4-linux-ppc64le.deb
05466bd2d8e82f7245b838e29d0c90cdbaf67375b2b5bc980d8510d5edb206ad golangci-lint-1.64.4-linux-ppc64le.rpm
e013b6980f7140712af0aedc9a661265e1d3c6adc7e9d045734e3686a3601acc golangci-lint-1.64.4-linux-ppc64le.tar.gz
753980d2fb421d35292b71b51f9bb0552f18de2eb181ccf692ef79e086ad2304 golangci-lint-1.64.4-linux-riscv64.deb
73a33a883020298f8c652a4ace4f6888103d80d411fe3e9b77ce0a3b36ff7d3a golangci-lint-1.64.4-linux-riscv64.rpm
020efd09b894dc75012f43b1d4c58e69e5446ca3f79845b5e10a02588def3e1b golangci-lint-1.64.4-linux-riscv64.tar.gz
4d99a16bc9ff5cea5e4ae3568c6254390fbdad8f1d370c287744409112bceff9 golangci-lint-1.64.4-linux-s390x.deb
6e63374a9f95fc09f2bb9501059eb18b8107e08416722ba7b561ae385d80b33b golangci-lint-1.64.4-linux-s390x.rpm
62a6febb892b26eee84a32cc99d04afe90bf62d0970774b1bab75e36b66f53b4 golangci-lint-1.64.4-linux-s390x.tar.gz
5ba4d235f9abd60d2d280f9fe4228c6ddab11ae9fe48d253b7e477046675f1f6 golangci-lint-1.64.4-netbsd-386.tar.gz
061643bb4370a52010e5e76f88f5b5220e51058df50af86b45286263af92b321 golangci-lint-1.64.4-netbsd-amd64.tar.gz
12b2be6c35bcf7a36374e952c7efec7905a17fa3ebe6214924ba8721b766c4af golangci-lint-1.64.4-netbsd-arm64.tar.gz
553a71a677751dccee082ed8afe24cf58bef725f72aa02aedcc6758dd8f44125 golangci-lint-1.64.4-netbsd-armv6.tar.gz
74a5a5a7b8c8cd11dccdec303ee6592d904b09836782e6d658be359737f300ef golangci-lint-1.64.4-netbsd-armv7.tar.gz
b3ceea2187843f9ba6510303141a72465621e3f190a262c0b48707b9e983ea9a golangci-lint-1.64.4-source.tar.gz
fda4de312fcb0b4853b03bcdfb31ac631a68fa0b966f6aa010eb30d14427180b golangci-lint-1.64.4-windows-386.zip
8fc60bd809f86c6431ed8f3d1f25917538c0ebc93e670cfb199f25bbaa56e602 golangci-lint-1.64.4-windows-amd64.zip
b9058c9d2ae8d75048c6ad8885d11e5ac0a6c40be2af85bfa6605e48d69d3eb6 golangci-lint-1.64.4-windows-arm64.zip
809b0131adb1aa8b359becd13222df367b505bc2955bb3b1d2d0dd6f07347b13 golangci-lint-1.64.4-windows-armv6.zip
fc43f8c95d6bec8ba9a3557a0bb63cf9f504137ce6d24cfdc4c330d46f7779d2 golangci-lint-1.64.4-windows-armv7.zip
# https://github.com/golangci/golangci-lint/releases/download/v1.64.6/
08f9459e7125fed2612abd71596e04d172695921aff82120d1c1e5e9b6fff2a3 golangci-lint-1.64.6-darwin-amd64.tar.gz
8c10d0c7c3935b8c2269d628b6a06a8f48d8fb4cc31af02fe4ce0cd18b0704c1 golangci-lint-1.64.6-darwin-arm64.tar.gz
c07fcabb55deb8d2c4d390025568e76162d7f91b1d14bd2311be45d8d440a624 golangci-lint-1.64.6-freebsd-386.tar.gz
8ed1ef1102e1a42983ffcaae8e06de6a540334c3a69e205c610b8a7c92d469cd golangci-lint-1.64.6-freebsd-amd64.tar.gz
8f8dda66d1b4a85cc8a1daf1b69af6559c3eb0a41dd8033148a9ad85cfc0e1d9 golangci-lint-1.64.6-freebsd-armv6.tar.gz
59e8ca1fa254661b2a55e96515b14a10cd02221d443054deac5b28c3c3e12d6b golangci-lint-1.64.6-freebsd-armv7.tar.gz
e3d323d5f132e9b7593141bfe1d19c8460e65a55cea1008ec96945fed563f981 golangci-lint-1.64.6-illumos-amd64.tar.gz
5ce910f2a864c5fbeb32a30cbd506e1b2ef215f7a0f422cd5be6370b13db6f03 golangci-lint-1.64.6-linux-386.deb
2caab682a26b9a5fb94ba24e3a7e1404fa9eab2c12e36ae1c5548d80a1be190c golangci-lint-1.64.6-linux-386.rpm
2d82d0a4716e6d9b70c95103054855cb4b5f20f7bbdee42297f0189955bd14b6 golangci-lint-1.64.6-linux-386.tar.gz
9cd82503e9841abcaa57663fc899587fe90363c26d86a793a98d3080fd25e907 golangci-lint-1.64.6-linux-amd64.deb
981aaca5e5202d4fbb162ec7080490eb67ef5d32add5fb62fb02210597acc9da golangci-lint-1.64.6-linux-amd64.rpm
71e290acbacb7b3ba4f68f0540fb74dc180c4336ac8a6f3bdcd7fcc48b15148d golangci-lint-1.64.6-linux-amd64.tar.gz
718016bb06c1f598a8d23c7964e2643de621dbe5808688cb38857eb0bb773c84 golangci-lint-1.64.6-linux-arm64.deb
ddc0e7b4b10b47cf894aea157e89e3674bbb60f8f5c480110c21c49cc2c1634d golangci-lint-1.64.6-linux-arm64.rpm
99a7ff13dec7a8781a68408b6ecb8a1c5e82786cba3189eaa91d5cdcc24362ce golangci-lint-1.64.6-linux-arm64.tar.gz
90e360f89c244394912b8709fb83a900b6d56cf19141df2afaf9e902ef3057b1 golangci-lint-1.64.6-linux-armv6.deb
46546ff7c98d873ffcdbee06b39dc1024fc08db4fbf1f6309360a44cf976b5c2 golangci-lint-1.64.6-linux-armv6.rpm
e45c1a42868aca0b0ee54d14fb89da216f3b4409367cd7bce3b5f36788b4fc92 golangci-lint-1.64.6-linux-armv6.tar.gz
3cf6ddbbbf358db3de4b64a24f9683bbe2da3f003cfdee86cb610124b57abafb golangci-lint-1.64.6-linux-armv7.deb
508b6712710da10f11aab9ea5e63af415c932dfe7fff718e649d3100b838f797 golangci-lint-1.64.6-linux-armv7.rpm
da9a8bbee86b4dfee73fbc17e0856ec84c5b04919eb09bf3dd5904c39ce41753 golangci-lint-1.64.6-linux-armv7.tar.gz
ad496a58284e1e9c8ac6f993fec429dcd96c85a8c4715dbb6530a5af0dae7fbd golangci-lint-1.64.6-linux-loong64.deb
3bd70fa737b224750254dce616d9a188570e49b894b0cdb2fd04155e2c061350 golangci-lint-1.64.6-linux-loong64.rpm
a535af973499729f2215a84303eb0de61399057aad6901ddea1b4f73f68d5f2c golangci-lint-1.64.6-linux-loong64.tar.gz
6ad2a1cd37ca30303a488abfca2de52aff57519901c6d8d2608656fe9fb05292 golangci-lint-1.64.6-linux-mips64.deb
5f18369f0ca010a02c267352ebe6e3e0514c6debff49899c9e5520906c0da287 golangci-lint-1.64.6-linux-mips64.rpm
3449d6c13307b91b0e8817f8911c07c3412cdb6931b8d101e42db3e9762e91ad golangci-lint-1.64.6-linux-mips64.tar.gz
d4712a348f65dcaf9f6c58f1cfd6d0984d54a902873dca5e76f0d686f5c59499 golangci-lint-1.64.6-linux-mips64le.deb
57cea4538894558cb8c956d6b69c5509e4304546abe4a467478fc9ada0c736d6 golangci-lint-1.64.6-linux-mips64le.rpm
bc030977d44535b2152fddb2732f056d193c043992fe638ddecea21a40ef09fe golangci-lint-1.64.6-linux-mips64le.tar.gz
1ceb4e492efa527d246c61798c581f49113756fab8c39bb3eefdb1fa97041f92 golangci-lint-1.64.6-linux-ppc64le.deb
454e1c2b3583a8644e0c33a970fb4ce35b8f11848e1a770d9095d99d159ad0ab golangci-lint-1.64.6-linux-ppc64le.rpm
fddf30d35923eb6c7bb57520d8645768f802bf86c4cbf5a3a13049efb9e71b82 golangci-lint-1.64.6-linux-ppc64le.tar.gz
bd75f0dd6f65bee5821c433803b28f3c427ef3582764c3d0e7f7fae1c9d468b6 golangci-lint-1.64.6-linux-riscv64.deb
58457207c225cbd5340c8dcb75d9a44aa890d604e28464115a3a9762febaed04 golangci-lint-1.64.6-linux-riscv64.rpm
82639518a613a6705b7e2de5b28c278e875d782a5c97e9c1b2ac10b4ecd7852f golangci-lint-1.64.6-linux-riscv64.tar.gz
131d69204f8ced200b1437731e987cc886edac30dc87e6e1dcbd4f833d351713 golangci-lint-1.64.6-linux-s390x.deb
ca6caf28ca7a1df7cff720f8fd6ac4b8f2eac10c8cbfe7d2eade70876aded570 golangci-lint-1.64.6-linux-s390x.rpm
ed966d28a6512bc2b1120029a9f78ed77f2015e330b589a731d67d59be30b0b1 golangci-lint-1.64.6-linux-s390x.tar.gz
b181132b41943fc77ace7f9f5523085d12d3613f27774a0e35e17dd5e65ba589 golangci-lint-1.64.6-netbsd-386.tar.gz
f7b81c426006e3c699dc8665797a462aecba8c2cd23ac4971472e55184d95bc9 golangci-lint-1.64.6-netbsd-amd64.tar.gz
663d6fb4c3bef57b8aacdb1b1a4634075e55d294ed170724b443374860897ca6 golangci-lint-1.64.6-netbsd-arm64.tar.gz
8c7a76ee822568cc19352bbb9b2b0863dc5e185eb07782adbbaf648afd02ebcd golangci-lint-1.64.6-netbsd-armv6.tar.gz
0ce26d56ce78e516529e087118ba3f1bcd71d311b4c5b2bde6ec24a6e8966d85 golangci-lint-1.64.6-netbsd-armv7.tar.gz
135d5d998168683f46e4fab308cef5aa3c282025b7de6b258f976aadfb820db7 golangci-lint-1.64.6-source.tar.gz
ccdb0cc249531a205304a76308cfa7cda830083d083d557884416a2461148263 golangci-lint-1.64.6-windows-386.zip
2d88f282e08e1853c70bc7c914b5f58beaa6509903d56098aeb9bc3df30ea9d5 golangci-lint-1.64.6-windows-amd64.zip
6a3c6e7afc6916392679d7d6b95ac239827644e3e50ec4e8ca6ab1e4e6db65fe golangci-lint-1.64.6-windows-arm64.zip
9c9c1ef9687651566987f93e76252f7526c386901d7d8aeee044ca88115da4b1 golangci-lint-1.64.6-windows-armv6.zip
4f0df114155791799dfde8cd8cb6307fecfb17fed70b44205486ec925e2f39cf golangci-lint-1.64.6-windows-armv7.zip
# This is the builder on PPA that will build Go itself (inception-y), don't modify!
#
# This version is fine to be old and full of security holes, we just use it
# to build the latest Go. Don't change it.
#
# version:ppa-builder-1 1.19.6
# version:ppa-builder-1.19 1.19.6
# https://go.dev/dl/
d7f0013f82e6d7f862cc6cb5c8cdb48eef5f2e239b35baa97e2f1a7466043767 go1.19.6.src.tar.gz
# version:ppa-builder-2 1.21.9
# version:ppa-builder-1.21 1.21.9
# https://go.dev/dl/
58f0c5ced45a0012bce2ff7a9df03e128abcc8818ebabe5027bb92bafe20e421 go1.21.9.src.tar.gz
# version:ppa-builder-1.23 1.23.6
# https://go.dev/dl/
039c5b04e65279daceee8a6f71e70bd05cf5b801782b6f77c6e19e2ed0511222 go1.23.6.src.tar.gz
# version:protoc 27.1
# https://github.com/protocolbuffers/protobuf/releases/
# https://github.com/protocolbuffers/protobuf/releases/download/v27.1/

View file

@ -25,8 +25,7 @@ Usage: go run build/ci.go <command> <command flags/arguments>
Available commands are:
lint -- runs certain pre-selected linters
check_tidy -- verifies that everything is 'go mod tidy'-ed
check_generate -- verifies that everything is 'go generate'-ed
check_generate -- verifies that 'go generate' and 'go mod tidy' do not produce changes
check_baddeps -- verifies that certain dependencies are avoided
install [ -arch architecture ] [ -cc compiler ] [ packages... ] -- builds packages and executables
@ -155,8 +154,6 @@ func main() {
doTest(os.Args[2:])
case "lint":
doLint(os.Args[2:])
case "check_tidy":
doCheckTidy()
case "check_generate":
doCheckGenerate()
case "check_baddeps":
@ -352,22 +349,6 @@ func downloadSpecTestFixtures(csdb *build.ChecksumDB, cachedir string) string {
// doCheckTidy assets that the Go modules files are tidied already.
func doCheckTidy() {
targets := []string{"go.mod", "go.sum"}
hashes, err := build.HashFiles(targets)
if err != nil {
log.Fatalf("failed to hash go.mod/go.sum: %v", err)
}
build.MustRun(new(build.GoToolchain).Go("mod", "tidy"))
tidied, err := build.HashFiles(targets)
if err != nil {
log.Fatalf("failed to rehash go.mod/go.sum: %v", err)
}
if updates := build.DiffHashes(hashes, tidied); len(updates) > 0 {
log.Fatalf("files changed on running 'go mod tidy': %v", updates)
}
fmt.Println("No untidy module files detected.")
}
// doCheckGenerate ensures that re-generating generated files does not cause
@ -375,12 +356,13 @@ func doCheckTidy() {
func doCheckGenerate() {
var (
cachedir = flag.String("cachedir", "./build/cache", "directory for caching binaries.")
tc = new(build.GoToolchain)
)
// Compute the origin hashes of all the files
var hashes map[string][32]byte
var err error
hashes, err = build.HashFolder(".", []string{"tests/testdata", "build/cache"})
hashes, err = build.HashFolder(".", []string{"tests/testdata", "build/cache", ".git"})
if err != nil {
log.Fatal("Error computing hashes", "err", err)
}
@ -389,13 +371,13 @@ func doCheckGenerate() {
protocPath = downloadProtoc(*cachedir)
protocGenGoPath = downloadProtocGenGo(*cachedir)
)
c := new(build.GoToolchain).Go("generate", "./...")
c := tc.Go("generate", "./...")
pathList := []string{filepath.Join(protocPath, "bin"), protocGenGoPath, os.Getenv("PATH")}
c.Env = append(c.Env, "PATH="+strings.Join(pathList, string(os.PathListSeparator)))
build.MustRun(c)
// Check if generate file hashes have changed
generated, err := build.HashFolder(".", []string{"tests/testdata", "build/cache"})
generated, err := build.HashFolder(".", []string{"tests/testdata", "build/cache", ".git"})
if err != nil {
log.Fatalf("Error re-computing hashes: %v", err)
}
@ -407,6 +389,10 @@ func doCheckGenerate() {
log.Fatal("One or more generated files were updated by running 'go generate ./...'")
}
fmt.Println("No stale files detected.")
// Run go mod tidy check.
build.MustRun(tc.Go("mod", "tidy", "-diff"))
fmt.Println("No untidy module files detected.")
}
// doCheckBadDeps verifies whether certain unintended dependencies between some
@ -843,7 +829,7 @@ func downloadGoBootstrapSources(cachedir string) []string {
csdb := build.MustLoadChecksums("build/checksums.txt")
var bundles []string
for _, booter := range []string{"ppa-builder-1", "ppa-builder-2"} {
for _, booter := range []string{"ppa-builder-1.19", "ppa-builder-1.21", "ppa-builder-1.23"} {
gobootVersion, err := build.Version(csdb, booter)
if err != nil {
log.Fatal(err)

View file

@ -21,7 +21,8 @@ override_dh_auto_build:
# requirements opposed to older versions of Go.
(mv .goboot-1 ../ && cd ../.goboot-1/src && ./make.bash)
(mv .goboot-2 ../ && cd ../.goboot-2/src && GOROOT_BOOTSTRAP=`pwd`/../../.goboot-1 ./make.bash)
(mv .go ../ && cd ../.go/src && GOROOT_BOOTSTRAP=`pwd`/../../.goboot-2 ./make.bash)
(mv .goboot-3 ../ && cd ../.goboot-3/src && GOROOT_BOOTSTRAP=`pwd`/../../.goboot-2 ./make.bash)
(mv .go ../ && cd ../.go/src && GOROOT_BOOTSTRAP=`pwd`/../../.goboot-3 ./make.bash)
# We can't download external go modules within Launchpad, so we're shipping the
# entire dependency source cache with go-ethereum.

View file

@ -93,7 +93,7 @@ func init() {
}
func abigen(c *cli.Context) error {
utils.CheckExclusive(c, abiFlag, jsonFlag) // Only one source can be selected.
flags.CheckExclusive(c, abiFlag, jsonFlag) // Only one source can be selected.
if c.String(pkgFlag.Name) == "" {
utils.Fatalf("No destination package specified (--pkg)")

View file

@ -94,7 +94,7 @@ type Account struct {
> Notifications are not confirmable by definition, since they do not have a Response object to be returned. As such, the Client would not be aware of any errors (like e.g. "Invalid params","Internal error"
### 3.1.0
* Add `ContentType` `string` to `SignDataRequest` to accommodate the latest EIP-191 and EIP-712 implementations.
* Add `ContentType` `string` to `SignDataRequest` to accommodate the latest [EIP-191](https://eips.ethereum.org/EIPS/eip-191) and [EIP-712](https://eips.ethereum.org/EIPS/eip-712) implementations.
### 3.0.0

View file

@ -57,13 +57,7 @@ func TestMain(m *testing.M) {
// This method creates a temporary keystore folder which will be removed after
// the test exits.
func runClef(t *testing.T, args ...string) *testproc {
ddir, err := os.MkdirTemp("", "cleftest-*")
if err != nil {
return nil
}
t.Cleanup(func() {
os.RemoveAll(ddir)
})
ddir := t.TempDir()
return runWithKeystore(t, ddir, args...)
}

View file

@ -163,7 +163,7 @@ func discv4Ping(ctx *cli.Context) error {
defer disc.Close()
start := time.Now()
if err := disc.Ping(n); err != nil {
if _, err := disc.Ping(n); err != nil {
return fmt.Errorf("node didn't respond: %v", err)
}
fmt.Printf("node responded to ping (RTT %v).\n", time.Since(start))

View file

@ -84,7 +84,8 @@ func discv5Ping(ctx *cli.Context) error {
disc, _ := startV5(ctx)
defer disc.Close()
fmt.Println(disc.Ping(n))
_, err := disc.Ping(n)
fmt.Println(err)
return nil
}

View file

@ -24,6 +24,7 @@ import (
"errors"
"fmt"
"io"
"maps"
"math/big"
"os"
"path/filepath"
@ -40,7 +41,6 @@ import (
"github.com/ethereum/go-ethereum/eth/protocols/eth"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"golang.org/x/exp/maps"
)
// Chain is a lightweight blockchain-like store which can read a hivechain
@ -162,8 +162,8 @@ func (c *Chain) RootAt(height int) common.Hash {
// GetSender returns the address associated with account at the index in the
// pre-funded accounts list.
func (c *Chain) GetSender(idx int) (common.Address, uint64) {
accounts := maps.Keys(c.senders)
slices.SortFunc(accounts, common.Address.Cmp)
accounts := slices.SortedFunc(maps.Keys(c.senders), common.Address.Cmp)
addr := accounts[idx]
return addr, c.senders[addr].Nonce
}

View file

@ -130,11 +130,16 @@ func (c *Conn) Write(proto Proto, code uint64, msg any) error {
return err
}
var errDisc error = fmt.Errorf("disconnect")
// ReadEth reads an Eth sub-protocol wire message.
func (c *Conn) ReadEth() (any, error) {
c.SetReadDeadline(time.Now().Add(timeout))
for {
code, data, _, err := c.Conn.Read()
if code == discMsg {
return nil, errDisc
}
if err != nil {
return nil, err
}

View file

@ -17,8 +17,12 @@
package ethtest
import (
"context"
"crypto/rand"
"fmt"
"reflect"
"sync"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
@ -79,6 +83,8 @@ func (s *Suite) EthTests() []utesting.Test {
{Name: "InvalidTxs", Fn: s.TestInvalidTxs},
{Name: "NewPooledTxs", Fn: s.TestNewPooledTxs},
{Name: "BlobViolations", Fn: s.TestBlobViolations},
{Name: "TestBlobTxWithoutSidecar", Fn: s.TestBlobTxWithoutSidecar},
{Name: "TestBlobTxWithMismatchedSidecar", Fn: s.TestBlobTxWithMismatchedSidecar},
}
}
@ -825,3 +831,194 @@ func (s *Suite) TestBlobViolations(t *utesting.T) {
conn.Close()
}
}
// mangleSidecar returns a copy of the given blob transaction where the sidecar
// data has been modified to produce a different commitment hash.
func mangleSidecar(tx *types.Transaction) *types.Transaction {
sidecar := tx.BlobTxSidecar()
copy := types.BlobTxSidecar{
Blobs: append([]kzg4844.Blob{}, sidecar.Blobs...),
Commitments: append([]kzg4844.Commitment{}, sidecar.Commitments...),
Proofs: append([]kzg4844.Proof{}, sidecar.Proofs...),
}
// zero the first commitment to alter the sidecar hash
copy.Commitments[0] = kzg4844.Commitment{}
return tx.WithBlobTxSidecar(&copy)
}
func (s *Suite) TestBlobTxWithoutSidecar(t *utesting.T) {
t.Log(`This test checks that a blob transaction first advertised/transmitted without blobs will result in the sending peer being disconnected, and the full transaction should be successfully retrieved from another peer.`)
tx := s.makeBlobTxs(1, 2, 42)[0]
badTx := tx.WithoutBlobTxSidecar()
s.testBadBlobTx(t, tx, badTx)
}
func (s *Suite) TestBlobTxWithMismatchedSidecar(t *utesting.T) {
t.Log(`This test checks that a blob transaction first advertised/transmitted without blobs, whose commitment don't correspond to the blob_versioned_hashes in the transaction, will result in the sending peer being disconnected, and the full transaction should be successfully retrieved from another peer.`)
tx := s.makeBlobTxs(1, 2, 43)[0]
badTx := mangleSidecar(tx)
s.testBadBlobTx(t, tx, badTx)
}
// readUntil reads eth protocol messages until a message of the target type is
// received. It returns an error if there is a disconnect, or if the context
// is cancelled before a message of the desired type can be read.
func readUntil[T any](ctx context.Context, conn *Conn) (*T, error) {
for {
select {
case <-ctx.Done():
return nil, context.Canceled
default:
}
received, err := conn.ReadEth()
if err != nil {
if err == errDisc {
return nil, errDisc
}
continue
}
switch res := received.(type) {
case *T:
return res, nil
}
}
}
// readUntilDisconnect reads eth protocol messages until the peer disconnects.
// It returns whether the peer disconnects in the next 100ms.
func readUntilDisconnect(conn *Conn) (disconnected bool) {
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
_, err := readUntil[struct{}](ctx, conn)
return err == errDisc
}
func (s *Suite) testBadBlobTx(t *utesting.T, tx *types.Transaction, badTx *types.Transaction) {
stage1, stage2, stage3 := new(sync.WaitGroup), new(sync.WaitGroup), new(sync.WaitGroup)
stage1.Add(1)
stage2.Add(1)
stage3.Add(1)
errc := make(chan error)
badPeer := func() {
// announce the correct hash from the bad peer.
// when the transaction is first requested before transmitting it from the bad peer,
// trigger step 2: connection and announcement by good peers
conn, err := s.dial()
if err != nil {
errc <- fmt.Errorf("dial fail: %v", err)
return
}
defer conn.Close()
if err := conn.peer(s.chain, nil); err != nil {
errc <- fmt.Errorf("bad peer: peering failed: %v", err)
return
}
ann := eth.NewPooledTransactionHashesPacket{
Types: []byte{types.BlobTxType},
Sizes: []uint32{uint32(badTx.Size())},
Hashes: []common.Hash{badTx.Hash()},
}
if err := conn.Write(ethProto, eth.NewPooledTransactionHashesMsg, ann); err != nil {
errc <- fmt.Errorf("sending announcement failed: %v", err)
return
}
req, err := readUntil[eth.GetPooledTransactionsPacket](context.Background(), conn)
if err != nil {
errc <- fmt.Errorf("failed to read GetPooledTransactions message: %v", err)
return
}
stage1.Done()
stage2.Wait()
// the good peer is connected, and has announced the tx.
// proceed to send the incorrect one from the bad peer.
resp := eth.PooledTransactionsPacket{RequestId: req.RequestId, PooledTransactionsResponse: eth.PooledTransactionsResponse(types.Transactions{badTx})}
if err := conn.Write(ethProto, eth.PooledTransactionsMsg, resp); err != nil {
errc <- fmt.Errorf("writing pooled tx response failed: %v", err)
return
}
if !readUntilDisconnect(conn) {
errc <- fmt.Errorf("expected bad peer to be disconnected")
return
}
stage3.Done()
}
goodPeer := func() {
stage1.Wait()
conn, err := s.dial()
if err != nil {
errc <- fmt.Errorf("dial fail: %v", err)
return
}
defer conn.Close()
if err := conn.peer(s.chain, nil); err != nil {
errc <- fmt.Errorf("peering failed: %v", err)
return
}
ann := eth.NewPooledTransactionHashesPacket{
Types: []byte{types.BlobTxType},
Sizes: []uint32{uint32(tx.Size())},
Hashes: []common.Hash{tx.Hash()},
}
if err := conn.Write(ethProto, eth.NewPooledTransactionHashesMsg, ann); err != nil {
errc <- fmt.Errorf("sending announcement failed: %v", err)
return
}
// wait until the bad peer has transmitted the incorrect transaction
stage2.Done()
stage3.Wait()
// the bad peer has transmitted the bad tx, and been disconnected.
// transmit the same tx but with correct sidecar from the good peer.
var req *eth.GetPooledTransactionsPacket
req, err = readUntil[eth.GetPooledTransactionsPacket](context.Background(), conn)
if err != nil {
errc <- fmt.Errorf("reading pooled tx request failed: %v", err)
return
}
if req.GetPooledTransactionsRequest[0] != tx.Hash() {
errc <- fmt.Errorf("requested unknown tx hash")
return
}
resp := eth.PooledTransactionsPacket{RequestId: req.RequestId, PooledTransactionsResponse: eth.PooledTransactionsResponse(types.Transactions{tx})}
if err := conn.Write(ethProto, eth.PooledTransactionsMsg, resp); err != nil {
errc <- fmt.Errorf("writing pooled tx response failed: %v", err)
return
}
if readUntilDisconnect(conn) {
errc <- fmt.Errorf("unexpected disconnect")
return
}
close(errc)
}
if err := s.engine.sendForkchoiceUpdated(); err != nil {
t.Fatalf("send fcu failed: %v", err)
}
go goodPeer()
go badPeer()
err := <-errc
if err != nil {
t.Fatalf("%v", err)
}
}

View file

@ -20,6 +20,7 @@ import (
"encoding/json"
"errors"
"fmt"
"maps"
"os"
"regexp"
"slices"
@ -28,7 +29,6 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/tests"
"github.com/urfave/cli/v2"
"golang.org/x/exp/maps"
)
var blockTestCommand = &cli.Command{
@ -80,8 +80,7 @@ func runBlockTest(ctx *cli.Context, fname string) ([]testResult, error) {
tracer := tracerFromFlags(ctx)
// Pull out keys to sort and ensure tests are run in order.
keys := maps.Keys(tests)
slices.Sort(keys)
keys := slices.Sorted(maps.Keys(tests))
// Run all the tests.
var results []testResult

View file

@ -50,7 +50,7 @@ var (
ArgsUsage: "<genesisPath>",
Flags: slices.Concat([]cli.Flag{
utils.CachePreimagesFlag,
utils.OverrideCancun,
utils.OverridePrague,
utils.OverrideVerkle,
}, utils.DatabaseFlags),
Description: `
@ -103,11 +103,12 @@ if one is set. Otherwise it prints the genesis from the datadir.`,
utils.StateHistoryFlag,
}, utils.DatabaseFlags),
Description: `
The import command imports blocks from an RLP-encoded form. The form can be one file
with several RLP-encoded blocks, or several files can be used.
The import command allows the import of blocks from an RLP-encoded format. This format can be a single file
containing multiple RLP-encoded blocks, or multiple files can be given.
If only one file is used, import error will result in failure. If several files are used,
processing will proceed even if an individual RLP-file import failure occurs.`,
If only one file is used, an import error will result in the entire import process failing. If
multiple files are processed, the import process will continue even if an individual RLP file fails
to import successfully.`,
}
exportCommand = &cli.Command{
Action: exportChain,
@ -212,9 +213,9 @@ func initGenesis(ctx *cli.Context) error {
defer stack.Close()
var overrides core.ChainOverrides
if ctx.IsSet(utils.OverrideCancun.Name) {
v := ctx.Uint64(utils.OverrideCancun.Name)
overrides.OverrideCancun = &v
if ctx.IsSet(utils.OverridePrague.Name) {
v := ctx.Uint64(utils.OverridePrague.Name)
overrides.OverridePrague = &v
}
if ctx.IsSet(utils.OverrideVerkle.Name) {
v := ctx.Uint64(utils.OverrideVerkle.Name)
@ -319,6 +320,9 @@ func importChain(ctx *cli.Context) error {
if err := utils.ImportChain(chain, arg); err != nil {
importErr = err
log.Error("Import error", "file", arg, "err", err)
if err == utils.ErrImportInterrupted {
break
}
}
}
}

View file

@ -183,9 +183,9 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
// makeFullNode loads geth configuration and creates the Ethereum backend.
func makeFullNode(ctx *cli.Context) *node.Node {
stack, cfg := makeConfigNode(ctx)
if ctx.IsSet(utils.OverrideCancun.Name) {
v := ctx.Uint64(utils.OverrideCancun.Name)
cfg.Eth.OverrideCancun = &v
if ctx.IsSet(utils.OverridePrague.Name) {
v := ctx.Uint64(utils.OverridePrague.Name)
cfg.Eth.OverridePrague = &v
}
if ctx.IsSet(utils.OverrideVerkle.Name) {
v := ctx.Uint64(utils.OverrideVerkle.Name)

View file

@ -62,7 +62,7 @@ var (
utils.NoUSBFlag, // deprecated
utils.USBFlag,
utils.SmartCardDaemonPathFlag,
utils.OverrideCancun,
utils.OverridePrague,
utils.OverrideVerkle,
utils.EnablePersonal, // deprecated
utils.TxPoolLocalsFlag,
@ -86,6 +86,7 @@ var (
utils.SnapshotFlag,
utils.TxLookupLimitFlag, // deprecated
utils.TransactionHistoryFlag,
utils.ChainHistoryFlag,
utils.StateHistoryFlag,
utils.LightServeFlag, // deprecated
utils.LightIngressFlag, // deprecated

View file

@ -55,6 +55,9 @@ const (
importBatchSize = 2500
)
// ErrImportInterrupted is returned when the user interrupts the import process.
var ErrImportInterrupted = errors.New("interrupted")
// Fatalf formats a message to standard error and exits the program.
// The message is also printed to standard output if standard error
// is redirected to a different file.
@ -191,7 +194,7 @@ func ImportChain(chain *core.BlockChain, fn string) error {
for batch := 0; ; batch++ {
// Load a batch of RLP blocks.
if checkInterrupt() {
return errors.New("interrupted")
return ErrImportInterrupted
}
i := 0
for ; i < importBatchSize; i++ {

View file

@ -233,9 +233,9 @@ var (
Value: 2048,
Category: flags.EthCategory,
}
OverrideCancun = &cli.Uint64Flag{
Name: "override.cancun",
Usage: "Manually specify the Cancun fork timestamp, overriding the bundled setting",
OverridePrague = &cli.Uint64Flag{
Name: "override.prague",
Usage: "Manually specify the Prague fork timestamp, overriding the bundled setting",
Category: flags.EthCategory,
}
OverrideVerkle = &cli.Uint64Flag{
@ -272,6 +272,12 @@ var (
Value: ethconfig.Defaults.TransactionHistory,
Category: flags.StateCategory,
}
ChainHistoryFlag = &cli.StringFlag{
Name: "history.chain",
Usage: `Blockchain history retention ("all" or "postmerge")`,
Value: ethconfig.Defaults.HistoryMode.String(),
Category: flags.StateCategory,
}
// Beacon client light sync settings
BeaconApiFlag = &cli.StringSliceFlag{
Name: "beacon.api",
@ -1197,7 +1203,7 @@ func setWS(ctx *cli.Context, cfg *node.Config) {
// setIPC creates an IPC path configuration from the set command line flags,
// returning an empty string if IPC was explicitly disabled, or the set path.
func setIPC(ctx *cli.Context, cfg *node.Config) {
CheckExclusive(ctx, IPCDisabledFlag, IPCPathFlag)
flags.CheckExclusive(ctx, IPCDisabledFlag, IPCPathFlag)
switch {
case ctx.Bool(IPCDisabledFlag.Name):
cfg.IPCPath = ""
@ -1295,8 +1301,8 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) {
cfg.NoDiscovery = true
}
CheckExclusive(ctx, DiscoveryV4Flag, NoDiscoverFlag)
CheckExclusive(ctx, DiscoveryV5Flag, NoDiscoverFlag)
flags.CheckExclusive(ctx, DiscoveryV4Flag, NoDiscoverFlag)
flags.CheckExclusive(ctx, DiscoveryV5Flag, NoDiscoverFlag)
cfg.DiscoveryV4 = ctx.Bool(DiscoveryV4Flag.Name)
cfg.DiscoveryV5 = ctx.Bool(DiscoveryV5Flag.Name)
@ -1528,52 +1534,11 @@ func setRequiredBlocks(ctx *cli.Context, cfg *ethconfig.Config) {
}
}
// CheckExclusive verifies that only a single instance of the provided flags was
// set by the user. Each flag might optionally be followed by a string type to
// specialize it further.
func CheckExclusive(ctx *cli.Context, args ...interface{}) {
set := make([]string, 0, 1)
for i := 0; i < len(args); i++ {
// Make sure the next argument is a flag and skip if not set
flag, ok := args[i].(cli.Flag)
if !ok {
panic(fmt.Sprintf("invalid argument, not cli.Flag type: %T", args[i]))
}
// Check if next arg extends current and expand its name if so
name := flag.Names()[0]
if i+1 < len(args) {
switch option := args[i+1].(type) {
case string:
// Extended flag check, make sure value set doesn't conflict with passed in option
if ctx.String(flag.Names()[0]) == option {
name += "=" + option
set = append(set, "--"+name)
}
// shift arguments and continue
i++
continue
case cli.Flag:
default:
panic(fmt.Sprintf("invalid argument, not cli.Flag or string extension: %T", args[i+1]))
}
}
// Mark the flag if it's set
if ctx.IsSet(flag.Names()[0]) {
set = append(set, "--"+name)
}
}
if len(set) > 1 {
Fatalf("Flags %v can't be used at the same time", strings.Join(set, ", "))
}
}
// SetEthConfig applies eth-related command line flags to the config.
func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
// Avoid conflicting network flags
CheckExclusive(ctx, MainnetFlag, DeveloperFlag, SepoliaFlag, HoleskyFlag)
CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer
flags.CheckExclusive(ctx, MainnetFlag, DeveloperFlag, SepoliaFlag, HoleskyFlag)
flags.CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer
// Set configurations from CLI flags
setEtherbase(ctx, cfg)
@ -1607,10 +1572,19 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
if ctx.IsSet(SyncTargetFlag.Name) {
cfg.SyncMode = ethconfig.FullSync // dev sync target forces full sync
} else if ctx.IsSet(SyncModeFlag.Name) {
if err = cfg.SyncMode.UnmarshalText([]byte(ctx.String(SyncModeFlag.Name))); err != nil {
Fatalf("invalid --syncmode flag: %v", err)
value := ctx.String(SyncModeFlag.Name)
if err = cfg.SyncMode.UnmarshalText([]byte(value)); err != nil {
Fatalf("--%v: %v", SyncModeFlag.Name, err)
}
}
if ctx.IsSet(ChainHistoryFlag.Name) {
value := ctx.String(ChainHistoryFlag.Name)
if err = cfg.HistoryMode.UnmarshalText([]byte(value)); err != nil {
Fatalf("--%s: %v", ChainHistoryFlag.Name, err)
}
}
if ctx.IsSet(NetworkIdFlag.Name) {
cfg.NetworkId = ctx.Uint64(NetworkIdFlag.Name)
}
@ -1841,7 +1815,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
func MakeBeaconLightConfig(ctx *cli.Context) bparams.ClientConfig {
var config bparams.ClientConfig
customConfig := ctx.IsSet(BeaconConfigFlag.Name)
CheckExclusive(ctx, MainnetFlag, SepoliaFlag, HoleskyFlag, BeaconConfigFlag)
flags.CheckExclusive(ctx, MainnetFlag, SepoliaFlag, HoleskyFlag, BeaconConfigFlag)
switch {
case ctx.Bool(MainnetFlag.Name):
config.ChainConfig = *bparams.MainnetLightConfig
@ -2127,7 +2101,7 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
gspec = MakeGenesis(ctx)
chainDb = MakeChainDatabase(ctx, stack, readonly)
)
config, err := core.LoadChainConfig(chainDb, gspec)
config, _, err := core.LoadChainConfig(chainDb, gspec)
if err != nil {
Fatalf("%v", err)
}

View file

@ -87,11 +87,7 @@ func TestHistoryImportAndExport(t *testing.T) {
}
// Make temp directory for era files.
dir, err := os.MkdirTemp("", "history-export-test")
if err != nil {
t.Fatalf("error creating temp test directory: %v", err)
}
defer os.RemoveAll(dir)
dir := t.TempDir()
// Export history to temp directory.
if err := ExportHistory(chain, dir, 0, count, step); err != nil {

29
cmd/workload/README.md Normal file
View file

@ -0,0 +1,29 @@
## Workload Testing Tool
This tool performs RPC calls against a live node. It has tests for the Sepolia testnet and
Mainnet. Note the tests require a fully synced node.
To run the tests against a Sepolia node, use:
```shell
> ./workload test --sepolia http://host:8545
```
To run a specific test, use the `--run` flag to filter the test cases. Filtering works
similar to the `go test` command. For example, to run only tests for `eth_getBlockByHash`
and `eth_getBlockByNumber`, use this command:
```
> ./workload test --sepolia --run History/getBlockBy http://host:8545
```
### Regenerating tests
There is a facility for updating the tests from the chain. This can also be used to
generate the tests for a new network. As an example, to recreate tests for mainnet, run
the following commands (in this directory) against a synced mainnet node:
```shell
> go run . filtergen --queries queries/filter_queries_mainnet.json http://host:8545
> go run . historygen --history-tests queries/history_mainnet.json http://host:8545
```

219
cmd/workload/filtertest.go Normal file
View file

@ -0,0 +1,219 @@
// Copyright 2025 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"context"
"encoding/json"
"fmt"
"math/big"
"time"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/internal/utesting"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
)
type filterTestSuite struct {
cfg testConfig
queries [][]*filterQuery
}
func newFilterTestSuite(cfg testConfig) *filterTestSuite {
s := &filterTestSuite{cfg: cfg}
if err := s.loadQueries(); err != nil {
exit(err)
}
return s
}
func (s *filterTestSuite) allTests() []utesting.Test {
return []utesting.Test{
{Name: "Filter/ShortRange", Fn: s.filterShortRange},
{Name: "Filter/LongRange", Fn: s.filterLongRange, Slow: true},
{Name: "Filter/FullRange", Fn: s.filterFullRange, Slow: true},
}
}
func (s *filterTestSuite) filterRange(t *utesting.T, test func(query *filterQuery) bool, do func(t *utesting.T, query *filterQuery)) {
var count, total int
for _, bucket := range s.queries {
for _, query := range bucket {
if test(query) {
total++
}
}
}
if total == 0 {
t.Fatalf("No suitable queries available")
}
start := time.Now()
last := start
for _, bucket := range s.queries {
for _, query := range bucket {
if test(query) {
do(t, query)
count++
if time.Since(last) > time.Second*5 {
t.Logf("Making filter query %d/%d (elapsed: %v)", count, total, time.Since(start))
last = time.Now()
}
}
}
}
t.Logf("Made %d filter queries (elapsed: %v)", count, time.Since(start))
}
const filterRangeThreshold = 10000
// filterShortRange runs all short-range filter tests.
func (s *filterTestSuite) filterShortRange(t *utesting.T) {
s.filterRange(t, func(query *filterQuery) bool {
return query.ToBlock+1-query.FromBlock <= filterRangeThreshold
}, s.queryAndCheck)
}
// filterShortRange runs all long-range filter tests.
func (s *filterTestSuite) filterLongRange(t *utesting.T) {
s.filterRange(t, func(query *filterQuery) bool {
return query.ToBlock+1-query.FromBlock > filterRangeThreshold
}, s.queryAndCheck)
}
// filterFullRange runs all filter tests, extending their range from genesis up
// to the latest block. Note that results are only partially verified in this mode.
func (s *filterTestSuite) filterFullRange(t *utesting.T) {
finalized := mustGetFinalizedBlock(s.cfg.client)
s.filterRange(t, func(query *filterQuery) bool {
return query.ToBlock+1-query.FromBlock > finalized/2
}, s.fullRangeQueryAndCheck)
}
func (s *filterTestSuite) queryAndCheck(t *utesting.T, query *filterQuery) {
query.run(s.cfg.client, s.cfg.historyPruneBlock)
if query.Err != nil {
t.Errorf("Filter query failed (fromBlock: %d toBlock: %d addresses: %v topics: %v error: %v)", query.FromBlock, query.ToBlock, query.Address, query.Topics, query.Err)
return
}
if *query.ResultHash != query.calculateHash() {
t.Fatalf("Filter query result mismatch (fromBlock: %d toBlock: %d addresses: %v topics: %v)", query.FromBlock, query.ToBlock, query.Address, query.Topics)
}
}
func (s *filterTestSuite) fullRangeQueryAndCheck(t *utesting.T, query *filterQuery) {
frQuery := &filterQuery{ // create full range query
FromBlock: 0,
ToBlock: int64(rpc.LatestBlockNumber),
Address: query.Address,
Topics: query.Topics,
}
frQuery.run(s.cfg.client, s.cfg.historyPruneBlock)
if frQuery.Err != nil {
t.Errorf("Full range filter query failed (addresses: %v topics: %v error: %v)", frQuery.Address, frQuery.Topics, frQuery.Err)
return
}
// filter out results outside the original query range
j := 0
for _, log := range frQuery.results {
if int64(log.BlockNumber) >= query.FromBlock && int64(log.BlockNumber) <= query.ToBlock {
frQuery.results[j] = log
j++
}
}
frQuery.results = frQuery.results[:j]
if *query.ResultHash != frQuery.calculateHash() {
t.Fatalf("Full range filter query result mismatch (fromBlock: %d toBlock: %d addresses: %v topics: %v)", query.FromBlock, query.ToBlock, query.Address, query.Topics)
}
}
func (s *filterTestSuite) loadQueries() error {
file, err := s.cfg.fsys.Open(s.cfg.filterQueryFile)
if err != nil {
return fmt.Errorf("can't open filterQueryFile: %v", err)
}
defer file.Close()
var queries [][]*filterQuery
if err := json.NewDecoder(file).Decode(&queries); err != nil {
return fmt.Errorf("invalid JSON in %s: %v", s.cfg.filterQueryFile, err)
}
var count int
for _, bucket := range queries {
count += len(bucket)
}
if count == 0 {
return fmt.Errorf("filterQueryFile %s is empty", s.cfg.filterQueryFile)
}
s.queries = queries
return nil
}
// filterQuery is a single query for testing.
type filterQuery struct {
FromBlock int64 `json:"fromBlock"`
ToBlock int64 `json:"toBlock"`
Address []common.Address `json:"address"`
Topics [][]common.Hash `json:"topics"`
ResultHash *common.Hash `json:"resultHash,omitempty"`
results []types.Log
Err error `json:"error,omitempty"`
}
func (fq *filterQuery) isWildcard() bool {
if len(fq.Address) != 0 {
return false
}
for _, topics := range fq.Topics {
if len(topics) != 0 {
return false
}
}
return true
}
func (fq *filterQuery) calculateHash() common.Hash {
enc, err := rlp.EncodeToBytes(&fq.results)
if err != nil {
exit(fmt.Errorf("Error encoding logs: %v", err))
}
return crypto.Keccak256Hash(enc)
}
func (fq *filterQuery) run(client *client, historyPruneBlock *uint64) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
logs, err := client.Eth.FilterLogs(ctx, ethereum.FilterQuery{
FromBlock: big.NewInt(fq.FromBlock),
ToBlock: big.NewInt(fq.ToBlock),
Addresses: fq.Address,
Topics: fq.Topics,
})
if err != nil {
if err = validateHistoryPruneErr(fq.Err, uint64(fq.FromBlock), historyPruneBlock); err == errPrunedHistory {
return
} else if err != nil {
fmt.Printf("Filter query failed: fromBlock: %d toBlock: %d addresses: %v topics: %v error: %v\n",
fq.FromBlock, fq.ToBlock, fq.Address, fq.Topics, err)
}
fq.Err = err
}
fq.results = logs
}

View file

@ -0,0 +1,382 @@
// Copyright 2025 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"context"
"encoding/json"
"fmt"
"math"
"math/big"
"math/rand"
"os"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/rpc"
"github.com/urfave/cli/v2"
)
var (
filterGenerateCommand = &cli.Command{
Name: "filtergen",
Usage: "Generates query set for log filter workload test",
ArgsUsage: "<RPC endpoint URL>",
Action: filterGenCmd,
Flags: []cli.Flag{
filterQueryFileFlag,
filterErrorFileFlag,
},
}
filterQueryFileFlag = &cli.StringFlag{
Name: "queries",
Usage: "JSON file containing filter test queries",
Value: "filter_queries.json",
Category: flags.TestingCategory,
}
filterErrorFileFlag = &cli.StringFlag{
Name: "errors",
Usage: "JSON file containing failed filter queries",
Value: "filter_errors.json",
Category: flags.TestingCategory,
}
)
// filterGenCmd is the main function of the filter tests generator.
func filterGenCmd(ctx *cli.Context) error {
f := newFilterTestGen(ctx)
lastWrite := time.Now()
for {
select {
case <-ctx.Done():
return nil
default:
}
f.updateFinalizedBlock()
query := f.newQuery()
query.run(f.client, nil)
if query.Err != nil {
f.errors = append(f.errors, query)
continue
}
if len(query.results) > 0 && len(query.results) <= maxFilterResultSize {
for {
extQuery := f.extendRange(query)
if extQuery == nil {
break
}
extQuery.run(f.client, nil)
if extQuery.Err == nil && len(extQuery.results) < len(query.results) {
extQuery.Err = fmt.Errorf("invalid result length; old range %d %d; old length %d; new range %d %d; new length %d; address %v; Topics %v",
query.FromBlock, query.ToBlock, len(query.results),
extQuery.FromBlock, extQuery.ToBlock, len(extQuery.results),
extQuery.Address, extQuery.Topics,
)
}
if extQuery.Err != nil {
f.errors = append(f.errors, extQuery)
break
}
if len(extQuery.results) > maxFilterResultSize {
break
}
query = extQuery
}
f.storeQuery(query)
if time.Since(lastWrite) > time.Second*10 {
f.writeQueries()
f.writeErrors()
lastWrite = time.Now()
}
}
}
}
// filterTestGen is the filter query test generator.
type filterTestGen struct {
client *client
queryFile string
errorFile string
finalizedBlock int64
queries [filterBuckets][]*filterQuery
errors []*filterQuery
}
func newFilterTestGen(ctx *cli.Context) *filterTestGen {
return &filterTestGen{
client: makeClient(ctx),
queryFile: ctx.String(filterQueryFileFlag.Name),
errorFile: ctx.String(filterErrorFileFlag.Name),
}
}
func (s *filterTestGen) updateFinalizedBlock() {
s.finalizedBlock = mustGetFinalizedBlock(s.client)
}
const (
// Parameter of the random filter query generator.
maxFilterRange = 10000000
maxFilterResultSize = 300
filterBuckets = 10
maxFilterBucketSize = 100
filterSeedChance = 10
filterMergeChance = 45
)
// storeQuery adds a filter query to the output file.
func (s *filterTestGen) storeQuery(query *filterQuery) {
query.ResultHash = new(common.Hash)
*query.ResultHash = query.calculateHash()
logRatio := math.Log(float64(len(query.results))*float64(s.finalizedBlock)/float64(query.ToBlock+1-query.FromBlock)) / math.Log(float64(s.finalizedBlock)*maxFilterResultSize)
bucket := int(math.Floor(logRatio * filterBuckets))
if bucket >= filterBuckets {
bucket = filterBuckets - 1
}
if len(s.queries[bucket]) < maxFilterBucketSize {
s.queries[bucket] = append(s.queries[bucket], query)
} else {
s.queries[bucket][rand.Intn(len(s.queries[bucket]))] = query
}
fmt.Print("Generated queries per bucket:")
for _, list := range s.queries {
fmt.Print(" ", len(list))
}
fmt.Println()
}
func (s *filterTestGen) extendRange(q *filterQuery) *filterQuery {
rangeLen := q.ToBlock + 1 - q.FromBlock
extLen := rand.Int63n(rangeLen) + 1
if rangeLen+extLen > s.finalizedBlock {
return nil
}
extBefore := min(rand.Int63n(extLen+1), q.FromBlock)
extAfter := extLen - extBefore
if q.ToBlock+extAfter > s.finalizedBlock {
d := q.ToBlock + extAfter - s.finalizedBlock
extAfter -= d
if extBefore+d <= q.FromBlock {
extBefore += d
} else {
extBefore = q.FromBlock
}
}
return &filterQuery{
FromBlock: q.FromBlock - extBefore,
ToBlock: q.ToBlock + extAfter,
Address: q.Address,
Topics: q.Topics,
}
}
// newQuery generates a new filter query.
func (s *filterTestGen) newQuery() *filterQuery {
for {
t := rand.Intn(100)
if t < filterSeedChance {
return s.newSeedQuery()
}
if t < filterSeedChance+filterMergeChance {
if query := s.newMergedQuery(); query != nil {
return query
}
continue
}
if query := s.newNarrowedQuery(); query != nil {
return query
}
}
}
// newSeedQuery creates a query that gets all logs in a random non-finalized block.
func (s *filterTestGen) newSeedQuery() *filterQuery {
block := rand.Int63n(s.finalizedBlock + 1)
return &filterQuery{
FromBlock: block,
ToBlock: block,
}
}
// newMergedQuery creates a new query by combining (with OR) the filter criteria
// of two existing queries (chosen at random).
func (s *filterTestGen) newMergedQuery() *filterQuery {
q1 := s.randomQuery()
q2 := s.randomQuery()
if q1 == nil || q2 == nil || q1 == q2 {
return nil
}
var (
block int64
topicCount int
)
if rand.Intn(2) == 0 {
block = q1.FromBlock + rand.Int63n(q1.ToBlock+1-q1.FromBlock)
topicCount = len(q1.Topics)
} else {
block = q2.FromBlock + rand.Int63n(q2.ToBlock+1-q2.FromBlock)
topicCount = len(q2.Topics)
}
m := &filterQuery{
FromBlock: block,
ToBlock: block,
Topics: make([][]common.Hash, topicCount),
}
for _, addr := range q1.Address {
if rand.Intn(2) == 0 {
m.Address = append(m.Address, addr)
}
}
for _, addr := range q2.Address {
if rand.Intn(2) == 0 {
m.Address = append(m.Address, addr)
}
}
for i := range m.Topics {
if len(q1.Topics) > i {
for _, topic := range q1.Topics[i] {
if rand.Intn(2) == 0 {
m.Topics[i] = append(m.Topics[i], topic)
}
}
}
if len(q2.Topics) > i {
for _, topic := range q2.Topics[i] {
if rand.Intn(2) == 0 {
m.Topics[i] = append(m.Topics[i], topic)
}
}
}
}
return m
}
// newNarrowedQuery creates a new query by 'narrowing' an existing (randomly chosen)
// query. The new query is made more specific by analyzing the filter criteria and adding
// topics/addresses from the known result set.
func (s *filterTestGen) newNarrowedQuery() *filterQuery {
q := s.randomQuery()
if q == nil {
return nil
}
log := q.results[rand.Intn(len(q.results))]
var emptyCount int
if len(q.Address) == 0 {
emptyCount++
}
for i := range log.Topics {
if len(q.Topics) <= i || len(q.Topics[i]) == 0 {
emptyCount++
}
}
if emptyCount == 0 {
return nil
}
query := &filterQuery{
FromBlock: q.FromBlock,
ToBlock: q.ToBlock,
Address: make([]common.Address, len(q.Address)),
Topics: make([][]common.Hash, len(q.Topics)),
}
copy(query.Address, q.Address)
for i, topics := range q.Topics {
if len(topics) > 0 {
query.Topics[i] = make([]common.Hash, len(topics))
copy(query.Topics[i], topics)
}
}
pick := rand.Intn(emptyCount)
if len(query.Address) == 0 {
if pick == 0 {
query.Address = []common.Address{log.Address}
return query
}
pick--
}
for i := range log.Topics {
if len(query.Topics) <= i || len(query.Topics[i]) == 0 {
if pick == 0 {
if len(query.Topics) <= i {
query.Topics = append(query.Topics, make([][]common.Hash, i+1-len(query.Topics))...)
}
query.Topics[i] = []common.Hash{log.Topics[i]}
return query
}
pick--
}
}
panic("unreachable")
}
// randomQuery returns a random query from the ones that were already generated.
func (s *filterTestGen) randomQuery() *filterQuery {
var bucket, bucketCount int
for _, list := range s.queries {
if len(list) > 0 {
bucketCount++
}
}
if bucketCount == 0 {
return nil
}
pick := rand.Intn(bucketCount)
for i, list := range s.queries {
if len(list) > 0 {
if pick == 0 {
bucket = i
break
}
pick--
}
}
return s.queries[bucket][rand.Intn(len(s.queries[bucket]))]
}
// writeQueries serializes the generated queries to the output file.
func (s *filterTestGen) writeQueries() {
file, err := os.Create(s.queryFile)
if err != nil {
exit(fmt.Errorf("Error creating filter test query file %s: %v", s.queryFile, err))
return
}
json.NewEncoder(file).Encode(&s.queries)
file.Close()
}
// writeQueries serializes the generated errors to the error file.
func (s *filterTestGen) writeErrors() {
file, err := os.Create(s.errorFile)
if err != nil {
exit(fmt.Errorf("Error creating filter error file %s: %v", s.errorFile, err))
return
}
defer file.Close()
json.NewEncoder(file).Encode(s.errors)
}
func mustGetFinalizedBlock(client *client) int64 {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
header, err := client.Eth.HeaderByNumber(ctx, big.NewInt(int64(rpc.FinalizedBlockNumber)))
if err != nil {
exit(fmt.Errorf("could not fetch finalized header (error: %v)", err))
}
return header.Number.Int64()
}

View file

@ -0,0 +1,137 @@
// Copyright 2025 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"fmt"
"math/rand"
"slices"
"sort"
"time"
"github.com/urfave/cli/v2"
)
var (
filterPerfCommand = &cli.Command{
Name: "filterperf",
Usage: "Runs log filter performance test against an RPC endpoint",
ArgsUsage: "<RPC endpoint URL>",
Action: filterPerfCmd,
Flags: []cli.Flag{
testSepoliaFlag,
testMainnetFlag,
filterQueryFileFlag,
filterErrorFileFlag,
},
}
)
const passCount = 1
func filterPerfCmd(ctx *cli.Context) error {
cfg := testConfigFromCLI(ctx)
f := newFilterTestSuite(cfg)
type queryTest struct {
query *filterQuery
bucket, index int
runtime []time.Duration
medianTime time.Duration
}
var queries, processed []queryTest
for i, bucket := range f.queries[:] {
for j, query := range bucket {
queries = append(queries, queryTest{query: query, bucket: i, index: j})
}
}
// Run test queries.
var failed, mismatch int
for i := 1; i <= passCount; i++ {
fmt.Println("Performance test pass", i, "/", passCount)
for len(queries) > 0 {
pick := rand.Intn(len(queries))
qt := queries[pick]
queries[pick] = queries[len(queries)-1]
queries = queries[:len(queries)-1]
start := time.Now()
qt.query.run(cfg.client, cfg.historyPruneBlock)
qt.runtime = append(qt.runtime, time.Since(start))
slices.Sort(qt.runtime)
qt.medianTime = qt.runtime[len(qt.runtime)/2]
if qt.query.Err != nil {
failed++
continue
}
if rhash := qt.query.calculateHash(); *qt.query.ResultHash != rhash {
fmt.Printf("Filter query result mismatch: fromBlock: %d toBlock: %d addresses: %v topics: %v expected hash: %064x calculated hash: %064x\n", qt.query.FromBlock, qt.query.ToBlock, qt.query.Address, qt.query.Topics, *qt.query.ResultHash, rhash)
continue
}
processed = append(processed, qt)
if len(processed)%50 == 0 {
fmt.Println(" processed:", len(processed), "remaining", len(queries), "failed:", failed, "result mismatch:", mismatch)
}
}
queries, processed = processed, nil
}
// Show results and stats.
fmt.Println("Performance test finished; processed:", len(queries), "failed:", failed, "result mismatch:", mismatch)
stats := make([]bucketStats, len(f.queries))
var wildcardStats bucketStats
for _, qt := range queries {
bs := &stats[qt.bucket]
if qt.query.isWildcard() {
bs = &wildcardStats
}
bs.blocks += qt.query.ToBlock + 1 - qt.query.FromBlock
bs.count++
bs.logs += len(qt.query.results)
bs.runtime += qt.medianTime
}
fmt.Println()
for i := range stats {
stats[i].print(fmt.Sprintf("bucket #%d", i+1))
}
wildcardStats.print("wild card queries")
fmt.Println()
sort.Slice(queries, func(i, j int) bool {
return queries[i].medianTime > queries[j].medianTime
})
for i := 0; i < 10; i++ {
q := queries[i]
fmt.Printf("Most expensive query #%-2d median runtime: %13v max runtime: %13v result count: %4d fromBlock: %9d toBlock: %9d addresses: %v topics: %v\n",
i+1, q.medianTime, q.runtime[len(q.runtime)-1], len(q.query.results), q.query.FromBlock, q.query.ToBlock, q.query.Address, q.query.Topics)
}
return nil
}
type bucketStats struct {
blocks int64
count, logs int
runtime time.Duration
}
func (st *bucketStats) print(name string) {
if st.count == 0 {
return
}
fmt.Printf("%-20s queries: %4d average block length: %12.2f average log count: %7.2f average runtime: %13v\n",
name, st.count, float64(st.blocks)/float64(st.count), float64(st.logs)/float64(st.count), st.runtime/time.Duration(st.count))
}

333
cmd/workload/historytest.go Normal file
View file

@ -0,0 +1,333 @@
// Copyright 2025 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/internal/utesting"
)
// historyTest is the content of a history test.
type historyTest struct {
BlockNumbers []uint64 `json:"blockNumbers"`
BlockHashes []common.Hash `json:"blockHashes"`
TxCounts []int `json:"txCounts"`
TxHashIndex []int `json:"txHashIndex"`
TxHashes []*common.Hash `json:"txHashes"`
ReceiptsHashes []common.Hash `json:"blockReceiptsHashes"`
}
type historyTestSuite struct {
cfg testConfig
tests historyTest
}
func newHistoryTestSuite(cfg testConfig) *historyTestSuite {
s := &historyTestSuite{cfg: cfg}
if err := s.loadTests(); err != nil {
exit(err)
}
return s
}
func (s *historyTestSuite) loadTests() error {
file, err := s.cfg.fsys.Open(s.cfg.historyTestFile)
if err != nil {
return fmt.Errorf("can't open historyTestFile: %v", err)
}
defer file.Close()
if err := json.NewDecoder(file).Decode(&s.tests); err != nil {
return fmt.Errorf("invalid JSON in %s: %v", s.cfg.historyTestFile, err)
}
if len(s.tests.BlockNumbers) == 0 {
return fmt.Errorf("historyTestFile %s has no test data", s.cfg.historyTestFile)
}
return nil
}
func (s *historyTestSuite) allTests() []utesting.Test {
return []utesting.Test{
{
Name: "History/getBlockByHash",
Fn: s.testGetBlockByHash,
},
{
Name: "History/getBlockByNumber",
Fn: s.testGetBlockByNumber,
},
{
Name: "History/getBlockReceiptsByHash",
Fn: s.testGetBlockReceiptsByHash,
},
{
Name: "History/getBlockReceiptsByNumber",
Fn: s.testGetBlockReceiptsByNumber,
},
{
Name: "History/getBlockTransactionCountByHash",
Fn: s.testGetBlockTransactionCountByHash,
},
{
Name: "History/getBlockTransactionCountByNumber",
Fn: s.testGetBlockTransactionCountByNumber,
},
{
Name: "History/getTransactionByBlockHashAndIndex",
Fn: s.testGetTransactionByBlockHashAndIndex,
},
{
Name: "History/getTransactionByBlockNumberAndIndex",
Fn: s.testGetTransactionByBlockNumberAndIndex,
},
}
}
func (s *historyTestSuite) testGetBlockByHash(t *utesting.T) {
ctx := context.Background()
for i, num := range s.tests.BlockNumbers {
bhash := s.tests.BlockHashes[i]
b, err := s.cfg.client.getBlockByHash(ctx, bhash, false)
if err = validateHistoryPruneErr(err, num, s.cfg.historyPruneBlock); err == errPrunedHistory {
continue
} else if err != nil {
t.Errorf("block %d (hash %v): error %v", num, bhash, err)
continue
}
if b == nil {
t.Errorf("block %d (hash %v): not found", num, bhash)
continue
}
if b.Hash != bhash || uint64(b.Number) != num {
t.Errorf("block %d (hash %v): invalid number/hash", num, bhash)
}
}
}
func (s *historyTestSuite) testGetBlockByNumber(t *utesting.T) {
ctx := context.Background()
for i, num := range s.tests.BlockNumbers {
bhash := s.tests.BlockHashes[i]
b, err := s.cfg.client.getBlockByNumber(ctx, num, false)
if err = validateHistoryPruneErr(err, num, s.cfg.historyPruneBlock); err == errPrunedHistory {
continue
} else if err != nil {
t.Errorf("block %d (hash %v): error %v", num, bhash, err)
continue
}
if b == nil {
t.Errorf("block %d (hash %v): not found", num, bhash)
continue
}
if b.Hash != bhash || uint64(b.Number) != num {
t.Errorf("block %d (hash %v): invalid number/hash", num, bhash)
}
}
}
func (s *historyTestSuite) testGetBlockTransactionCountByHash(t *utesting.T) {
ctx := context.Background()
for i, num := range s.tests.BlockNumbers {
bhash := s.tests.BlockHashes[i]
count, err := s.cfg.client.getBlockTransactionCountByHash(ctx, bhash)
if err = validateHistoryPruneErr(err, num, s.cfg.historyPruneBlock); err == errPrunedHistory {
continue
} else if err != nil {
t.Errorf("block %d (hash %v): error %v", num, bhash, err)
continue
}
expectedCount := uint64(s.tests.TxCounts[i])
if count != expectedCount {
t.Errorf("block %d (hash %v): wrong txcount %d, want %d", count, expectedCount)
}
}
}
func (s *historyTestSuite) testGetBlockTransactionCountByNumber(t *utesting.T) {
ctx := context.Background()
for i, num := range s.tests.BlockNumbers {
bhash := s.tests.BlockHashes[i]
count, err := s.cfg.client.getBlockTransactionCountByNumber(ctx, num)
if err = validateHistoryPruneErr(err, num, s.cfg.historyPruneBlock); err == errPrunedHistory {
continue
} else if err != nil {
t.Errorf("block %d (hash %v): error %v", num, bhash, err)
continue
}
expectedCount := uint64(s.tests.TxCounts[i])
if count != expectedCount {
t.Errorf("block %d (hash %v): wrong txcount %d, want %d", count, expectedCount)
}
}
}
func (s *historyTestSuite) testGetBlockReceiptsByHash(t *utesting.T) {
ctx := context.Background()
for i, num := range s.tests.BlockNumbers {
bhash := s.tests.BlockHashes[i]
receipts, err := s.cfg.client.getBlockReceipts(ctx, bhash)
if err = validateHistoryPruneErr(err, num, s.cfg.historyPruneBlock); err == errPrunedHistory {
continue
} else if err != nil {
t.Errorf("block %d (hash %v): error %v", num, bhash, err)
continue
}
hash := calcReceiptsHash(receipts)
expectedHash := s.tests.ReceiptsHashes[i]
if hash != expectedHash {
t.Errorf("block %d (hash %v): wrong receipts hash %v, want %v", num, bhash, hash, expectedHash)
}
}
}
func (s *historyTestSuite) testGetBlockReceiptsByNumber(t *utesting.T) {
ctx := context.Background()
for i, num := range s.tests.BlockNumbers {
bhash := s.tests.BlockHashes[i]
receipts, err := s.cfg.client.getBlockReceipts(ctx, hexutil.Uint64(num))
if err = validateHistoryPruneErr(err, num, s.cfg.historyPruneBlock); err == errPrunedHistory {
continue
} else if err != nil {
t.Errorf("block %d (hash %v): error %v", num, bhash, err)
continue
}
hash := calcReceiptsHash(receipts)
expectedHash := s.tests.ReceiptsHashes[i]
if hash != expectedHash {
t.Errorf("block %d (hash %v): wrong receipts hash %v, want %v", num, bhash, hash, expectedHash)
}
}
}
func (s *historyTestSuite) testGetTransactionByBlockHashAndIndex(t *utesting.T) {
ctx := context.Background()
for i, num := range s.tests.BlockNumbers {
bhash := s.tests.BlockHashes[i]
txIndex := s.tests.TxHashIndex[i]
expectedHash := s.tests.TxHashes[i]
if expectedHash == nil {
continue // no txs in block
}
tx, err := s.cfg.client.getTransactionByBlockHashAndIndex(ctx, bhash, uint64(txIndex))
if err = validateHistoryPruneErr(err, num, s.cfg.historyPruneBlock); err == errPrunedHistory {
continue
} else if err != nil {
t.Errorf("block %d (hash %v): error %v", num, bhash, err)
continue
}
if tx == nil {
t.Errorf("block %d (hash %v): txIndex %d not found", num, bhash, txIndex)
continue
}
if tx.Hash != *expectedHash || uint64(tx.TransactionIndex) != uint64(txIndex) {
t.Errorf("block %d (hash %v): txIndex %d has wrong txHash/Index", num, bhash, txIndex)
}
}
}
func (s *historyTestSuite) testGetTransactionByBlockNumberAndIndex(t *utesting.T) {
ctx := context.Background()
for i, num := range s.tests.BlockNumbers {
bhash := s.tests.BlockHashes[i]
txIndex := s.tests.TxHashIndex[i]
expectedHash := s.tests.TxHashes[i]
if expectedHash == nil {
continue // no txs in block
}
tx, err := s.cfg.client.getTransactionByBlockNumberAndIndex(ctx, num, uint64(txIndex))
if err = validateHistoryPruneErr(err, num, s.cfg.historyPruneBlock); err == errPrunedHistory {
continue
} else if err != nil {
t.Errorf("block %d (hash %v): error %v", num, bhash, err)
continue
}
if tx == nil {
t.Errorf("block %d (hash %v): txIndex %d not found", num, bhash, txIndex)
continue
}
if tx.Hash != *expectedHash || uint64(tx.TransactionIndex) != uint64(txIndex) {
t.Errorf("block %d (hash %v): txIndex %d has wrong txHash/Index", num, bhash, txIndex)
}
}
}
type simpleBlock struct {
Number hexutil.Uint64 `json:"number"`
Hash common.Hash `json:"hash"`
}
type simpleTransaction struct {
Hash common.Hash `json:"hash"`
TransactionIndex hexutil.Uint64 `json:"transactionIndex"`
}
func (c *client) getBlockByHash(ctx context.Context, arg common.Hash, fullTx bool) (*simpleBlock, error) {
var r *simpleBlock
err := c.RPC.CallContext(ctx, &r, "eth_getBlockByHash", arg, fullTx)
return r, err
}
func (c *client) getBlockByNumber(ctx context.Context, arg uint64, fullTx bool) (*simpleBlock, error) {
var r *simpleBlock
err := c.RPC.CallContext(ctx, &r, "eth_getBlockByNumber", hexutil.Uint64(arg), fullTx)
return r, err
}
func (c *client) getTransactionByBlockHashAndIndex(ctx context.Context, block common.Hash, index uint64) (*simpleTransaction, error) {
var r *simpleTransaction
err := c.RPC.CallContext(ctx, &r, "eth_getTransactionByBlockHashAndIndex", block, hexutil.Uint64(index))
return r, err
}
func (c *client) getTransactionByBlockNumberAndIndex(ctx context.Context, block uint64, index uint64) (*simpleTransaction, error) {
var r *simpleTransaction
err := c.RPC.CallContext(ctx, &r, "eth_getTransactionByBlockNumberAndIndex", hexutil.Uint64(block), hexutil.Uint64(index))
return r, err
}
func (c *client) getBlockTransactionCountByHash(ctx context.Context, block common.Hash) (uint64, error) {
var r hexutil.Uint64
err := c.RPC.CallContext(ctx, &r, "eth_getBlockTransactionCountByHash", block)
return uint64(r), err
}
func (c *client) getBlockTransactionCountByNumber(ctx context.Context, block uint64) (uint64, error) {
var r hexutil.Uint64
err := c.RPC.CallContext(ctx, &r, "eth_getBlockTransactionCountByNumber", hexutil.Uint64(block))
return uint64(r), err
}
func (c *client) getBlockReceipts(ctx context.Context, arg any) ([]*types.Receipt, error) {
var result []*types.Receipt
err := c.RPC.CallContext(ctx, &result, "eth_getBlockReceipts", arg)
return result, err
}

View file

@ -0,0 +1,147 @@
// Copyright 2025 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"context"
"encoding/json"
"fmt"
"math/big"
"os"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/rlp"
"github.com/urfave/cli/v2"
)
var (
historyGenerateCommand = &cli.Command{
Name: "historygen",
Usage: "Generates history retrieval tests",
ArgsUsage: "<RPC endpoint URL>",
Action: generateHistoryTests,
Flags: []cli.Flag{
historyTestFileFlag,
historyTestEarliestFlag,
},
}
historyTestFileFlag = &cli.StringFlag{
Name: "history-tests",
Usage: "JSON file containing filter test queries",
Value: "history_tests.json",
Category: flags.TestingCategory,
}
historyTestEarliestFlag = &cli.IntFlag{
Name: "earliest",
Usage: "JSON file containing filter test queries",
Value: 0,
Category: flags.TestingCategory,
}
)
const historyTestBlockCount = 2000
func generateHistoryTests(clictx *cli.Context) error {
var (
client = makeClient(clictx)
earliest = uint64(clictx.Int(historyTestEarliestFlag.Name))
outputFile = clictx.String(historyTestFileFlag.Name)
ctx = context.Background()
)
test := new(historyTest)
// Create the block numbers. Here we choose 1k blocks between earliest and head.
latest, err := client.Eth.BlockNumber(ctx)
if err != nil {
exit(err)
}
if latest < historyTestBlockCount {
exit(fmt.Errorf("node seems not synced, latest block is %d", latest))
}
test.BlockNumbers = make([]uint64, 0, historyTestBlockCount)
stride := (latest - earliest) / historyTestBlockCount
for b := earliest; b < latest; b += stride {
test.BlockNumbers = append(test.BlockNumbers, b)
}
// Get blocks and assign block info into the test
fmt.Println("Fetching blocks")
blocks := make([]*types.Block, len(test.BlockNumbers))
for i, blocknum := range test.BlockNumbers {
b, err := client.Eth.BlockByNumber(ctx, new(big.Int).SetUint64(blocknum))
if err != nil {
exit(fmt.Errorf("error fetching block %d: %v", blocknum, err))
}
blocks[i] = b
}
test.BlockHashes = make([]common.Hash, len(blocks))
test.TxCounts = make([]int, len(blocks))
for i, block := range blocks {
test.BlockHashes[i] = block.Hash()
test.TxCounts[i] = len(block.Transactions())
}
// Fill tx index.
test.TxHashIndex = make([]int, len(blocks))
test.TxHashes = make([]*common.Hash, len(blocks))
for i, block := range blocks {
txs := block.Transactions()
if len(txs) == 0 {
continue
}
index := len(txs) / 2
txhash := txs[index].Hash()
test.TxHashIndex[i] = index
test.TxHashes[i] = &txhash
}
// Get receipts.
fmt.Println("Fetching receipts")
test.ReceiptsHashes = make([]common.Hash, len(blocks))
for i, blockHash := range test.BlockHashes {
receipts, err := client.getBlockReceipts(ctx, blockHash)
if err != nil {
exit(fmt.Errorf("error fetching block %v receipts: %v", blockHash, err))
}
test.ReceiptsHashes[i] = calcReceiptsHash(receipts)
}
// Write output file.
writeJSON(outputFile, test)
return nil
}
func calcReceiptsHash(rcpt []*types.Receipt) common.Hash {
h := crypto.NewKeccakState()
rlp.Encode(h, rcpt)
return common.Hash(h.Sum(nil))
}
func writeJSON(fileName string, value any) {
file, err := os.Create(fileName)
if err != nil {
exit(fmt.Errorf("Error creating %s: %v", fileName, err))
return
}
defer file.Close()
json.NewEncoder(file).Encode(value)
}

86
cmd/workload/main.go Normal file
View file

@ -0,0 +1,86 @@
// Copyright 2025 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"fmt"
"os"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/internal/debug"
"github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/rpc"
"github.com/urfave/cli/v2"
)
var app = flags.NewApp("go-ethereum workload test tool")
func init() {
app.Flags = append(app.Flags, debug.Flags...)
app.Before = func(ctx *cli.Context) error {
flags.MigrateGlobalFlags(ctx)
return debug.Setup(ctx)
}
app.After = func(ctx *cli.Context) error {
debug.Exit()
return nil
}
app.CommandNotFound = func(ctx *cli.Context, cmd string) {
fmt.Fprintf(os.Stderr, "No such command: %s\n", cmd)
os.Exit(1)
}
// Add subcommands.
app.Commands = []*cli.Command{
runTestCommand,
historyGenerateCommand,
filterGenerateCommand,
filterPerfCommand,
}
}
func main() {
exit(app.Run(os.Args))
}
type client struct {
Eth *ethclient.Client
RPC *rpc.Client
}
func makeClient(ctx *cli.Context) *client {
if ctx.NArg() < 1 {
exit("missing RPC endpoint URL as command-line argument")
}
url := ctx.Args().First()
cl, err := rpc.Dial(url)
if err != nil {
exit(fmt.Errorf("Could not create RPC client at %s: %v", url, err))
}
return &client{
RPC: cl,
Eth: ethclient.NewClient(cl),
}
}
func exit(err any) {
if err == nil {
os.Exit(0)
}
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

174
cmd/workload/testsuite.go Normal file
View file

@ -0,0 +1,174 @@
// Copyright 2020 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"embed"
"fmt"
"io/fs"
"os"
"slices"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/internal/utesting"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/urfave/cli/v2"
)
//go:embed queries
var builtinTestFiles embed.FS
var (
runTestCommand = &cli.Command{
Name: "test",
Usage: "Runs workload tests against an RPC endpoint",
ArgsUsage: "<RPC endpoint URL>",
Action: runTestCmd,
Flags: []cli.Flag{
testPatternFlag,
testTAPFlag,
testSlowFlag,
testSepoliaFlag,
testMainnetFlag,
filterQueryFileFlag,
historyTestFileFlag,
},
}
testPatternFlag = &cli.StringFlag{
Name: "run",
Usage: "Pattern of test suite(s) to run",
Category: flags.TestingCategory,
}
testTAPFlag = &cli.BoolFlag{
Name: "tap",
Usage: "Output test results in TAP format",
Category: flags.TestingCategory,
}
testSlowFlag = &cli.BoolFlag{
Name: "slow",
Usage: "Enable slow tests",
Value: false,
Category: flags.TestingCategory,
}
testSepoliaFlag = &cli.BoolFlag{
Name: "sepolia",
Usage: "Use test cases for sepolia network",
Category: flags.TestingCategory,
}
testMainnetFlag = &cli.BoolFlag{
Name: "mainnet",
Usage: "Use test cases for mainnet network",
Category: flags.TestingCategory,
}
)
// testConfig holds the parameters for testing.
type testConfig struct {
client *client
fsys fs.FS
filterQueryFile string
historyTestFile string
historyPruneBlock *uint64
}
var errPrunedHistory = fmt.Errorf("attempt to access pruned history")
// validateHistoryPruneErr checks whether the given error is caused by access
// to history before the pruning threshold block (it is an rpc.Error with code 4444).
// In this case, errPrunedHistory is returned.
// If the error is a pruned history error that occurs when accessing a block past the
// historyPrune block, an error is returned.
// Otherwise, the original value of err is returned.
func validateHistoryPruneErr(err error, blockNum uint64, historyPruneBlock *uint64) error {
if err != nil {
if rpcErr, ok := err.(rpc.Error); ok && rpcErr.ErrorCode() == 4444 {
if historyPruneBlock != nil && blockNum > *historyPruneBlock {
return fmt.Errorf("pruned history error returned after pruning threshold")
}
return errPrunedHistory
}
}
return err
}
func testConfigFromCLI(ctx *cli.Context) (cfg testConfig) {
flags.CheckExclusive(ctx, testMainnetFlag, testSepoliaFlag)
if (ctx.IsSet(testMainnetFlag.Name) || ctx.IsSet(testSepoliaFlag.Name)) && ctx.IsSet(filterQueryFileFlag.Name) {
exit(filterQueryFileFlag.Name + " cannot be used with " + testMainnetFlag.Name + " or " + testSepoliaFlag.Name)
}
// configure ethclient
cfg.client = makeClient(ctx)
// configure test files
switch {
case ctx.Bool(testMainnetFlag.Name):
cfg.fsys = builtinTestFiles
cfg.filterQueryFile = "queries/filter_queries_mainnet.json"
cfg.historyTestFile = "queries/history_mainnet.json"
cfg.historyPruneBlock = new(uint64)
*cfg.historyPruneBlock = ethconfig.HistoryPrunePoints[params.MainnetGenesisHash].BlockNumber
case ctx.Bool(testSepoliaFlag.Name):
cfg.fsys = builtinTestFiles
cfg.filterQueryFile = "queries/filter_queries_sepolia.json"
cfg.historyTestFile = "queries/history_sepolia.json"
cfg.historyPruneBlock = new(uint64)
*cfg.historyPruneBlock = ethconfig.HistoryPrunePoints[params.SepoliaGenesisHash].BlockNumber
default:
cfg.fsys = os.DirFS(".")
cfg.filterQueryFile = ctx.String(filterQueryFileFlag.Name)
cfg.historyTestFile = ctx.String(historyTestFileFlag.Name)
}
return cfg
}
func runTestCmd(ctx *cli.Context) error {
cfg := testConfigFromCLI(ctx)
filterSuite := newFilterTestSuite(cfg)
historySuite := newHistoryTestSuite(cfg)
// Filter test cases.
tests := filterSuite.allTests()
tests = append(tests, historySuite.allTests()...)
if ctx.IsSet(testPatternFlag.Name) {
tests = utesting.MatchTests(tests, ctx.String(testPatternFlag.Name))
}
if !ctx.Bool(testSlowFlag.Name) {
tests = slices.DeleteFunc(tests, func(test utesting.Test) bool {
return test.Slow
})
}
// Disable logging unless explicitly enabled.
if !ctx.IsSet("verbosity") && !ctx.IsSet("vmodule") {
log.SetDefault(log.NewLogger(log.DiscardHandler()))
}
// Run the tests.
var run = utesting.RunTests
if ctx.Bool(testTAPFlag.Name) {
run = utesting.RunTAP
}
results := run(tests, os.Stdout)
if utesting.CountFailures(results) > 0 {
os.Exit(1)
}
return nil
}

View file

@ -154,6 +154,10 @@ type CacheConfig struct {
SnapshotNoBuild bool // Whether the background generation is allowed
SnapshotWait bool // Wait for snapshot construction on startup. TODO(karalabe): This is a dirty hack for testing, nuke it
// This defines the cutoff block for history expiry.
// Blocks before this number may be unavailable in the chain database.
HistoryPruningCutoff uint64
}
// triedbConfig derives the configures for trie database.
@ -237,6 +241,7 @@ type BlockChain struct {
chainHeadFeed event.Feed
logsFeed event.Feed
blockProcFeed event.Feed
blockProcCounter int32
scope event.SubscriptionScope
genesisBlock *types.Block
@ -1570,8 +1575,6 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) {
if len(chain) == 0 {
return 0, nil
}
bc.blockProcFeed.Send(true)
defer bc.blockProcFeed.Send(false)
// Do a sanity check that the provided chain is actually ordered and linked.
for i := 1; i < len(chain); i++ {
@ -1611,6 +1614,16 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
if bc.insertStopped() {
return nil, 0, nil
}
if atomic.AddInt32(&bc.blockProcCounter, 1) == 1 {
bc.blockProcFeed.Send(true)
}
defer func() {
if atomic.AddInt32(&bc.blockProcCounter, -1) == 0 {
bc.blockProcFeed.Send(false)
}
}()
// Start a parallel signature recovery (signer will fluke on fork transition, minimal perf loss)
SenderCacher().RecoverFromBlocks(types.MakeSigner(bc.chainConfig, chain[0].Number(), chain[0].Time()), chain)
@ -2519,3 +2532,9 @@ func (bc *BlockChain) SetTrieFlushInterval(interval time.Duration) {
func (bc *BlockChain) GetTrieFlushInterval() time.Duration {
return time.Duration(bc.flushInterval.Load())
}
// HistoryPruningCutoff returns the configured history pruning point.
// Blocks before this might not be available in the database.
func (bc *BlockChain) HistoryPruningCutoff() uint64 {
return bc.cacheConfig.HistoryPruningCutoff
}

View file

@ -0,0 +1,156 @@
// 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 <http://www.gnu.org/licenses/>.
package filtermaps
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)
// blockchain represents the underlying blockchain of ChainView.
type blockchain interface {
GetHeader(hash common.Hash, number uint64) *types.Header
GetCanonicalHash(number uint64) common.Hash
GetReceiptsByHash(hash common.Hash) types.Receipts
}
// ChainView represents an immutable view of a chain with a block id and a set
// of receipts associated to each block number and a block hash associated with
// all block numbers except the head block. This is because in the future
// ChainView might represent a view where the head block is currently being
// created. Block id is a unique identifier that can also be calculated for the
// head block.
// Note that the view's head does not have to be the current canonical head
// of the underlying blockchain, it should only possess the block headers
// and receipts up until the expected chain view head.
type ChainView struct {
chain blockchain
headNumber uint64
hashes []common.Hash // block hashes starting backwards from headNumber until first canonical hash
}
// NewChainView creates a new ChainView.
func NewChainView(chain blockchain, number uint64, hash common.Hash) *ChainView {
cv := &ChainView{
chain: chain,
headNumber: number,
hashes: []common.Hash{hash},
}
cv.extendNonCanonical()
return cv
}
// getBlockHash returns the block hash belonging to the given block number.
// Note that the hash of the head block is not returned because ChainView might
// represent a view where the head block is currently being created.
func (cv *ChainView) getBlockHash(number uint64) common.Hash {
if number >= cv.headNumber {
panic("invalid block number")
}
return cv.blockHash(number)
}
// getBlockId returns the unique block id belonging to the given block number.
// Note that it is currently equal to the block hash. In the future it might
// be a different id for future blocks if the log index root becomes part of
// consensus and therefore rendering the index with the new head will happen
// before the hash of that new head is available.
func (cv *ChainView) getBlockId(number uint64) common.Hash {
if number > cv.headNumber {
panic("invalid block number")
}
return cv.blockHash(number)
}
// getReceipts returns the set of receipts belonging to the block at the given
// block number.
func (cv *ChainView) getReceipts(number uint64) types.Receipts {
if number > cv.headNumber {
panic("invalid block number")
}
return cv.chain.GetReceiptsByHash(cv.blockHash(number))
}
// limitedView returns a new chain view that is a truncated version of the parent view.
func (cv *ChainView) limitedView(newHead uint64) *ChainView {
if newHead >= cv.headNumber {
return cv
}
return NewChainView(cv.chain, newHead, cv.blockHash(newHead))
}
// equalViews returns true if the two chain views are equivalent.
func equalViews(cv1, cv2 *ChainView) bool {
if cv1 == nil || cv2 == nil {
return false
}
return cv1.headNumber == cv2.headNumber && cv1.getBlockId(cv1.headNumber) == cv2.getBlockId(cv2.headNumber)
}
// matchViews returns true if the two chain views are equivalent up until the
// specified block number. If the specified number is higher than one of the
// heads then false is returned.
func matchViews(cv1, cv2 *ChainView, number uint64) bool {
if cv1 == nil || cv2 == nil {
return false
}
if cv1.headNumber < number || cv2.headNumber < number {
return false
}
if number == cv1.headNumber || number == cv2.headNumber {
return cv1.getBlockId(number) == cv2.getBlockId(number)
}
return cv1.getBlockHash(number) == cv2.getBlockHash(number)
}
// extendNonCanonical checks whether the previously known reverse list of head
// hashes still ends with one that is canonical on the underlying blockchain.
// If necessary then it traverses further back on the header chain and adds
// more hashes to the list.
func (cv *ChainView) extendNonCanonical() bool {
for {
hash, number := cv.hashes[len(cv.hashes)-1], cv.headNumber-uint64(len(cv.hashes)-1)
if cv.chain.GetCanonicalHash(number) == hash {
return true
}
if number == 0 {
log.Error("Unknown genesis block hash found")
return false
}
header := cv.chain.GetHeader(hash, number)
if header == nil {
log.Error("Header not found", "number", number, "hash", hash)
return false
}
cv.hashes = append(cv.hashes, header.ParentHash)
}
}
// blockHash returns the given block hash without doing the head number check.
func (cv *ChainView) blockHash(number uint64) common.Hash {
if number+uint64(len(cv.hashes)) <= cv.headNumber {
hash := cv.chain.GetCanonicalHash(number)
if !cv.extendNonCanonical() {
return common.Hash{}
}
if number+uint64(len(cv.hashes)) <= cv.headNumber {
return hash
}
}
return cv.hashes[cv.headNumber-number]
}

View file

@ -0,0 +1,63 @@
// 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 <http://www.gnu.org/licenses/>.
package filtermaps
import (
_ "embed"
"encoding/json"
"github.com/ethereum/go-ethereum/common"
)
// checkpointList lists checkpoints for finalized epochs of a given chain.
// This allows the indexer to start indexing from the latest available
// checkpoint and then index tail epochs in reverse order.
type checkpointList []epochCheckpoint
// epochCheckpoint specified the last block of the epoch and the first log
// value index where that block starts. This allows a log value iterator to
// be initialized at the epoch boundary.
type epochCheckpoint struct {
BlockNumber uint64 // block that generated the last log value of the given epoch
BlockId common.Hash
FirstIndex uint64 // first log value index of the given block
}
//go:embed checkpoints_mainnet.json
var checkpointsMainnetJSON []byte
//go:embed checkpoints_sepolia.json
var checkpointsSepoliaJSON []byte
//go:embed checkpoints_holesky.json
var checkpointsHoleskyJSON []byte
// checkpoints lists sets of checkpoints for multiple chains. The matching
// checkpoint set is autodetected by the indexer once the canonical chain is
// known.
var checkpoints = []checkpointList{
decodeCheckpoints(checkpointsMainnetJSON),
decodeCheckpoints(checkpointsSepoliaJSON),
decodeCheckpoints(checkpointsHoleskyJSON),
}
func decodeCheckpoints(encoded []byte) (result checkpointList) {
if err := json.Unmarshal(encoded, &result); err != nil {
panic(err)
}
return
}

View file

@ -0,0 +1,20 @@
[
{"blockNumber": 814411, "blockId": "0xf763e96fc3920359c5f706803024b78e83796a3a8563bb5a83c3ddd7cbfde287", "firstIndex": 67107637},
{"blockNumber": 914278, "blockId": "0x0678cf8d53c0d6d27896df657d98cc73bc63ca468b6295068003938ef9b0f927", "firstIndex": 134217671},
{"blockNumber": 1048874, "blockId": "0x3620c3d52a40ff4d9fc58c3104cfa2f327f55592caf6a2394c207a5e00b4f740", "firstIndex": 201326382},
{"blockNumber": 1144441, "blockId": "0x438fb42850f5a0d8e1666de598a4d0106b62da0f7448c62fe029b8cbad35d08d", "firstIndex": 268435440},
{"blockNumber": 1230411, "blockId": "0xf0ee07e60a93910723b259473a253dd9cf674e8b78c4f153b32ad7032efffeeb", "firstIndex": 335543079},
{"blockNumber": 1309112, "blockId": "0xc1646e5ef4b4343880a85b1a4111e3321d609a1225e9cebbe10d1c7abf99e58d", "firstIndex": 402653100},
{"blockNumber": 1380522, "blockId": "0x1617cae91989d97ac6335c4217aa6cc7f7f4c2837e20b3b5211d98d6f9e97e44", "firstIndex": 469761917},
{"blockNumber": 1476962, "blockId": "0xd978455d2618d093dfc685d7f43f61be6dae0fa8a9cb915ae459aa6e0a5525f0", "firstIndex": 536870773},
{"blockNumber": 1533518, "blockId": "0xe7d39d71bd9d5f1f3157c35e0329531a7950a19e3042407e38948b89b5384f78", "firstIndex": 603979664},
{"blockNumber": 1613787, "blockId": "0xa793168d135c075732a618ec367faaed5f359ffa81898c73cb4ec54ec2caa696", "firstIndex": 671088003},
{"blockNumber": 1719099, "blockId": "0xc4394c71a8a24efe64c5ff2afcdd1594f3708524e6084aa7dadd862bd704ab03", "firstIndex": 738196914},
{"blockNumber": 1973165, "blockId": "0xee3a9e959a437c707a3036736ec8d42a9261ac6100972c26f65eedcde315a81d", "firstIndex": 805306333},
{"blockNumber": 2274844, "blockId": "0x76e2d33653ed9282c63ad09d721e1f2e29064aa9c26202e20fc4cc73e8dfe5f6", "firstIndex": 872415141},
{"blockNumber": 2530503, "blockId": "0x59f4e45345f8b8f848be5004fe75c4a28f651864256c3aa9b2da63369432b718", "firstIndex": 939523693},
{"blockNumber": 2781903, "blockId": "0xc981e91c6fb69c5e8146ead738fcfc561831f11d7786d39c7fa533966fc37675", "firstIndex": 1006632906},
{"blockNumber": 3101713, "blockId": "0xc7baa577c91d8439e3fc79002d2113d07ca54a4724bf2f1f5af937b7ba8e1f32", "firstIndex": 1073741382},
{"blockNumber": 3221770, "blockId": "0xa6b8240b7883fcc71aa5001b5ba66c889975c5217e14c16edebdd6f6e23a9424", "firstIndex": 1140850360}
]

View file

@ -0,0 +1,256 @@
[
{"blockNumber": 4166218, "blockId": "0xdd767e0426256179125551e8e40f33565a96d1c94076c7746fa79d767ed4ad65", "firstIndex": 67108680},
{"blockNumber": 4514014, "blockId": "0x33a0879bdabea4a7a3f2b424388cbcbf2fbd519bddadf13752a259049c78e95d", "firstIndex": 134217343},
{"blockNumber": 4817415, "blockId": "0x4f0e8c7dd04fbe0985b9394575b19f13ea66a2a628fa5b08178ce4b138c6db80", "firstIndex": 201326352},
{"blockNumber": 5087733, "blockId": "0xc84cd5e9cda999c919803c7a53a23bb77a18827fbde401d3463f1e9e52536424", "firstIndex": 268435343},
{"blockNumber": 5306107, "blockId": "0x13f028b5fc055d23f55a92a2eeecfbcfbda8a08e4cd519ce451ba2e70428f5f9", "firstIndex": 335544094},
{"blockNumber": 5509918, "blockId": "0x1ed770a58a7b4d4a828b7bb44c8820a674d562b23a6a0139981abe4c489d4dad", "firstIndex": 402652853},
{"blockNumber": 5670390, "blockId": "0x3923ee6a62e6cc5132afdadf1851ae4e73148e6fbe0a8319cafd2a120c98efa3", "firstIndex": 469761897},
{"blockNumber": 5826139, "blockId": "0xe61bc6ef03c333805f26319e1688f82553f98aa5e902b200e0621a3371b69050", "firstIndex": 536870853},
{"blockNumber": 5953029, "blockId": "0x43d710b1b7243b848400975048ccefdfaba091c692c7f01c619d988886cc160f", "firstIndex": 603979580},
{"blockNumber": 6102846, "blockId": "0xa100b2018f6545cc689656b4b846677b138955b7efd30e850cd14c246430ba18", "firstIndex": 671088291},
{"blockNumber": 6276718, "blockId": "0xb832ac448b06c104ba50faefd58b0b94d53c0fba5cb268086adad4db99c2f35f", "firstIndex": 738197399},
{"blockNumber": 6448696, "blockId": "0x48e8ae6f729ad6c76b6cf632bd52a6df7886ed55be09d43c5004fcc1463e533b", "firstIndex": 805305988},
{"blockNumber": 6655974, "blockId": "0xac395971a6ffc30f807848f68b97b2834f8ea13478a7615860b6a69e3d0823ca", "firstIndex": 872415033},
{"blockNumber": 6873949, "blockId": "0xc522ddb1113b1e9a87b2bdcb11ce78756beba6454a890122f121a032b5769354", "firstIndex": 939523784},
{"blockNumber": 7080953, "blockId": "0x3606de577d80120d1edbb64bad7fa6795e788bae342866a98cc58ce2f7575045", "firstIndex": 1006632796},
{"blockNumber": 7267002, "blockId": "0xad770882a69d216e955e34fef84851e56c0de82deacd6187a7a41f6170cd6c6d", "firstIndex": 1073741045},
{"blockNumber": 7466708, "blockId": "0x17a48817b3a65aba333a5b56f3ff2e86fbcc19e184b046a5305a5182fdd8eb8a", "firstIndex": 1140850680},
{"blockNumber": 7661807, "blockId": "0xa74731ee775fbd3f4d9313c68562737dd7c8d2c9eb968791d8abe167e16ddc96", "firstIndex": 1207959112},
{"blockNumber": 7834556, "blockId": "0xe4b4812448075508cb05a0e3257f91b49509dc78cd963676a633864db6e78956", "firstIndex": 1275068095},
{"blockNumber": 7990068, "blockId": "0x07bd4ca38abb4584a6209e04035646aa545ebbb6c948d438d4c25bfd9cb205fa", "firstIndex": 1342176620},
{"blockNumber": 8143032, "blockId": "0x0e3149e9637290b044ee693b8fcb66e23d22db3ad0bdda32962138ba18e59f3f", "firstIndex": 1409285949},
{"blockNumber": 8297660, "blockId": "0x34cd24f80247f7dfaf316b2e637f4b62f72ecc90703014fb25cb98ad044fc2c0", "firstIndex": 1476394911},
{"blockNumber": 8465137, "blockId": "0x4452fa296498248d7f10c9dc6ec1e4ae7503aa07f491e6d38b21aea5d2c658a8", "firstIndex": 1543503744},
{"blockNumber": 8655820, "blockId": "0x7bdb9008b30be420f7152cc294ac6e5328eed5b4abd954a34105de3da24f3cc6", "firstIndex": 1610612619},
{"blockNumber": 8807187, "blockId": "0xde03e3bfddc722c019f0b59bc55efabcd5ab68c6711f4c08d0390a56f396590d", "firstIndex": 1677721589},
{"blockNumber": 8911171, "blockId": "0xe44f342de74ab05a2a994f8841bdf88f720b9dc260177ba4030d0f7077901324", "firstIndex": 1744830310},
{"blockNumber": 8960320, "blockId": "0x79764f9ff6e0fe4848eda1805687872021076e4e603112861af84181395ac559", "firstIndex": 1811938893},
{"blockNumber": 9085994, "blockId": "0x24a101d1c8a63367a0953d10dc79c3b587a93bd7fd382084708adefce0b8363f", "firstIndex": 1879047965},
{"blockNumber": 9230924, "blockId": "0xb176a98d3acd855cbb75265fb6be955a8d51abc771e021e13275d5b3ecb07eeb", "firstIndex": 1946156668},
{"blockNumber": 9390535, "blockId": "0x640f5e2d511a5141878d57ae7a619f19b72a2bd3ef019cf0a22d74d93d9acf07", "firstIndex": 2013265733},
{"blockNumber": 9515674, "blockId": "0xff4a7b6b21aeaeb6e1a75ecd22b1f34c058a0ce1477ce90a8ce78165fd1d0941", "firstIndex": 2080374553},
{"blockNumber": 9659426, "blockId": "0xc351455249343b41e9171e183612b68c3c895271c62bd2c53d9e3ab1aa865aa1", "firstIndex": 2147483567},
{"blockNumber": 9794018, "blockId": "0xde98035b4b7f9449c256239b65c7ff2c0330de44dee190106d0a96fb6f683238", "firstIndex": 2214592213},
{"blockNumber": 9923840, "blockId": "0x881da313a1e2b6fab58a1d6fa65b5dacfdc9d68a3112a647104955b5233f84e3", "firstIndex": 2281701302},
{"blockNumber": 10042435, "blockId": "0x451f6459640a6f54e2a535cc3a49cfc469861da3ddc101840ab3aef9e17fa424", "firstIndex": 2348810174},
{"blockNumber": 10168883, "blockId": "0x5d16ff5adf0df1e4dc810da60af37399ef733be7870f21112b8c2cfff4995dd9", "firstIndex": 2415918783},
{"blockNumber": 10289554, "blockId": "0x85d5690f15a787c43b9a49e8dd6e324f0b3e0c9796d07c0cfb128e5c168f5488", "firstIndex": 2483027930},
{"blockNumber": 10386676, "blockId": "0x20f675ea72db448024a8a0b8e3ec180cac37a5910575bc32f8d9f5cdfe3c2649", "firstIndex": 2550136212},
{"blockNumber": 10479675, "blockId": "0x014abb07acf2330cc78800ca1f564928f2daccca4b389bf5c59f4b840d843ec0", "firstIndex": 2617245218},
{"blockNumber": 10562661, "blockId": "0xd437607a3f81ce8b7c605e167ce5e52bf8a3e02cdc646997bd0ccc57a50ad7d1", "firstIndex": 2684354520},
{"blockNumber": 10641508, "blockId": "0x2e8ab6470c29f90ac23dcfc58310f0208f5d0e752a0c7982a77a223eca104082", "firstIndex": 2751462730},
{"blockNumber": 10717156, "blockId": "0x8820447b6429dd12be603c1c130be532e9db065bb4bc6b2a9d4551794d63789a", "firstIndex": 2818571831},
{"blockNumber": 10784549, "blockId": "0xc557daab80a7cdc963d62aa881faf3ab1baceff8e027046bcd203e432e0983b3", "firstIndex": 2885680800},
{"blockNumber": 10848651, "blockId": "0xede1b0de5db6685a6f589096ceb8fccb08d3ff60e8b00a93caa4a775b48e07fc", "firstIndex": 2952789740},
{"blockNumber": 10909166, "blockId": "0x989db675899d13323006a4d6174557e3c5501c672afd60d8bd902fc98d37e92e", "firstIndex": 3019897599},
{"blockNumber": 10972902, "blockId": "0x5484050cc2c7d774bc5cd6af1c2ef8c19d1de12dabe25867c9b365924ea10434", "firstIndex": 3087007422},
{"blockNumber": 11036597, "blockId": "0x1e3686e19056587c385262d5b0a07b3ec04e804c2d59e9aaca1e5876e78f69ae", "firstIndex": 3154116231},
{"blockNumber": 11102520, "blockId": "0x339cf302fe813cce3bb9318b860dfa8be7f688413f38a6ea1987a1b84d742b4b", "firstIndex": 3221224863},
{"blockNumber": 11168162, "blockId": "0xc0fa21ea090627610bcac4732dff702633f310cabafc42bc500d3d4805198fe0", "firstIndex": 3288334273},
{"blockNumber": 11233707, "blockId": "0x491c37a479b8cf22eaa3654ae34c5ddc4627df8c58ca8a6979159e1710428576", "firstIndex": 3355442691},
{"blockNumber": 11300526, "blockId": "0xb7366d2a24df99002cffe0c9a00959c93ef0dcfc3fd17389e2020bf5caa788eb", "firstIndex": 3422551480},
{"blockNumber": 11367621, "blockId": "0xce53df5080c5b5238bb7717dfbfd88c2f574cfbb3d91f92b57171a00e9776cd2", "firstIndex": 3489660710},
{"blockNumber": 11431881, "blockId": "0x2a08ff9c4f6fd152166213d902f0870822429f01d5f90e384ac54a3eac0ceb3a", "firstIndex": 3556768626},
{"blockNumber": 11497107, "blockId": "0x1f99c6b65f2b1cb06ed1786c6a0274ff1b9eacab6cb729fcd386f10ebbd88123", "firstIndex": 3623878389},
{"blockNumber": 11560104, "blockId": "0xebe6924817bbdfe52af49667da1376bae5a2994b375d4b996e8ff2683744e37a", "firstIndex": 3690986640},
{"blockNumber": 11625129, "blockId": "0xbe6eee325329ee2fe632d8576864c29dd1c79bab891dc0a22d5b2ac87618d26e", "firstIndex": 3758095773},
{"blockNumber": 11690397, "blockId": "0xc28bf55f858ddf5b82d1ceb3b5258b90a9ca34df8863a1c652c4d359f5748fdf", "firstIndex": 3825204492},
{"blockNumber": 11755087, "blockId": "0x0c10cde6ce1bbe24dc57347fe4aaebc17b7d8e8d7d97e3db573133477f494740", "firstIndex": 3892314051},
{"blockNumber": 11819674, "blockId": "0x36b694a1776c94e4c6ae4a410931b2086de47a83e437517040e3290ce9afff67", "firstIndex": 3959422445},
{"blockNumber": 11883358, "blockId": "0x21f447aca9ddf94ed71df9fa3648a12acc2ba603f89f24c4784936864c41945f", "firstIndex": 4026531743},
{"blockNumber": 11948524, "blockId": "0x71a52b6cce80d3a552b0daa18beb952facf81a89bc7ca769d08ac297f317507a", "firstIndex": 4093640009},
{"blockNumber": 12013168, "blockId": "0x9a7fb369b8d8cd0edd0d890d636096f20c63abb7eb5798ad1e578cac599e3db8", "firstIndex": 4160748475},
{"blockNumber": 12078711, "blockId": "0x5de09329413b0c2f58d926f225197552a335ba3d5544d7bdb45e7574f78c9b8d", "firstIndex": 4227858275},
{"blockNumber": 12143640, "blockId": "0xbeafc0e1e0586f5a95f00f2a796d7df122c79c187aa2d917129297f24b8306bd", "firstIndex": 4294967145},
{"blockNumber": 12208005, "blockId": "0x052487095cdd4a604808e6c14e30fb68b3fa546d35585b315f287219d38ef77c", "firstIndex": 4362075289},
{"blockNumber": 12272465, "blockId": "0x82c8a50413bd67a0d6f53b085adcd9ae8c25ecc07ed766fa80297a8dcae63b29", "firstIndex": 4429184610},
{"blockNumber": 12329418, "blockId": "0x294c147e48d32c217ff3f27a3c8c989f15eee57a911408ec4c28d4f13a36bb3b", "firstIndex": 4496292968},
{"blockNumber": 12382388, "blockId": "0x8c2555965ff735690d2d94ececc48df4700e079c7b21b8e601a30d4e99bc4b5b", "firstIndex": 4563401809},
{"blockNumber": 12437052, "blockId": "0x2e38362031f36a0f3394da619dcc03be03c19700594cbd1df84c2c476a87de63", "firstIndex": 4630511012},
{"blockNumber": 12490026, "blockId": "0x122749c02a55c9c2a1e69068f54b6c1d25419eb743e3553aba91acf1daeadc35", "firstIndex": 4697619920},
{"blockNumber": 12541747, "blockId": "0xfb9f12aa2902da798ac05fab425434f8c7ce98050d67d416dbb32f98c21f66f7", "firstIndex": 4764728267},
{"blockNumber": 12597413, "blockId": "0x9a7a399c2904ac8d0fec580550525e7e1a73d8f65f739bf7c05d86e389d0d3f7", "firstIndex": 4831837757},
{"blockNumber": 12651950, "blockId": "0xb78dcb572cdafb9c4e2f3863ef518a3b2df0cd4f76faa26a423b2ca0c1cde734", "firstIndex": 4898946491},
{"blockNumber": 12706472, "blockId": "0xfd21f41ec6b0c39287d7d48c134d1212a261c53d65db99739994b003150bbad1", "firstIndex": 4966054796},
{"blockNumber": 12762929, "blockId": "0xc94d994bc40b2ae7dc23cf2b92cc01e84915f090bb57c0d9a67584bd564d3916", "firstIndex": 5033164307},
{"blockNumber": 12816689, "blockId": "0x7770c72f22cbf6ccf7ab85d203088f7ede89632cf0042c690102f926a90bd09d", "firstIndex": 5100273412},
{"blockNumber": 12872408, "blockId": "0x2e008b8c952d828875d777f7912f472af96ffc977f2ceae884006682cab6b8ed", "firstIndex": 5167381625},
{"blockNumber": 12929718, "blockId": "0x85eb0ed3c5910c6a01b65ef0a5b76c59c2cdb5094e6e27eb87c751d77bcc2c88", "firstIndex": 5234491305},
{"blockNumber": 12988757, "blockId": "0xdf12045bea73af18d4e71f8be8e334160f78b85f96a3535a4056409d8b61355a", "firstIndex": 5301600237},
{"blockNumber": 13049172, "blockId": "0xf07608d97a101cd9a95fee9d9062a15bcb333263e555f8cfa31da037e0468f30", "firstIndex": 5368709080},
{"blockNumber": 13108936, "blockId": "0x42739341db582d2f39b91ec9e8cc758777ca3f6ff9f25cd98883619fd5f026a7", "firstIndex": 5435817013},
{"blockNumber": 13175495, "blockId": "0x564f25eacb229350b7c648b5828169e7a0344ae62e866206828e2cfad8947f10", "firstIndex": 5502926476},
{"blockNumber": 13237721, "blockId": "0x0973425abec0fa6319701b46e07c2373b0580e3adbed6900aad27d5bf26dcb95", "firstIndex": 5570035419},
{"blockNumber": 13298771, "blockId": "0xf3a16fec5be808c9f7782fb578dc8cef7f8e2110f7289bd03c0cc13977dd1518", "firstIndex": 5637143840},
{"blockNumber": 13361281, "blockId": "0x3c0b6364201ca9221b61af3de27a3a87e111870b8c7efc43a6d8496e98c68690", "firstIndex": 5704253046},
{"blockNumber": 13421819, "blockId": "0x2f472e57997b95558b99e3e5e7e0e8d4dbf8b71c081aac6536c9ff5925dac2ce", "firstIndex": 5771361231},
{"blockNumber": 13480620, "blockId": "0xc4d689e87464a0c83c661c8e3a0614c370631de857f7e385b161dfe8bacd3e71", "firstIndex": 5838469468},
{"blockNumber": 13535793, "blockId": "0xe7674bacc8edce9fb3efd59b92c97da48fe7ace1de314b4a67d7d032fc3bb680", "firstIndex": 5905578026},
{"blockNumber": 13590588, "blockId": "0x6a3e86bdce7dd7d8792e1af9156edd8c3ffee7c20fed97001f58a9a2699f6594", "firstIndex": 5972687757},
{"blockNumber": 13646707, "blockId": "0xab404a5d3709cf571b04e9493f37116eeb5dd2bc9dc10c48387c1e0199013d69", "firstIndex": 6039797165},
{"blockNumber": 13703025, "blockId": "0x20e2fde15b8fe56f5dd7ab0f324c552038167ed44864bf3978e531ae68d6d138", "firstIndex": 6106905803},
{"blockNumber": 13761024, "blockId": "0x2ae49275e13e780f1d29aea8507b2a708ff7bfe977efac93e050273b8b3a8164", "firstIndex": 6174015107},
{"blockNumber": 13819468, "blockId": "0xb9d19cb31dedb1128b11cad9ffd6e58c70fe7ba65ba68f1ac63668ac5160ad85", "firstIndex": 6241124350},
{"blockNumber": 13877932, "blockId": "0x80b1ff0bb069a8479360a15eaa84ba30da02cfacadc564837f4b1c90478addb8", "firstIndex": 6308232256},
{"blockNumber": 13935384, "blockId": "0xe1f5469a559a6114dd469af61b118b9d9551a69bbd49a4e88f2a2d724830c871", "firstIndex": 6375341632},
{"blockNumber": 13994042, "blockId": "0x25188fb75f2328c870ade7c38ef42ff5fddef9c4e364eebe4c5d8d9cc3ecabab", "firstIndex": 6442449799},
{"blockNumber": 14051123, "blockId": "0xf4ef2bce9ee9222bdcf6b3a0c204676d9345e211e10c983e523930274e041ef1", "firstIndex": 6509559107},
{"blockNumber": 14109189, "blockId": "0x80b730c28f75d8cb5ec2fb736341cd87cb4ecb2c9c614e0a4ecc0f9812675d50", "firstIndex": 6576667347},
{"blockNumber": 14166822, "blockId": "0xf662a24b91684fa8ac462b31071f406de8d6183dba46d30d690f4407bc6af36f", "firstIndex": 6643777079},
{"blockNumber": 14222488, "blockId": "0x7333e324c96b12f11a38d1fc2ddb4860e018b90f5dc10f3dbe19f7679bb95535", "firstIndex": 6710885890},
{"blockNumber": 14277180, "blockId": "0x4373c1000e8e10179657689e2f0e42f88bd1601ecb4a5d83970d10287f6654cc", "firstIndex": 6777994595},
{"blockNumber": 14331080, "blockId": "0x9c708a750a3f284ec0ee950110b36fd488cb1ec24cd0c2ea72c19551ec5c42a5", "firstIndex": 6845103719},
{"blockNumber": 14384243, "blockId": "0x34ce7503b76335aa18dec880b0cefd388a29e0fcff6f2e1ddda8fb8c0ac1daf0", "firstIndex": 6912212376},
{"blockNumber": 14437670, "blockId": "0x79842efd3e406b41f51935fe2e6ad20a7dd5a9db2280ebd7f602ed93da1e3c24", "firstIndex": 6979320543},
{"blockNumber": 14489204, "blockId": "0xcd12addf0afdc229e9fe3bd0a34677a3826c5e78d4baf715f8ed36b736d6627a", "firstIndex": 7046430591},
{"blockNumber": 14541688, "blockId": "0x55f617abf208a73fc467e8cb5feead586b671dbb0f6281570b3c44b8eabb2b9e", "firstIndex": 7113538755},
{"blockNumber": 14594551, "blockId": "0xc7211bf772e93c8c2f945fcb6098b47c3455604cb8b94a505cb5cb720914c369", "firstIndex": 7180646025},
{"blockNumber": 14645065, "blockId": "0x6d5b0326f4b22e2b0196986a514f23ec6e9a62f70f53300a22b21ff661a6ef7e", "firstIndex": 7247756883},
{"blockNumber": 14695926, "blockId": "0x0a77272250e43b4bb46c02eb76944881a3c6b00a21bb9086a8229199bd62d97a", "firstIndex": 7314865843},
{"blockNumber": 14746330, "blockId": "0xd677fdbaf8efb1bfdc138ac6b2bd5d0e890a29acb1f52f40169181ad517b0d31", "firstIndex": 7381974956},
{"blockNumber": 14798546, "blockId": "0xbb277e8623acd2ce2340cf32f6c0ddab70fd95d862287f68a3c37250a70619cd", "firstIndex": 7449082890},
{"blockNumber": 14848230, "blockId": "0x587b39f11bdaa2091291c7c3947e88df2e91e7997f2375dfd43b6e310a538582", "firstIndex": 7516192636},
{"blockNumber": 14897646, "blockId": "0xf5b5c9d0c024ca0c0f0c6171871f609687f4ccb064ededbd61176cf23a9011e8", "firstIndex": 7583299602},
{"blockNumber": 14950782, "blockId": "0x50549486afaf92a4c3520012b325e914ef77a82e4d6530a71f9b1cca31bfae18", "firstIndex": 7650409868},
{"blockNumber": 15004101, "blockId": "0x7edac55dea3ee4308db60b9bc0524836226fe301e085b3ce39105bd145ba7fc3", "firstIndex": 7717517503},
{"blockNumber": 15056903, "blockId": "0xb4cfd02d435718598179cdba3f5c11eb8653fe97ec8d89c60673e3e07b8dfc94", "firstIndex": 7784627997},
{"blockNumber": 15108302, "blockId": "0x53c77a7de4515e9e93467a76f04cc401834bcdd64e9dfa03cf6d2844a6930293", "firstIndex": 7851736988},
{"blockNumber": 15159526, "blockId": "0x1a31ad84b423254d7ff24e7eca54048ed8cc13cec5eb7289bf3f98ed4de9f724", "firstIndex": 7918844431},
{"blockNumber": 15211013, "blockId": "0xe5d491e1d6cc5322454143b915c106be1bf28114a41b054ba5e5cfe0abecafba", "firstIndex": 7985953942},
{"blockNumber": 15264389, "blockId": "0xd9939bb9e58e95d2672c1148b4ec5730204527d3f3fc98ca03a67dc85cf3d710", "firstIndex": 8053063187},
{"blockNumber": 15315862, "blockId": "0x7254f99c4bb05235d5b437984c9132164e33182d4ce11a3847999da5c28b4092", "firstIndex": 8120172147},
{"blockNumber": 15364726, "blockId": "0x11b57547579d9009679e327f57e308fe86856391805bc3c86e7b39daae890f52", "firstIndex": 8187281042},
{"blockNumber": 15412886, "blockId": "0xbe3602b1dbef9015a3ec7968ac7652edf4424934b6bf7b713b99d8556f1d9444", "firstIndex": 8254390023},
{"blockNumber": 15462792, "blockId": "0x3348ca4e14ac8d3c6ac6df676deaf3e3b5e0a11b599f73bd9739b74ebd693efe", "firstIndex": 8321499024},
{"blockNumber": 15509914, "blockId": "0xbc98fd6b71438d5a169f9373172fea799fa3d22a8e6fe648d35e1070f2261113", "firstIndex": 8388606521},
{"blockNumber": 15558748, "blockId": "0x5fa2cf499276ae74a5b8618990e71ed11a063619afe25c01b46e6252eba14c19", "firstIndex": 8455716577},
{"blockNumber": 15604217, "blockId": "0x78a608e13d2eb3c5fed81a19b829ede88071cf01ea9ff58112a7472435f97c30", "firstIndex": 8522825668},
{"blockNumber": 15651869, "blockId": "0xd465d861d925d1475440782ff16c2b3361ba3c8e169d7cc90eb8dfc0f31b0aac", "firstIndex": 8589934080},
{"blockNumber": 15700968, "blockId": "0x71e3def131271e02c06ca945d14a995703a48faac1334a9e2e2321edd0b504d0", "firstIndex": 8657043390},
{"blockNumber": 15762986, "blockId": "0x9b1b51dca2eae29162ca66968a77b45175f134b44aea3defadcb924f83e0b944", "firstIndex": 8724151376},
{"blockNumber": 15814455, "blockId": "0x3c04a509cb6304d3df4bef57e0119d9e615ab737ec0b4a7deada6e5f57d9f873", "firstIndex": 8791260562},
{"blockNumber": 15865639, "blockId": "0x9e9e26148c774518ecf362c0e7c65a5c1b054a8a3e4e36036c70e273fac6147c", "firstIndex": 8858368894},
{"blockNumber": 15920564, "blockId": "0x9efe1d4dbfd9aa891ac0cffd3e1422a27ba2ea4add211b6900a2242cdb0f0ca0", "firstIndex": 8925477950},
{"blockNumber": 15974371, "blockId": "0xc63ccef7bc35a0b431a411f99fe581b322d00cfc6422d078696808a5658a32ac", "firstIndex": 8992587107},
{"blockNumber": 16032913, "blockId": "0x3e60957224964669a8646914e3166553b9f4256d5be160b17995d838af3ef137", "firstIndex": 9059696632},
{"blockNumber": 16091057, "blockId": "0x12b346047bb49063ab6d9e737775924cf05c52114202ddb1a2bdaf9caabbfe0c", "firstIndex": 9126804912},
{"blockNumber": 16150977, "blockId": "0x49318a32ff0ce979c4061c1c34db2a94fb06e7669c93742b75aff14a134fa598", "firstIndex": 9193913896},
{"blockNumber": 16207432, "blockId": "0xf7870865edf81be4389a0be01468da959de703df0d431610814d16ed480176e4", "firstIndex": 9261019778},
{"blockNumber": 16262582, "blockId": "0x25818e0f4d54af6c44ef7b23add34409a47de3ab1c905889478f3ec8ad173ec3", "firstIndex": 9328131320},
{"blockNumber": 16319695, "blockId": "0x25de4b1c18cc503f5d12b4fa9072d33a11fa503a3dbeb9ab3d016b57c1e5cd4d", "firstIndex": 9395240790},
{"blockNumber": 16373605, "blockId": "0x3794a5e0d2aa10baf1e6a5ec623d6089fdd39799eff633017d8df5144526939f", "firstIndex": 9462349509},
{"blockNumber": 16423494, "blockId": "0xe0217d947ba3865dfc9288e0c890b0996457bb9d18467bd125e86bbb0052b57f", "firstIndex": 9529458033},
{"blockNumber": 16474853, "blockId": "0xd454f033d190f22f9e56f0209ea1eeb3b6257805d5d88650d2759eb4d24821b7", "firstIndex": 9596567055},
{"blockNumber": 16525689, "blockId": "0x8a23cbbf3e258e13f5a1ada434366796cb4a3e5b1062455582fb2bc3ab991541", "firstIndex": 9663674943},
{"blockNumber": 16574203, "blockId": "0xc1a5b7d26e8222bd2d56ef4108f75d69f7c116707d348950834e00962241a4f8", "firstIndex": 9730785112},
{"blockNumber": 16622622, "blockId": "0x3ddb3ef7a4309bd788258fb0d62613c89a0b4de715f4e12f6017a194d19d6481", "firstIndex": 9797893665},
{"blockNumber": 16672585, "blockId": "0x8aa5e9f72b261f9e2a9eb768483d1bbd84d3a88fdb1346f6a9a7f262fd28ba41", "firstIndex": 9865002893},
{"blockNumber": 16720124, "blockId": "0x2128f8baf264166e37554d5c31a06de58d9ccfb663117358251da548a23a060f", "firstIndex": 9932111275},
{"blockNumber": 16769162, "blockId": "0x6b3e849482d3222032740ad6b8f98e24636c82682a6a3572b1ef76dfebc66821", "firstIndex": 9999217824},
{"blockNumber": 16818311, "blockId": "0xe45f57381978a2bfc85bd20af1c41e2b630412642ac4f606b477f05f030ef5d9", "firstIndex": 10066328668},
{"blockNumber": 16869531, "blockId": "0xa154555266d24dc1f4885af5fafcf8cab3de788998cf69e1d28f56aa13a40c43", "firstIndex": 10133437302},
{"blockNumber": 16921611, "blockId": "0xf1f829b4ab5eec6e243916dd530993fa11eef5510fd730e8d09ead6b380355a1", "firstIndex": 10200547185},
{"blockNumber": 16974870, "blockId": "0x1a33202b95926ae4cb8e6e99d8d150f3c50d817b3a316452bdf428c971dabde5", "firstIndex": 10267655914},
{"blockNumber": 17031277, "blockId": "0x706c9dd0dc81e7ac29d2ea0f826e6b8a1dcb5adb1b904ff6e43260729c9fd0a7", "firstIndex": 10334764934},
{"blockNumber": 17086330, "blockId": "0x085a80cafe96b520105b9a1f8e7a2bbc9474da24da7e6344ca7c4d32db822f92", "firstIndex": 10401871892},
{"blockNumber": 17141311, "blockId": "0x33ec6513dfa515bc5f6356476b4eb075a8064181d6aaf6aa1a1e18887e342f74", "firstIndex": 10468982364},
{"blockNumber": 17190907, "blockId": "0x6f41273d3bf30d3347e7eb68872a49b3ac947f314543478be7a28a55e5c41a3c", "firstIndex": 10536090817},
{"blockNumber": 17237199, "blockId": "0x9a87a14a128c0345a366940f821a14f16719de628658ac0628e410a72d723e90", "firstIndex": 10603200178},
{"blockNumber": 17287181, "blockId": "0x9c6e78adcf562ac63c103e3e5a02f025023079aca79bdd6ef18f7bd2a6271c29", "firstIndex": 10670309183},
{"blockNumber": 17338652, "blockId": "0x1b747da97b2397a293602af57514dab4ca1010bb6c601ff05cb2012dd1124ebb", "firstIndex": 10737418023},
{"blockNumber": 17389337, "blockId": "0xbc3c0ca1e5989605b9b59c94b418562eb17ccbce30e45ac8531cf0b3867a6b2c", "firstIndex": 10804522857},
{"blockNumber": 17442261, "blockId": "0x1ec341be1cbd09f559bfa3d3e39a341d8e21052eeb7880931d43d086651733b7", "firstIndex": 10871635535},
{"blockNumber": 17497787, "blockId": "0x6069880d486f2548599df1e14e12752d3eb9bc99843a98cd6631c22be1b58554", "firstIndex": 10938744657},
{"blockNumber": 17554322, "blockId": "0x69b2564bc00b1f310f6b416912869d7530d7864bf7d70d55c7ace554f129b989", "firstIndex": 11005852829},
{"blockNumber": 17608492, "blockId": "0x7d590653d5fa52c0d3ee453a77d2088504f57adcef35cd57c567afb554608457", "firstIndex": 11072961972},
{"blockNumber": 17664272, "blockId": "0xdc16159d3500cdc7410873102f41fc55de2a8a41e3779c4b70e6224a541e2b9e", "firstIndex": 11140070967},
{"blockNumber": 17715101, "blockId": "0x655e33c4e81182464ea0b0e1fdbc53ce53902431db5107326b816091a4564652", "firstIndex": 11207179487},
{"blockNumber": 17764042, "blockId": "0x54439184f31cd83ba06b48b6dbfdd744ae7246355be1327b44744058711d05c0", "firstIndex": 11274287303},
{"blockNumber": 17814383, "blockId": "0xfb453bc951360c76fb09bb1b9a3e39d23ececa0adb93368cc3f41f0457845089", "firstIndex": 11341397984},
{"blockNumber": 17864648, "blockId": "0x32a68823ef4ec0cbab2fe50c97e3f462b575e8b117da40d00c710b4c66ee1d6d", "firstIndex": 11408505657},
{"blockNumber": 17913366, "blockId": "0x04b944aab8a4ff91b77c2191817cf051766100c227616a3746af53407e740124", "firstIndex": 11475614351},
{"blockNumber": 17961690, "blockId": "0x08bee7cc0b764106ca01dd5370b617879487ffb423688c96e948dce125990f45", "firstIndex": 11542723488},
{"blockNumber": 18011048, "blockId": "0x94c39d3a64f3e9a91b1d98554cd29e1390e30fa61cfa4e909c503eee2fd9f165", "firstIndex": 11609833142},
{"blockNumber": 18061209, "blockId": "0x2ee9ade68955c030488c8a30537bdf948355f7dd5ae64942b5bfce1be6650e19", "firstIndex": 11676941316},
{"blockNumber": 18111692, "blockId": "0xd6c4fd0c1cc20ed5e7960bb5043e9e5e9c66a4d2ec5709ac9797fff678435640", "firstIndex": 11744050346},
{"blockNumber": 18166212, "blockId": "0x3262588c2ef79a3b3f6a3db6435202d22f5667cd48c136b0797404901525c9ff", "firstIndex": 11811159686},
{"blockNumber": 18218743, "blockId": "0x935bd9a4164ff7ecd09a37b916ce5bf78487bd19377b5b17be153e39318aee74", "firstIndex": 11878268593},
{"blockNumber": 18271236, "blockId": "0xe58ebb821f27e3665898f390802a3d129d217b3a3ee36d890a85cf22a0a8aa33", "firstIndex": 11945376750},
{"blockNumber": 18323007, "blockId": "0x3997a841468efa1bc614bfc3de4502274901b04b428f87a1f3086dfd78cda1eb", "firstIndex": 12012485748},
{"blockNumber": 18372443, "blockId": "0xc44a13a5d02e8dc39f355de5e21ce7bb311ce7f4d9114ff480dce235a169e416", "firstIndex": 12079595370},
{"blockNumber": 18421829, "blockId": "0x7da63a0b613d8745597b2ac64fd5cc8b2fb14b24d163b12a0a39d7d3d4ff7b5c", "firstIndex": 12146703582},
{"blockNumber": 18471706, "blockId": "0xd632a1893f415ff618f4b612a7687e6af1f12feeed81f46f0022090829c1eb4c", "firstIndex": 12213812677},
{"blockNumber": 18522301, "blockId": "0x44fa2cf08145ae40e8e42f4e6b4ab7df360a17c5a065ce45fcc41b51bee011f4", "firstIndex": 12280921639},
{"blockNumber": 18572935, "blockId": "0x72b8ab4c78c90425ee054b4806a8be703da0febdf1d51866358ec2bd21ba9529", "firstIndex": 12348029751},
{"blockNumber": 18623431, "blockId": "0x8c4cb2f13501d9788820280c6f16692d0737258c3896f1e4bded32d838febf7f", "firstIndex": 12415138965},
{"blockNumber": 18675470, "blockId": "0x523b73c19ea8b3ae32ef141a83ef9855e667ebf51443cfcabd1a06659359062a", "firstIndex": 12482247454},
{"blockNumber": 18725728, "blockId": "0x0cfbd131eb5dad51488238079fba29a63eebb5c32d1a543cb072e48dc2104ef3", "firstIndex": 12549356369},
{"blockNumber": 18778387, "blockId": "0xc4906c77af8058b9f172a4f0e8788c7887f05caa5ac752b38b5387080f74ae49", "firstIndex": 12616465992},
{"blockNumber": 18835044, "blockId": "0x49c5e07f409a841dc81f3ef8417f1951f8fcc13c90134f9d2a0cd11938f9fa36", "firstIndex": 12683575082},
{"blockNumber": 18883308, "blockId": "0x386a58dd5f79a419eeb05075b07b3ff3bc836a265c9688854a504223b1d6a830", "firstIndex": 12750683753},
{"blockNumber": 18933635, "blockId": "0xd3881292147589bd2e192769e5c9175b5d03a453fe1ef3c4b5b6858ac9402a2f", "firstIndex": 12817792470},
{"blockNumber": 18988254, "blockId": "0xcbe72dfa15428ac21b9c59c703ceaa0eb4b2205927687261d7aaed3dbb3783ea", "firstIndex": 12884882858},
{"blockNumber": 19041325, "blockId": "0x92b077e1c2f8819da728f0307c914fdcd57eba14ea07d9a45c28d1ed8ffff576", "firstIndex": 12952010530},
{"blockNumber": 19089163, "blockId": "0x43f8ab2d3dfc34c8e18cba903074d54e235dc546f19c4eb78245a522c266c84e", "firstIndex": 13019119228},
{"blockNumber": 19140629, "blockId": "0xab7b7ae5424b18105a13b657fa6099d4ab67fde5baff39fe6e4de707397e995c", "firstIndex": 13086228236},
{"blockNumber": 19192118, "blockId": "0x451327e6a5cf6ce1c8c14c01687dc5f719f3c2176f46bac4f264616256e30d1c", "firstIndex": 13153337116},
{"blockNumber": 19237836, "blockId": "0x9b260d6be369557d1dc88aca423e2697e697d941d1b726c183015b5649e248c8", "firstIndex": 13220445421},
{"blockNumber": 19291271, "blockId": "0x4878c28d79e1f71bc11e062eb61cb52ae6a18b670b0f9bea38b477944615078e", "firstIndex": 13287554254},
{"blockNumber": 19344448, "blockId": "0x56243b9ad863bf90953fe9aa6e64a426629384db1190e70dce79575d30595f7e", "firstIndex": 13354663659},
{"blockNumber": 19394948, "blockId": "0x195173b64dda7908d6aa39a63c8bdd29ec181d401e369d513be1308550d0ddcb", "firstIndex": 13421771935},
{"blockNumber": 19443075, "blockId": "0xd39c1d60996475e65d1ab5b4e755f510ca466564a8155d35db6667988d6c0e44", "firstIndex": 13488880427},
{"blockNumber": 19488383, "blockId": "0x28956eb8856fa8db59c02585016b8baf43bc44bc35b00bdaf8a6babe51101c5c", "firstIndex": 13555977105},
{"blockNumber": 19534584, "blockId": "0x2421c97b0f140185d4c20943cd4ed7d7424468482feb76e3003a1cc69da3fd7b", "firstIndex": 13623097580},
{"blockNumber": 19579602, "blockId": "0x25f96529028e9f51c59aec9ce8de282b7dd67066fd46a1694130698ed0f40d8b", "firstIndex": 13690207623},
{"blockNumber": 19621517, "blockId": "0x4f6f6e0a0488f3d51823bc4b07c292348c259b1866968f77ee76b66b37101c75", "firstIndex": 13757315529},
{"blockNumber": 19665085, "blockId": "0x00f9315f89681b44bff46f1bad8894bc6dfae1c459d3d6520f9881861304a496", "firstIndex": 13824425382},
{"blockNumber": 19709229, "blockId": "0x24e022b21ae1ba8a3e8c87cb9734aa1d1810fc4a69fe147d3ebb1ff0df8bcc15", "firstIndex": 13891534799},
{"blockNumber": 19755387, "blockId": "0x77f184b7183b1a351760d242041249464b42cfaa6fbc4326f352b06bb3b21a02", "firstIndex": 13958642483},
{"blockNumber": 19803894, "blockId": "0xf37eb1b054a6d61272940361f386eb744cded84d15c3250a7eabadede257371c", "firstIndex": 14025751618},
{"blockNumber": 19847885, "blockId": "0x4659649fa8a3b4bbe8978673ba9a22ae20352c7052b676d373b5a51b1967ffa4", "firstIndex": 14092848654},
{"blockNumber": 19894193, "blockId": "0x15606bdc0f1a710bd69443c7154d4979aece9329977b65990c9b39d6df84ed5c", "firstIndex": 14159970181},
{"blockNumber": 19938551, "blockId": "0x6a8f4571924ed902bd8e71bf8ed9cc9d72cabeabc410277c8f0fc2b477d00eb7", "firstIndex": 14227077892},
{"blockNumber": 19985354, "blockId": "0x7b6fb6376410b4d9e5d7ee02f78b2054e005dd2976eea47fc714f66b967dc285", "firstIndex": 14294187965},
{"blockNumber": 20028440, "blockId": "0x9b37440b71c24756b8855b8012432b84276ae94c80aa1ccc8b70a7705992103c", "firstIndex": 14361296503},
{"blockNumber": 20071780, "blockId": "0xa2ed129f343f3d60419772ec5635edcd36b8680c9419b6626e2bc84b230c709b", "firstIndex": 14428405230},
{"blockNumber": 20113832, "blockId": "0xe7a610e8bcbf8ded141ebc7142de03dfc54b1bcc79e3bf8d07fad4e42b665bba", "firstIndex": 14495512019},
{"blockNumber": 20156854, "blockId": "0xbe09704f65a70ef8843d9c8e511ddc989ea139dbe94cdfe37f52b03620d62385", "firstIndex": 14562622430},
{"blockNumber": 20200135, "blockId": "0x9a58c34d5f77342e94065d119905c000223cd988c4b11f1539fff20737159630", "firstIndex": 14629731923},
{"blockNumber": 20244389, "blockId": "0x1e733f0db9ef21183107259b3c2408c78fa5a01469928cd295f3ea7e8eedda45", "firstIndex": 14696840011},
{"blockNumber": 20288489, "blockId": "0xb5ad7edd86b181226c8c7be0a08069e3955234e797426843fff9de0f57ec59cc", "firstIndex": 14763949714},
{"blockNumber": 20333582, "blockId": "0x8040c209f5cd1738ee0f85c2f1db7c43a420d148680c7390fd1701b9f0bb671a", "firstIndex": 14831058335},
{"blockNumber": 20377087, "blockId": "0x08fdc4cd246b6ae9d4a45646b0aed6af3bb330eb6cd4c8b93646157e7b002b84", "firstIndex": 14898167722},
{"blockNumber": 20421699, "blockId": "0x5a2912b5fc2f02df33b655155990f92dcaacda5b75427fe3d87fb38f36b1c17d", "firstIndex": 14965275691},
{"blockNumber": 20467194, "blockId": "0x3deaf4325c461004b090b0261996c645ab529c1471feaf7dc2bbe1f128180297", "firstIndex": 15032385211},
{"blockNumber": 20512397, "blockId": "0x37e39697ec1b7683a6202be250ffaee7a1102e8030f87550b94af05ec66cec83", "firstIndex": 15099493973},
{"blockNumber": 20557443, "blockId": "0x8e9c04468f3111eab8b1f6a58b277862c624861c237cadecc53ec249bd811bda", "firstIndex": 15166602882},
{"blockNumber": 20595899, "blockId": "0x9787555fe57e4650002257eb2c88f1ef435b99d406e33fe2f889be180123ef25", "firstIndex": 15233709908},
{"blockNumber": 20638606, "blockId": "0x70681cffd159ce2e580dbbbe8fa6b5343dbcb081429cdda6c577e615bef4ef05", "firstIndex": 15300820678},
{"blockNumber": 20683605, "blockId": "0xb32662d5e241132ffe2249caea67f5746a6f4382297b2ac87c81e2794faf1f7a", "firstIndex": 15367929350},
{"blockNumber": 20728630, "blockId": "0x15a817c846928b673032d5eacd0cff7a04217d268457aa30a322ecca32be4d49", "firstIndex": 15435037830},
{"blockNumber": 20771519, "blockId": "0x542bc7b9804bbc45f4be470f4dc56f215a4dec71fed71eba2ffc804afd262b95", "firstIndex": 15502145990},
{"blockNumber": 20815097, "blockId": "0x798cdd51c964fcf18561d70095d9613b84ba836817972799c9dfd0bfbe1e042b", "firstIndex": 15569256033},
{"blockNumber": 20857859, "blockId": "0xfb5bb066d419a651d8e0186569eb4e8d8bcd5181d8f02e0d578b5dfe2fc738dd", "firstIndex": 15636364671},
{"blockNumber": 20896890, "blockId": "0x834b8d6fad779e4cf8214128f6c93d7387b6d6279e517f6f0a284b5d831cc3ae", "firstIndex": 15703472902},
{"blockNumber": 20939387, "blockId": "0x7adee7c78420c711efa216c61e0b561e581d7ff0331efd91ee16a609b34cfdc2", "firstIndex": 15770582325},
{"blockNumber": 20981303, "blockId": "0x6f5d7b0cc6dad5eb258176e07de21795a8347d68f7303f06934046e0236bea6d", "firstIndex": 15837691713},
{"blockNumber": 21023216, "blockId": "0x96cfe35a45df1297a36f42c59ebe706ab0473dfbf59ce910b5c5a8dbf696de1c", "firstIndex": 15904799667},
{"blockNumber": 21068378, "blockId": "0x93753875ff330d922b23f823203198f3b1bb8833367c6b6a8f896ff54be2c12d", "firstIndex": 15971909040},
{"blockNumber": 21112445, "blockId": "0x6ac02fa6ae486b86aba562eaf6f3d883befaa8ebedcfd8d74bdb7368d42deee3", "firstIndex": 16039003625},
{"blockNumber": 21155992, "blockId": "0x25f76896b4b693bafb79e9a535e2bf00ed62a577e35209749346e8e79a60bb71", "firstIndex": 16106126344},
{"blockNumber": 21200962, "blockId": "0x725f2befe913cb2659d262e2d3b6f79a706b31c557d52669471da22347ec8287", "firstIndex": 16173235265},
{"blockNumber": 21244663, "blockId": "0x6778c4194f54e70939da38853daddb22bfaf160d35617ab05d0f5c476741147b", "firstIndex": 16240344735},
{"blockNumber": 21290273, "blockId": "0x433ac819c40bd3061205fe0ece0645eec73f54a0a5c1559c981f983345bc0154", "firstIndex": 16307453543},
{"blockNumber": 21336156, "blockId": "0x261dc8c1639d505624150d2388d15ed10bfb4c3ce9c0c327a4ec26531689a097", "firstIndex": 16374562466},
{"blockNumber": 21378880, "blockId": "0x5c78b2b70553140dfdfdd4f415b98f88e74f74662315834038fd99042277d917", "firstIndex": 16441671104},
{"blockNumber": 21421613, "blockId": "0x854532f9d1c77627b763f9cbc7099a653d59554ed57fa763bc218834c82955fe", "firstIndex": 16508780351},
{"blockNumber": 21466875, "blockId": "0xb8b83cc62084e948235ef4b5973bf7fd988fa28bcaa72f7d38ad8e50de729618", "firstIndex": 16575888599},
{"blockNumber": 21511942, "blockId": "0xe806a28bc1b7f8cd752c8ceedbe081d49773d4558a9fb95e3357c0c07172522d", "firstIndex": 16642996907},
{"blockNumber": 21550291, "blockId": "0x1f3e26d303e7a2a9b0614f12f62b189da365b3947c5fe2d99ed2711b37fe7daa", "firstIndex": 16710106826},
{"blockNumber": 21592690, "blockId": "0xa1408cfbc693faee4425e8fd9e83a181be535c33f874b56c3a7a114404c4f686", "firstIndex": 16777215566},
{"blockNumber": 21636275, "blockId": "0x704734c2d0351f8ccd38721a9a4b80c063368afaaa857518d98498180a502bba", "firstIndex": 16844323959},
{"blockNumber": 21681066, "blockId": "0x1e738568ed393395c498b109ad61c0286747318aae0364936f19a7b6aba94aef", "firstIndex": 16911433076},
{"blockNumber": 21725592, "blockId": "0xee87b7948e25a7498a247c616a0fbaa27f21b004e11fc56f2a20c03791ed8122", "firstIndex": 16978540993}
]

View file

@ -0,0 +1,63 @@
[
{"blockNumber": 3246675, "blockId": "0x36bf7de9e1f151963088ca3efa206b6e78411d699d2f64f3bf86895294275e0b", "firstIndex": 67107286},
{"blockNumber": 3575582, "blockId": "0x08931012467636d3b67ae187790951daed2bb6423f9cd94e166df787b856788d", "firstIndex": 134217672},
{"blockNumber": 3694264, "blockId": "0x1f35f276a3c78e5942ee285fcbd0c687691853c599a2f5b174ea88f653bc9514", "firstIndex": 201326578},
{"blockNumber": 3725632, "blockId": "0x3bcb264c56c3eeab6c8588145f09dff3fb5f821d9fc1e7b92264b14314dae553", "firstIndex": 268433636},
{"blockNumber": 3795390, "blockId": "0x2d1ef2815bb8e018b275fa65540b98265285016aff12596bd89a3b1442d248eb", "firstIndex": 335542953},
{"blockNumber": 3856683, "blockId": "0x8a9a46d6f53975cd9ec829c3c307a99fb62b8428cefb63ffe06d17143649c3ee", "firstIndex": 402648835},
{"blockNumber": 3869370, "blockId": "0x2e8c04e7e5e96d09260b65d77b1770b4105b0db2ee7d638c48f086b8afac17db", "firstIndex": 469759276},
{"blockNumber": 3938357, "blockId": "0xf20f2cdbcc412d5340e31955d14a6526ea748ba99b5ec70b6615bdb18bcd4cfb", "firstIndex": 536868027},
{"blockNumber": 3984894, "blockId": "0x0bcd886b3cebb884d5beeaf5ad15ee1514968b5ad07177297c7d9c00f27aa406", "firstIndex": 603968430},
{"blockNumber": 4002664, "blockId": "0x7d3575b6ca685468fa5a5fa9ff9d5fac4415b0a67a3ed87d3530f127db32fff4", "firstIndex": 671088417},
{"blockNumber": 4113187, "blockId": "0x3a5313ac5b602134bb73535b22801261e891ccb7bd660ab20e0a536dc46d3e13", "firstIndex": 738197016},
{"blockNumber": 4260758, "blockId": "0xe30fb9a304d3602896a5716d310f67ba34ccef7f809a3ead4b2d991cb9ee4eb0", "firstIndex": 805306270},
{"blockNumber": 4391131, "blockId": "0x3958478c1c3be9b7caedbcc96230ed446d711e56580e324bc2fcf903fc87c90f", "firstIndex": 872415115},
{"blockNumber": 4515650, "blockId": "0x46a3a7b97a9dff4ef4dc2c1cc5cd501f2182d9548655b77b5e07a2dbb41071a4", "firstIndex": 939523930},
{"blockNumber": 4634818, "blockId": "0x2197d0dd3925c1d7ba3e2c4eef20035b68efc0a2506f76ddd9e481e0ce8ca6e1", "firstIndex": 1006628557},
{"blockNumber": 4718295, "blockId": "0xcce7bb4af1a41e6056ef68192e60c738be01ac3e071ed1ec52cead08a39995ce", "firstIndex": 1073734698},
{"blockNumber": 4753438, "blockId": "0xa60e043728a369cdf39a399bd7a903085ee9386f38176947578e5692b4b01f65", "firstIndex": 1140843192},
{"blockNumber": 4786522, "blockId": "0x10629cadc00e65f193fa4d10ecd2bf1855e442814c4a409d19aae9eb895dce13", "firstIndex": 1207956586},
{"blockNumber": 4811706, "blockId": "0xf1e94111f0086733bdcb4a653486a8b94ec998b61dda0af0fd465c9b4e344f87", "firstIndex": 1275058221},
{"blockNumber": 4841796, "blockId": "0xa530f7dd72881ac831affdc579c9d75f6d4b6853b1f1894d320bd9047df5f9eb", "firstIndex": 1342177155},
{"blockNumber": 4914835, "blockId": "0xbd8321e354f72c4190225f8ed63d4aba794b3b568677d985e099cb62d9d36bae", "firstIndex": 1409286143},
{"blockNumber": 4992519, "blockId": "0x4a06a5a4aa5bc52151937cc1c0f8da691a0282e94aab8b73b9faa87da8d028de", "firstIndex": 1476384367},
{"blockNumber": 5088668, "blockId": "0xb7d5ee03c08ed3936348eeb3931be8f804e61f2b09debf305967c6a7bbf007e0", "firstIndex": 1543502599},
{"blockNumber": 5155029, "blockId": "0x84f590dfc2e11f1ca53c1757ac3c508d56f55ee24d6ca5501895974be4250d76", "firstIndex": 1610605837},
{"blockNumber": 5204413, "blockId": "0xeaf2c3fb6f927c16d38fab08b34303867b87470558612404c7f9e3256b80c5b9", "firstIndex": 1677720841},
{"blockNumber": 5269957, "blockId": "0x596e0b2e8e4c18c803b61767320fe32c063153d870c94e4a08e9a68cbaa582a9", "firstIndex": 1744825147},
{"blockNumber": 5337678, "blockId": "0x7b2d54f8af1ecaaaab994e137d4421d8236c1c10d9a7bdcb9e5500db7a3fe9a3", "firstIndex": 1811939316},
{"blockNumber": 5399058, "blockId": "0xb61ef16d55c96682fb62b0110a2dbc50d8eff2526be4121ece3690700611c71b", "firstIndex": 1879046044},
{"blockNumber": 5422707, "blockId": "0xdabcab7c0cc9cb9f22f7507a1076c87831cb1afed9d0aa5bcd93f22266720c91", "firstIndex": 1946156915},
{"blockNumber": 5454264, "blockId": "0xe1bde812906605ce662f5fd9f01b49c7331fb25f52ab5b12d35ea2b4da5458fe", "firstIndex": 2013259168},
{"blockNumber": 5498898, "blockId": "0x9533d9c5353d22f8a235e95831cfbf4d5a7220a430ca23494526a9d3aa866fe8", "firstIndex": 2080374321},
{"blockNumber": 5554801, "blockId": "0xe7b320bbecb19f1e99dd6ce4aed1efc754d7b2022e1f80389e8a21413c465f55", "firstIndex": 2147476253},
{"blockNumber": 5594725, "blockId": "0xce6750be4a5b3e0fe152dd02308e94f7d56b254852a7e9acef6e14105053d7d1", "firstIndex": 2214591591},
{"blockNumber": 5645198, "blockId": "0x5d42d39999c546f37001d5f613732fb54032384dd71a686d3664d2c8a1337752", "firstIndex": 2281696503},
{"blockNumber": 5687659, "blockId": "0x3ed941be39a33ffa69cf3531a67f5a25f712ba05db890ff377f60d26842e4b1c", "firstIndex": 2348801751},
{"blockNumber": 5727823, "blockId": "0xaf699b6c4cd58181bd609a66990b8edb5d1b94d5ff1ab732ded35ce7b8373353", "firstIndex": 2415917178},
{"blockNumber": 5784505, "blockId": "0x621c740d04ea41f70a2f0537e21e5b96169aea8a8efee0ae5527717e5c40aa64", "firstIndex": 2483024581},
{"blockNumber": 5843958, "blockId": "0xec122204a4e4698748f55a1c9f8582c46bacda029aee4de1a234e67e3288e6b1", "firstIndex": 2550136761},
{"blockNumber": 5906359, "blockId": "0x8af5ce73fbd7a6110fb8b19b75a7322456ece88fcfa1614c745f1a65f4e915c1", "firstIndex": 2617245617},
{"blockNumber": 5977944, "blockId": "0xbc8186258298a4f376124989cfb7b22c2bea6603a5245bb6c505c5fc45844bbd", "firstIndex": 2684350982},
{"blockNumber": 6051571, "blockId": "0x54f9df9d9d73d1aa1cfcd6f372377c6013ecba2a1ed158d3c304f4fca51dae58", "firstIndex": 2751463209},
{"blockNumber": 6118461, "blockId": "0xfea757fad3f763c62a514a9c24924934539ca56620bd811f83e9cc2e671f0cf0", "firstIndex": 2818572283},
{"blockNumber": 6174385, "blockId": "0x2d8d0226e58f7516c13f9e1c9cf3ea65bb520fa1dfd7249dc9ea34a4e1fd430d", "firstIndex": 2885681036},
{"blockNumber": 6276318, "blockId": "0xa922e9d54fd062b658c4866ed218632ddd51f250d671628a42968bb912d3ed5d", "firstIndex": 2952789983},
{"blockNumber": 6368452, "blockId": "0x8d3d7466a7c9ca7298f82c37c38b0f64ec04522d2ed2e2349f8edc020c57f2c4", "firstIndex": 3019898695},
{"blockNumber": 6470810, "blockId": "0x9887c35542835ee81153fa0e4d8a9e6f170b6e14fc78d8c7f3d900d0a70434f1", "firstIndex": 3087007578},
{"blockNumber": 6553334, "blockId": "0x7b0d89a0282c18785fcc108dbdc9d45dd9d63b7084ddc676df9e9504585a5969", "firstIndex": 3154115987},
{"blockNumber": 6663825, "blockId": "0xff6cec99324a89d6d36275c17a4569f0cba203fe5b0350f155a7d5445e0ed419", "firstIndex": 3221224775},
{"blockNumber": 6767082, "blockId": "0xe10a96a7194f98bf262f0cb1cdfb4d3b9a2072139dfcbe3f1eb01419e353044e", "firstIndex": 3288334139},
{"blockNumber": 6886709, "blockId": "0x20f6a5d986913025ad5b6b6387d818e49a3caf838326f4002c1439ca61313be5", "firstIndex": 3355442979},
{"blockNumber": 6978948, "blockId": "0xd7c3024765245ec49e6a48b076d540bc91f57f2ccc125e17d60dd37bb669f843", "firstIndex": 3422551908},
{"blockNumber": 7098891, "blockId": "0x05114c037e1b4d69a46d74a974be9bce45e87ad2226a59b44dd17f98dd2fd0d1", "firstIndex": 3489659530},
{"blockNumber": 7203157, "blockId": "0xc0f610014fcd9f2850274b58179d474f0947676fd0639b2884316467c631811d", "firstIndex": 3556769512},
{"blockNumber": 7256735, "blockId": "0x0324c15b3b23fd82c2962dd167618e77e60ebeac5a2c87f672caddc9732337b3", "firstIndex": 3623876508},
{"blockNumber": 7307851, "blockId": "0x8e23280d1a3aec877d7758413ed20299d381aa43e7e2fc6f381ad96e8ff0acef", "firstIndex": 3690987098},
{"blockNumber": 7369389, "blockId": "0xbf6436eb2b88539945d6673141a14cb79ffc1e7db2b57176acf8e02ff3b6fcd3", "firstIndex": 3758096287},
{"blockNumber": 7445220, "blockId": "0x147619f74815283d834ac08ff494fb4791207b3949c64b2623f11ff6141ee7a7", "firstIndex": 3825204992},
{"blockNumber": 7511632, "blockId": "0x5094d64868f419e6ac3d253d19d5feda76564a0d56d7bbf8a822dff1c2261b30", "firstIndex": 3892314047},
{"blockNumber": 7557280, "blockId": "0x54aba9351a1ba51873645221aa7c991024da1fe468a600ddb6e2559351d9c28f", "firstIndex": 3959422859},
{"blockNumber": 7606304, "blockId": "0xbbe2fed08cf0b0ff2cb6ae9fd7257843f77a04a7d4cafb06d7a4bedea6ab0c98", "firstIndex": 4026531690}
]

View file

@ -0,0 +1,726 @@
// Copyright 2024 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 <http://www.gnu.org/licenses/>.
package filtermaps
import (
"bytes"
"errors"
"fmt"
"os"
"slices"
"sync"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/ethdb/leveldb"
"github.com/ethereum/go-ethereum/log"
)
const (
cachedLastBlocks = 1000 // last block of map pointers
cachedLvPointers = 1000 // first log value pointer of block pointers
cachedBaseRows = 100 // groups of base layer filter row data
cachedFilterMaps = 3 // complete filter maps (cached by map renderer)
cachedRenderSnapshots = 8 // saved map renderer data at block boundaries
)
// FilterMaps is the in-memory representation of the log index structure that is
// responsible for building and updating the index according to the canonical
// chain.
//
// Note that FilterMaps implements the same data structure as proposed in EIP-7745
// without the tree hashing and consensus changes:
// https://eips.ethereum.org/EIPS/eip-7745
type FilterMaps struct {
// If disabled is set, log indexing is fully disabled.
// This is configured by the --history.logs.disable Geth flag.
// We chose to implement disabling this way because it requires less special
// case logic in eth/filters.
disabled bool
closeCh chan struct{}
closeWg sync.WaitGroup
history uint64
exportFileName string
Params
db ethdb.KeyValueStore
// fields written by the indexer and read by matcher backend. Indexer can
// read them without a lock and write them under indexLock write lock.
// Matcher backend can read them under indexLock read lock.
indexLock sync.RWMutex
indexedRange filterMapsRange
indexedView *ChainView // always consistent with the log index
// also accessed by indexer and matcher backend but no locking needed.
filterMapCache *lru.Cache[uint32, filterMap]
lastBlockCache *lru.Cache[uint32, lastBlockOfMap]
lvPointerCache *lru.Cache[uint64, uint64]
baseRowsCache *lru.Cache[uint64, [][]uint32]
// the matchers set and the fields of FilterMapsMatcherBackend instances are
// read and written both by exported functions and the indexer.
// Note that if both indexLock and matchersLock needs to be locked then
// indexLock should be locked first.
matchersLock sync.Mutex
matchers map[*FilterMapsMatcherBackend]struct{}
// fields only accessed by the indexer (no mutex required).
renderSnapshots *lru.Cache[uint64, *renderedMap]
startedHeadIndex, startedTailIndex, startedTailUnindex bool
startedHeadIndexAt, startedTailIndexAt, startedTailUnindexAt time.Time
loggedHeadIndex, loggedTailIndex bool
lastLogHeadIndex, lastLogTailIndex time.Time
ptrHeadIndex, ptrTailIndex, ptrTailUnindexBlock uint64
ptrTailUnindexMap uint32
targetView *ChainView
matcherSyncRequest *FilterMapsMatcherBackend
finalBlock, lastFinal uint64
lastFinalEpoch uint32
stop bool
targetViewCh chan *ChainView
finalBlockCh chan uint64
blockProcessingCh chan bool
blockProcessing bool
matcherSyncCh chan *FilterMapsMatcherBackend
waitIdleCh chan chan bool
tailRenderer *mapRenderer
// test hooks
testDisableSnapshots, testSnapshotUsed bool
}
// filterMap is a full or partial in-memory representation of a filter map where
// rows are allowed to have a nil value meaning the row is not stored in the
// structure. Note that therefore a known empty row should be represented with
// a zero-length slice.
// It can be used as a memory cache or an overlay while preparing a batch of
// changes to the structure. In either case a nil value should be interpreted
// as transparent (uncached/unchanged).
type filterMap []FilterRow
// copy returns a copy of the given filter map. Note that the row slices are
// copied but their contents are not. This permits extending the rows further
// (which happens during map rendering) without affecting the validity of
// copies made for snapshots during rendering.
func (fm filterMap) copy() filterMap {
c := make(filterMap, len(fm))
copy(c, fm)
return c
}
// FilterRow encodes a single row of a filter map as a list of column indices.
// Note that the values are always stored in the same order as they were added
// and if the same column index is added twice, it is also stored twice.
// Order of column indices and potential duplications do not matter when searching
// for a value but leaving the original order makes reverting to a previous state
// simpler.
type FilterRow []uint32
// Equal returns true if the given filter rows are equivalent.
func (a FilterRow) Equal(b FilterRow) bool {
return slices.Equal(a, b)
}
// filterMapsRange describes the rendered range of filter maps and the range
// of fully rendered blocks.
type filterMapsRange struct {
initialized bool
headBlockIndexed bool
headBlockDelimiter uint64 // zero if afterLastIndexedBlock != targetBlockNumber
// if initialized then all maps are rendered between firstRenderedMap and
// afterLastRenderedMap-1
firstRenderedMap, afterLastRenderedMap uint32
// if tailPartialEpoch > 0 then maps between firstRenderedMap-mapsPerEpoch and
// firstRenderedMap-mapsPerEpoch+tailPartialEpoch-1 are rendered
tailPartialEpoch uint32
// if initialized then all log values belonging to blocks between
// firstIndexedBlock and afterLastIndexedBlock are fully rendered
// blockLvPointers are available between firstIndexedBlock and afterLastIndexedBlock-1
firstIndexedBlock, afterLastIndexedBlock uint64
}
// hasIndexedBlocks returns true if the range has at least one fully indexed block.
func (fmr *filterMapsRange) hasIndexedBlocks() bool {
return fmr.initialized && fmr.afterLastIndexedBlock > fmr.firstIndexedBlock
}
// lastBlockOfMap is used for caching the (number, id) pairs belonging to the
// last block of each map.
type lastBlockOfMap struct {
number uint64
id common.Hash
}
// Config contains the configuration options for NewFilterMaps.
type Config struct {
History uint64 // number of historical blocks to index
Disabled bool // disables indexing completely
// This option enables the checkpoint JSON file generator.
// If set, the given file will be updated with checkpoint information.
ExportFileName string
}
// NewFilterMaps creates a new FilterMaps and starts the indexer.
func NewFilterMaps(db ethdb.KeyValueStore, initView *ChainView, params Params, config Config) *FilterMaps {
rs, initialized, err := rawdb.ReadFilterMapsRange(db)
if err != nil {
log.Error("Error reading log index range", "error", err)
}
params.deriveFields()
f := &FilterMaps{
db: db,
closeCh: make(chan struct{}),
waitIdleCh: make(chan chan bool),
targetViewCh: make(chan *ChainView, 1),
finalBlockCh: make(chan uint64, 1),
blockProcessingCh: make(chan bool, 1),
history: config.History,
disabled: config.Disabled,
exportFileName: config.ExportFileName,
Params: params,
indexedRange: filterMapsRange{
initialized: initialized,
headBlockIndexed: rs.HeadBlockIndexed,
headBlockDelimiter: rs.HeadBlockDelimiter,
firstIndexedBlock: rs.FirstIndexedBlock,
afterLastIndexedBlock: rs.AfterLastIndexedBlock,
firstRenderedMap: rs.FirstRenderedMap,
afterLastRenderedMap: rs.AfterLastRenderedMap,
tailPartialEpoch: rs.TailPartialEpoch,
},
matcherSyncCh: make(chan *FilterMapsMatcherBackend),
matchers: make(map[*FilterMapsMatcherBackend]struct{}),
filterMapCache: lru.NewCache[uint32, filterMap](cachedFilterMaps),
lastBlockCache: lru.NewCache[uint32, lastBlockOfMap](cachedLastBlocks),
lvPointerCache: lru.NewCache[uint64, uint64](cachedLvPointers),
baseRowsCache: lru.NewCache[uint64, [][]uint32](cachedBaseRows),
renderSnapshots: lru.NewCache[uint64, *renderedMap](cachedRenderSnapshots),
}
// Set initial indexer target.
f.targetView = initView
if f.indexedRange.initialized {
f.indexedView = f.initChainView(f.targetView)
f.indexedRange.headBlockIndexed = f.indexedRange.afterLastIndexedBlock == f.indexedView.headNumber+1
if !f.indexedRange.headBlockIndexed {
f.indexedRange.headBlockDelimiter = 0
}
}
if f.indexedRange.hasIndexedBlocks() {
log.Info("Initialized log indexer",
"first block", f.indexedRange.firstIndexedBlock, "last block", f.indexedRange.afterLastIndexedBlock-1,
"first map", f.indexedRange.firstRenderedMap, "last map", f.indexedRange.afterLastRenderedMap-1,
"head indexed", f.indexedRange.headBlockIndexed)
}
return f
}
// Start starts the indexer.
func (f *FilterMaps) Start() {
if !f.testDisableSnapshots && f.indexedRange.initialized && f.indexedRange.headBlockIndexed &&
f.indexedRange.firstRenderedMap < f.indexedRange.afterLastRenderedMap {
// previous target head rendered; load last map as snapshot
if err := f.loadHeadSnapshot(); err != nil {
log.Error("Could not load head filter map snapshot", "error", err)
}
}
f.closeWg.Add(1)
go f.indexerLoop()
}
// Stop ensures that the indexer is fully stopped before returning.
func (f *FilterMaps) Stop() {
close(f.closeCh)
f.closeWg.Wait()
}
// initChainView returns a chain view consistent with both the current target
// view and the current state of the log index as found in the database, based
// on the last block of stored maps.
// Note that the returned view might be shorter than the existing index if
// the latest maps are not consistent with targetView.
func (f *FilterMaps) initChainView(chainView *ChainView) *ChainView {
mapIndex := f.indexedRange.afterLastRenderedMap
for {
var ok bool
mapIndex, ok = f.lastMapBoundaryBefore(mapIndex)
if !ok {
break
}
lastBlockNumber, lastBlockId, err := f.getLastBlockOfMap(mapIndex)
if err != nil {
log.Error("Could not initialize indexed chain view", "error", err)
break
}
if lastBlockNumber <= chainView.headNumber && chainView.getBlockId(lastBlockNumber) == lastBlockId {
return chainView.limitedView(lastBlockNumber)
}
}
return chainView.limitedView(0)
}
// reset un-initializes the FilterMaps structure and removes all related data from
// the database. The function returns true if everything was successfully removed.
func (f *FilterMaps) reset() bool {
f.indexLock.Lock()
f.indexedRange = filterMapsRange{}
f.indexedView = nil
f.filterMapCache.Purge()
f.renderSnapshots.Purge()
f.lastBlockCache.Purge()
f.lvPointerCache.Purge()
f.baseRowsCache.Purge()
f.indexLock.Unlock()
// deleting the range first ensures that resetDb will be called again at next
// startup and any leftover data will be removed even if it cannot finish now.
rawdb.DeleteFilterMapsRange(f.db)
return f.removeDbWithPrefix([]byte(rawdb.FilterMapsPrefix), "Resetting log index database")
}
// init initializes an empty log index according to the current targetView.
func (f *FilterMaps) init() error {
f.indexLock.Lock()
defer f.indexLock.Unlock()
var bestIdx, bestLen int
for idx, checkpointList := range checkpoints {
// binary search for the last matching epoch head
min, max := 0, len(checkpointList)
for min < max {
mid := (min + max + 1) / 2
cp := checkpointList[mid-1]
if cp.BlockNumber <= f.targetView.headNumber && f.targetView.getBlockId(cp.BlockNumber) == cp.BlockId {
min = mid
} else {
max = mid - 1
}
}
if max > bestLen {
bestIdx, bestLen = idx, max
}
}
batch := f.db.NewBatch()
for epoch := range bestLen {
cp := checkpoints[bestIdx][epoch]
f.storeLastBlockOfMap(batch, (uint32(epoch+1)<<f.logMapsPerEpoch)-1, cp.BlockNumber, cp.BlockId)
f.storeBlockLvPointer(batch, cp.BlockNumber, cp.FirstIndex)
}
fmr := filterMapsRange{
initialized: true,
}
if bestLen > 0 {
cp := checkpoints[bestIdx][bestLen-1]
fmr.firstIndexedBlock = cp.BlockNumber + 1
fmr.afterLastIndexedBlock = cp.BlockNumber + 1
fmr.firstRenderedMap = uint32(bestLen) << f.logMapsPerEpoch
fmr.afterLastRenderedMap = uint32(bestLen) << f.logMapsPerEpoch
}
f.setRange(batch, f.targetView, fmr)
return batch.Write()
}
// removeDbWithPrefix removes data with the given prefix from the database and
// returns true if everything was successfully removed.
func (f *FilterMaps) removeDbWithPrefix(prefix []byte, action string) bool {
it := f.db.NewIterator(prefix, nil)
hasData := it.Next()
it.Release()
if !hasData {
return true
}
end := bytes.Clone(prefix)
end[len(end)-1]++
start := time.Now()
var retry bool
for {
err := f.db.DeleteRange(prefix, end)
if err == nil {
log.Info(action+" finished", "elapsed", time.Since(start))
return true
}
if err != leveldb.ErrTooManyKeys {
log.Error(action+" failed", "error", err)
return false
}
select {
case <-f.closeCh:
return false
default:
}
if !retry {
log.Info(action + " in progress...")
retry = true
}
}
}
// setRange updates the indexed chain view and covered range and also adds the
// changes to the given batch.
// Note that this function assumes that the index write lock is being held.
func (f *FilterMaps) setRange(batch ethdb.KeyValueWriter, newView *ChainView, newRange filterMapsRange) {
f.indexedView = newView
f.indexedRange = newRange
f.updateMatchersValidRange()
if newRange.initialized {
rs := rawdb.FilterMapsRange{
HeadBlockIndexed: newRange.headBlockIndexed,
HeadBlockDelimiter: newRange.headBlockDelimiter,
FirstIndexedBlock: newRange.firstIndexedBlock,
AfterLastIndexedBlock: newRange.afterLastIndexedBlock,
FirstRenderedMap: newRange.firstRenderedMap,
AfterLastRenderedMap: newRange.afterLastRenderedMap,
TailPartialEpoch: newRange.tailPartialEpoch,
}
rawdb.WriteFilterMapsRange(batch, rs)
} else {
rawdb.DeleteFilterMapsRange(batch)
}
}
// getLogByLvIndex returns the log at the given log value index. If the index does
// not point to the first log value entry of a log then no log and no error are
// returned as this can happen when the log value index was a false positive.
// Note that this function assumes that the log index structure is consistent
// with the canonical chain at the point where the given log value index points.
// If this is not the case then an invalid result or an error may be returned.
// Note that this function assumes that the indexer read lock is being held when
// called from outside the indexerLoop goroutine.
func (f *FilterMaps) getLogByLvIndex(lvIndex uint64) (*types.Log, error) {
mapIndex := uint32(lvIndex >> f.logValuesPerMap)
if mapIndex < f.indexedRange.firstRenderedMap || mapIndex >= f.indexedRange.afterLastRenderedMap {
return nil, nil
}
// find possible block range based on map to block pointers
lastBlockNumber, _, err := f.getLastBlockOfMap(mapIndex)
if err != nil {
return nil, fmt.Errorf("failed to retrieve last block of map %d containing searched log value index %d: %v", mapIndex, lvIndex, err)
}
var firstBlockNumber uint64
if mapIndex > 0 {
firstBlockNumber, _, err = f.getLastBlockOfMap(mapIndex - 1)
if err != nil {
return nil, fmt.Errorf("failed to retrieve last block of map %d before searched log value index %d: %v", mapIndex, lvIndex, err)
}
}
if firstBlockNumber < f.indexedRange.firstIndexedBlock {
firstBlockNumber = f.indexedRange.firstIndexedBlock
}
// find block with binary search based on block to log value index pointers
for firstBlockNumber < lastBlockNumber {
midBlockNumber := (firstBlockNumber + lastBlockNumber + 1) / 2
midLvPointer, err := f.getBlockLvPointer(midBlockNumber)
if err != nil {
return nil, fmt.Errorf("failed to retrieve log value pointer of block %d while binary searching log value index %d: %v", midBlockNumber, lvIndex, err)
}
if lvIndex < midLvPointer {
lastBlockNumber = midBlockNumber - 1
} else {
firstBlockNumber = midBlockNumber
}
}
// get block receipts
receipts := f.indexedView.getReceipts(firstBlockNumber)
if receipts == nil {
return nil, fmt.Errorf("failed to retrieve receipts for block %d containing searched log value index %d: %v", firstBlockNumber, lvIndex, err)
}
lvPointer, err := f.getBlockLvPointer(firstBlockNumber)
if err != nil {
return nil, fmt.Errorf("failed to retrieve log value pointer of block %d containing searched log value index %d: %v", firstBlockNumber, lvIndex, err)
}
// iterate through receipts to find the exact log starting at lvIndex
for _, receipt := range receipts {
for _, log := range receipt.Logs {
if lvPointer > lvIndex {
// lvIndex does not point to the first log value (address value)
// generated by a log as true matches should always do, so it
// is considered a false positive (no log and no error returned).
return nil, nil
}
if lvPointer == lvIndex {
return log, nil // potential match
}
lvPointer += uint64(len(log.Topics) + 1)
}
}
return nil, nil
}
// getFilterMap fetches an entire filter map from the database.
func (f *FilterMaps) getFilterMap(mapIndex uint32) (filterMap, error) {
if fm, ok := f.filterMapCache.Get(mapIndex); ok {
return fm, nil
}
fm := make(filterMap, f.mapHeight)
for rowIndex := range fm {
var err error
fm[rowIndex], err = f.getFilterMapRow(mapIndex, uint32(rowIndex), false)
if err != nil {
return nil, fmt.Errorf("failed to load filter map %d from database: %v", mapIndex, err)
}
}
f.filterMapCache.Add(mapIndex, fm)
return fm, nil
}
// getFilterMapRow fetches the given filter map row. If baseLayerOnly is true
// then only the first baseRowLength entries are returned.
func (f *FilterMaps) getFilterMapRow(mapIndex, rowIndex uint32, baseLayerOnly bool) (FilterRow, error) {
baseMapRowIndex := f.mapRowIndex(mapIndex&-f.baseRowGroupLength, rowIndex)
baseRows, ok := f.baseRowsCache.Get(baseMapRowIndex)
if !ok {
var err error
baseRows, err = rawdb.ReadFilterMapBaseRows(f.db, baseMapRowIndex, f.baseRowGroupLength, f.logMapWidth)
if err != nil {
return nil, fmt.Errorf("failed to retrieve filter map %d base rows %d: %v", mapIndex, rowIndex, err)
}
f.baseRowsCache.Add(baseMapRowIndex, baseRows)
}
baseRow := baseRows[mapIndex&(f.baseRowGroupLength-1)]
if baseLayerOnly {
return baseRow, nil
}
extRow, err := rawdb.ReadFilterMapExtRow(f.db, f.mapRowIndex(mapIndex, rowIndex), f.logMapWidth)
if err != nil {
return nil, fmt.Errorf("failed to retrieve filter map %d extended row %d: %v", mapIndex, rowIndex, err)
}
return FilterRow(append(baseRow, extRow...)), nil
}
// storeFilterMapRows stores a set of filter map rows at the corresponding map
// indices and a shared row index.
func (f *FilterMaps) storeFilterMapRows(batch ethdb.Batch, mapIndices []uint32, rowIndex uint32, rows []FilterRow) error {
for len(mapIndices) > 0 {
baseMapIndex := mapIndices[0] & -f.baseRowGroupLength
groupLength := 1
for groupLength < len(mapIndices) && mapIndices[groupLength]&-f.baseRowGroupLength == baseMapIndex {
groupLength++
}
if err := f.storeFilterMapRowsOfGroup(batch, mapIndices[:groupLength], rowIndex, rows[:groupLength]); err != nil {
return err
}
mapIndices, rows = mapIndices[groupLength:], rows[groupLength:]
}
return nil
}
// storeFilterMapRowsOfGroup stores a set of filter map rows at map indices
// belonging to the same base row group.
func (f *FilterMaps) storeFilterMapRowsOfGroup(batch ethdb.Batch, mapIndices []uint32, rowIndex uint32, rows []FilterRow) error {
baseMapIndex := mapIndices[0] & -f.baseRowGroupLength
baseMapRowIndex := f.mapRowIndex(baseMapIndex, rowIndex)
var baseRows [][]uint32
if uint32(len(mapIndices)) != f.baseRowGroupLength { // skip base rows read if all rows are replaced
var ok bool
baseRows, ok = f.baseRowsCache.Get(baseMapRowIndex)
if !ok {
var err error
baseRows, err = rawdb.ReadFilterMapBaseRows(f.db, baseMapRowIndex, f.baseRowGroupLength, f.logMapWidth)
if err != nil {
return fmt.Errorf("failed to retrieve filter map %d base rows %d for modification: %v", mapIndices[0]&-f.baseRowGroupLength, rowIndex, err)
}
}
} else {
baseRows = make([][]uint32, f.baseRowGroupLength)
}
for i, mapIndex := range mapIndices {
if mapIndex&-f.baseRowGroupLength != baseMapIndex {
panic("mapIndices are not in the same base row group")
}
baseRow := []uint32(rows[i])
var extRow FilterRow
if uint32(len(rows[i])) > f.baseRowLength {
extRow = baseRow[f.baseRowLength:]
baseRow = baseRow[:f.baseRowLength]
}
baseRows[mapIndex&(f.baseRowGroupLength-1)] = baseRow
rawdb.WriteFilterMapExtRow(batch, f.mapRowIndex(mapIndex, rowIndex), extRow, f.logMapWidth)
}
f.baseRowsCache.Add(baseMapRowIndex, baseRows)
rawdb.WriteFilterMapBaseRows(batch, baseMapRowIndex, baseRows, f.logMapWidth)
return nil
}
// mapRowIndex calculates the unified storage index where the given row of the
// given map is stored. Note that this indexing scheme is the same as the one
// proposed in EIP-7745 for tree-hashing the filter map structure and for the
// same data proximity reasons it is also suitable for database representation.
// See also:
// https://eips.ethereum.org/EIPS/eip-7745#hash-tree-structure
func (f *FilterMaps) mapRowIndex(mapIndex, rowIndex uint32) uint64 {
epochIndex, mapSubIndex := mapIndex>>f.logMapsPerEpoch, mapIndex&(f.mapsPerEpoch-1)
return (uint64(epochIndex)<<f.logMapHeight+uint64(rowIndex))<<f.logMapsPerEpoch + uint64(mapSubIndex)
}
// getBlockLvPointer returns the starting log value index where the log values
// generated by the given block are located. If blockNumber is beyond the current
// head then the first unoccupied log value index is returned.
// Note that this function assumes that the indexer read lock is being held when
// called from outside the indexerLoop goroutine.
func (f *FilterMaps) getBlockLvPointer(blockNumber uint64) (uint64, error) {
if blockNumber >= f.indexedRange.afterLastIndexedBlock && f.indexedRange.headBlockIndexed {
return f.indexedRange.headBlockDelimiter, nil
}
if lvPointer, ok := f.lvPointerCache.Get(blockNumber); ok {
return lvPointer, nil
}
lvPointer, err := rawdb.ReadBlockLvPointer(f.db, blockNumber)
if err != nil {
return 0, fmt.Errorf("failed to retrieve log value pointer of block %d: %v", blockNumber, err)
}
f.lvPointerCache.Add(blockNumber, lvPointer)
return lvPointer, nil
}
// storeBlockLvPointer stores the starting log value index where the log values
// generated by the given block are located.
func (f *FilterMaps) storeBlockLvPointer(batch ethdb.Batch, blockNumber, lvPointer uint64) {
f.lvPointerCache.Add(blockNumber, lvPointer)
rawdb.WriteBlockLvPointer(batch, blockNumber, lvPointer)
}
// deleteBlockLvPointer deletes the starting log value index where the log values
// generated by the given block are located.
func (f *FilterMaps) deleteBlockLvPointer(batch ethdb.Batch, blockNumber uint64) {
f.lvPointerCache.Remove(blockNumber)
rawdb.DeleteBlockLvPointer(batch, blockNumber)
}
// getLastBlockOfMap returns the number and id of the block that generated the
// last log value entry of the given map.
func (f *FilterMaps) getLastBlockOfMap(mapIndex uint32) (uint64, common.Hash, error) {
if lastBlock, ok := f.lastBlockCache.Get(mapIndex); ok {
return lastBlock.number, lastBlock.id, nil
}
number, id, err := rawdb.ReadFilterMapLastBlock(f.db, mapIndex)
if err != nil {
return 0, common.Hash{}, fmt.Errorf("failed to retrieve last block of map %d: %v", mapIndex, err)
}
f.lastBlockCache.Add(mapIndex, lastBlockOfMap{number: number, id: id})
return number, id, nil
}
// storeLastBlockOfMap stores the number of the block that generated the last
// log value entry of the given map.
func (f *FilterMaps) storeLastBlockOfMap(batch ethdb.Batch, mapIndex uint32, number uint64, id common.Hash) {
f.lastBlockCache.Add(mapIndex, lastBlockOfMap{number: number, id: id})
rawdb.WriteFilterMapLastBlock(batch, mapIndex, number, id)
}
// deleteLastBlockOfMap deletes the number of the block that generated the last
// log value entry of the given map.
func (f *FilterMaps) deleteLastBlockOfMap(batch ethdb.Batch, mapIndex uint32) {
f.lastBlockCache.Remove(mapIndex)
rawdb.DeleteFilterMapLastBlock(batch, mapIndex)
}
// deleteTailEpoch deletes index data from the earliest, either fully or partially
// indexed epoch. The last block pointer for the last map of the epoch and the
// corresponding block log value pointer are retained as these are always assumed
// to be available for each epoch.
func (f *FilterMaps) deleteTailEpoch(epoch uint32) error {
f.indexLock.Lock()
defer f.indexLock.Unlock()
firstMap := epoch << f.logMapsPerEpoch
lastBlock, _, err := f.getLastBlockOfMap(firstMap + f.mapsPerEpoch - 1)
if err != nil {
return fmt.Errorf("failed to retrieve last block of deleted epoch %d: %v", epoch, err)
}
var firstBlock uint64
if epoch > 0 {
firstBlock, _, err = f.getLastBlockOfMap(firstMap - 1)
if err != nil {
return fmt.Errorf("failed to retrieve last block before deleted epoch %d: %v", epoch, err)
}
firstBlock++
}
fmr := f.indexedRange
if f.indexedRange.firstRenderedMap == firstMap &&
f.indexedRange.afterLastRenderedMap > firstMap+f.mapsPerEpoch &&
f.indexedRange.tailPartialEpoch == 0 {
fmr.firstRenderedMap = firstMap + f.mapsPerEpoch
fmr.firstIndexedBlock = lastBlock + 1
} else if f.indexedRange.firstRenderedMap == firstMap+f.mapsPerEpoch {
fmr.tailPartialEpoch = 0
} else {
return errors.New("invalid tail epoch number")
}
f.setRange(f.db, f.indexedView, fmr)
rawdb.DeleteFilterMapRows(f.db, f.mapRowIndex(firstMap, 0), f.mapRowIndex(firstMap+f.mapsPerEpoch, 0))
for mapIndex := firstMap; mapIndex < firstMap+f.mapsPerEpoch; mapIndex++ {
f.filterMapCache.Remove(mapIndex)
}
rawdb.DeleteFilterMapLastBlocks(f.db, firstMap, firstMap+f.mapsPerEpoch-1) // keep last enrty
for mapIndex := firstMap; mapIndex < firstMap+f.mapsPerEpoch-1; mapIndex++ {
f.lastBlockCache.Remove(mapIndex)
}
rawdb.DeleteBlockLvPointers(f.db, firstBlock, lastBlock) // keep last enrty
for blockNumber := firstBlock; blockNumber < lastBlock; blockNumber++ {
f.lvPointerCache.Remove(blockNumber)
}
return nil
}
// exportCheckpoints exports epoch checkpoints in the format used by checkpoints.go.
func (f *FilterMaps) exportCheckpoints() {
finalLvPtr, err := f.getBlockLvPointer(f.finalBlock + 1)
if err != nil {
log.Error("Error fetching log value pointer of finalized block", "block", f.finalBlock, "error", err)
return
}
epochCount := uint32(finalLvPtr >> (f.logValuesPerMap + f.logMapsPerEpoch))
if epochCount == f.lastFinalEpoch {
return
}
w, err := os.Create(f.exportFileName)
if err != nil {
log.Error("Error creating checkpoint export file", "name", f.exportFileName, "error", err)
return
}
defer w.Close()
log.Info("Exporting log index checkpoints", "epochs", epochCount, "file", f.exportFileName)
w.WriteString("[\n")
comma := ","
for epoch := uint32(0); epoch < epochCount; epoch++ {
lastBlock, lastBlockId, err := f.getLastBlockOfMap((epoch+1)<<f.logMapsPerEpoch - 1)
if err != nil {
log.Error("Error fetching last block of epoch", "epoch", epoch, "error", err)
return
}
lvPtr, err := f.getBlockLvPointer(lastBlock)
if err != nil {
log.Error("Error fetching log value pointer of last block", "block", lastBlock, "error", err)
return
}
if epoch == epochCount-1 {
comma = ""
}
w.WriteString(fmt.Sprintf("{\"blockNumber\": %d, \"blockId\": \"0x%064x\", \"firstIndex\": %d}%s\n", lastBlock, lastBlockId, lvPtr, comma))
}
w.WriteString("]\n")
f.lastFinalEpoch = epochCount
}

395
core/filtermaps/indexer.go Normal file
View file

@ -0,0 +1,395 @@
// Copyright 2024 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 <http://www.gnu.org/licenses/>.
package filtermaps
import (
"math"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
)
const (
logFrequency = time.Second * 20 // log info frequency during long indexing/unindexing process
headLogDelay = time.Second // head indexing log info delay (do not log if finished faster)
)
// updateLoop initializes and updates the log index structure according to the
// current targetView.
func (f *FilterMaps) indexerLoop() {
defer f.closeWg.Done()
if f.disabled {
f.reset()
return
}
log.Info("Started log indexer")
for !f.stop {
if !f.indexedRange.initialized {
if err := f.init(); err != nil {
log.Error("Error initializing log index", "error", err)
f.waitForEvent()
continue
}
}
if !f.targetHeadIndexed() {
if !f.tryIndexHead() {
f.waitForEvent()
}
} else {
if f.finalBlock != f.lastFinal {
if f.exportFileName != "" {
f.exportCheckpoints()
}
f.lastFinal = f.finalBlock
}
if f.tryIndexTail() && f.tryUnindexTail() {
f.waitForEvent()
}
}
}
}
// SetTargetView sets a new target chain view for the indexer to render.
// Note that SetTargetView never blocks.
func (f *FilterMaps) SetTargetView(targetView *ChainView) {
if targetView == nil {
panic("nil targetView")
}
for {
select {
case <-f.targetViewCh:
case f.targetViewCh <- targetView:
return
}
}
}
// SetFinalBlock sets the finalized block number used for exporting checkpoints.
// Note that SetFinalBlock never blocks.
func (f *FilterMaps) SetFinalBlock(finalBlock uint64) {
for {
select {
case <-f.finalBlockCh:
case f.finalBlockCh <- finalBlock:
return
}
}
}
// SetBlockProcessing sets the block processing flag that temporarily suspends
// log index rendering.
// Note that SetBlockProcessing never blocks.
func (f *FilterMaps) SetBlockProcessing(blockProcessing bool) {
for {
select {
case <-f.blockProcessingCh:
case f.blockProcessingCh <- blockProcessing:
return
}
}
}
// WaitIdle blocks until the indexer is in an idle state while synced up to the
// latest targetView.
func (f *FilterMaps) WaitIdle() {
if f.disabled {
f.closeWg.Wait()
return
}
for {
ch := make(chan bool)
f.waitIdleCh <- ch
if <-ch {
return
}
}
}
// waitForEvent blocks until an event happens that the indexer might react to.
func (f *FilterMaps) waitForEvent() {
for !f.stop && (f.blockProcessing || f.targetHeadIndexed()) {
f.processSingleEvent(true)
}
}
// processEvents processes all events, blocking only if a block processing is
// happening and indexing should be suspended.
func (f *FilterMaps) processEvents() {
for !f.stop && f.processSingleEvent(f.blockProcessing) {
}
}
// processSingleEvent processes a single event either in a blocking or
// non-blocking manner.
func (f *FilterMaps) processSingleEvent(blocking bool) bool {
if f.matcherSyncRequest != nil && f.targetHeadIndexed() {
f.matcherSyncRequest.synced()
f.matcherSyncRequest = nil
}
if blocking {
select {
case targetView := <-f.targetViewCh:
f.setTargetView(targetView)
case f.finalBlock = <-f.finalBlockCh:
case f.matcherSyncRequest = <-f.matcherSyncCh:
case f.blockProcessing = <-f.blockProcessingCh:
case <-f.closeCh:
f.stop = true
case ch := <-f.waitIdleCh:
select {
case targetView := <-f.targetViewCh:
f.setTargetView(targetView)
default:
}
ch <- !f.blockProcessing && f.targetHeadIndexed()
}
} else {
select {
case targetView := <-f.targetViewCh:
f.setTargetView(targetView)
case f.finalBlock = <-f.finalBlockCh:
case f.matcherSyncRequest = <-f.matcherSyncCh:
case f.blockProcessing = <-f.blockProcessingCh:
case <-f.closeCh:
f.stop = true
default:
return false
}
}
return true
}
// setTargetView updates the target chain view of the iterator.
func (f *FilterMaps) setTargetView(targetView *ChainView) {
f.targetView = targetView
}
// tryIndexHead tries to render head maps according to the current targetView
// and returns true if successful.
func (f *FilterMaps) tryIndexHead() bool {
headRenderer, err := f.renderMapsBefore(math.MaxUint32)
if err != nil {
log.Error("Error creating log index head renderer", "error", err)
return false
}
if headRenderer == nil {
return true
}
if !f.startedHeadIndex {
f.lastLogHeadIndex = time.Now()
f.startedHeadIndexAt = f.lastLogHeadIndex
f.startedHeadIndex = true
f.ptrHeadIndex = f.indexedRange.afterLastIndexedBlock
}
if _, err := headRenderer.run(func() bool {
f.processEvents()
return f.stop
}, func() {
f.tryUnindexTail()
if f.indexedRange.hasIndexedBlocks() && f.indexedRange.afterLastIndexedBlock >= f.ptrHeadIndex &&
((!f.loggedHeadIndex && time.Since(f.startedHeadIndexAt) > headLogDelay) ||
time.Since(f.lastLogHeadIndex) > logFrequency) {
log.Info("Log index head rendering in progress",
"first block", f.indexedRange.firstIndexedBlock, "last block", f.indexedRange.afterLastIndexedBlock-1,
"processed", f.indexedRange.afterLastIndexedBlock-f.ptrHeadIndex,
"remaining", f.indexedView.headNumber+1-f.indexedRange.afterLastIndexedBlock,
"elapsed", common.PrettyDuration(time.Since(f.startedHeadIndexAt)))
f.loggedHeadIndex = true
f.lastLogHeadIndex = time.Now()
}
}); err != nil {
log.Error("Log index head rendering failed", "error", err)
return false
}
if f.loggedHeadIndex {
log.Info("Log index head rendering finished",
"first block", f.indexedRange.firstIndexedBlock, "last block", f.indexedRange.afterLastIndexedBlock-1,
"processed", f.indexedRange.afterLastIndexedBlock-f.ptrHeadIndex,
"elapsed", common.PrettyDuration(time.Since(f.startedHeadIndexAt)))
}
f.loggedHeadIndex, f.startedHeadIndex = false, false
return true
}
// tryIndexTail tries to render tail epochs until the tail target block is
// indexed and returns true if successful.
// Note that tail indexing is only started if the log index head is fully
// rendered according to targetView and is suspended as soon as the targetView
// is changed.
func (f *FilterMaps) tryIndexTail() bool {
for firstEpoch := f.indexedRange.firstRenderedMap >> f.logMapsPerEpoch; firstEpoch > 0 && f.needTailEpoch(firstEpoch-1); {
f.processEvents()
if f.stop || !f.targetHeadIndexed() {
return false
}
// resume process if tail rendering was interrupted because of head rendering
tailRenderer := f.tailRenderer
f.tailRenderer = nil
if tailRenderer != nil && tailRenderer.afterLastMap != f.indexedRange.firstRenderedMap {
tailRenderer = nil
}
if tailRenderer == nil {
var err error
tailRenderer, err = f.renderMapsBefore(f.indexedRange.firstRenderedMap)
if err != nil {
log.Error("Error creating log index tail renderer", "error", err)
return false
}
}
if tailRenderer == nil {
return true
}
if !f.startedTailIndex {
f.lastLogTailIndex = time.Now()
f.startedTailIndexAt = f.lastLogTailIndex
f.startedTailIndex = true
f.ptrTailIndex = f.indexedRange.firstIndexedBlock - f.tailPartialBlocks()
}
done, err := tailRenderer.run(func() bool {
f.processEvents()
return f.stop || !f.targetHeadIndexed()
}, func() {
tpb, ttb := f.tailPartialBlocks(), f.tailTargetBlock()
remaining := uint64(1)
if f.indexedRange.firstIndexedBlock > ttb+tpb {
remaining = f.indexedRange.firstIndexedBlock - ttb - tpb
}
if f.indexedRange.hasIndexedBlocks() && f.ptrTailIndex >= f.indexedRange.firstIndexedBlock &&
(!f.loggedTailIndex || time.Since(f.lastLogTailIndex) > logFrequency) {
log.Info("Log index tail rendering in progress",
"first block", f.indexedRange.firstIndexedBlock, "last block", f.indexedRange.afterLastIndexedBlock-1,
"processed", f.ptrTailIndex-f.indexedRange.firstIndexedBlock+tpb,
"remaining", remaining,
"next tail epoch percentage", f.indexedRange.tailPartialEpoch*100/f.mapsPerEpoch,
"elapsed", common.PrettyDuration(time.Since(f.startedTailIndexAt)))
f.loggedTailIndex = true
f.lastLogTailIndex = time.Now()
}
})
if err != nil {
log.Error("Log index tail rendering failed", "error", err)
}
if !done {
f.tailRenderer = tailRenderer // only keep tail renderer if interrupted by stopCb
return false
}
}
if f.loggedTailIndex {
log.Info("Log index tail rendering finished",
"first block", f.indexedRange.firstIndexedBlock, "last block", f.indexedRange.afterLastIndexedBlock-1,
"processed", f.ptrTailIndex-f.indexedRange.firstIndexedBlock,
"elapsed", common.PrettyDuration(time.Since(f.startedTailIndexAt)))
f.loggedTailIndex = false
}
return true
}
// tryUnindexTail removes entire epochs of log index data as long as the first
// fully indexed block is at least as old as the tail target.
// Note that unindexing is very quick as it only removes continuous ranges of
// data from the database and is also called while running head indexing.
func (f *FilterMaps) tryUnindexTail() bool {
for {
firstEpoch := (f.indexedRange.firstRenderedMap - f.indexedRange.tailPartialEpoch) >> f.logMapsPerEpoch
if f.needTailEpoch(firstEpoch) {
break
}
f.processEvents()
if f.stop {
return false
}
if !f.startedTailUnindex {
f.startedTailUnindexAt = time.Now()
f.startedTailUnindex = true
f.ptrTailUnindexMap = f.indexedRange.firstRenderedMap - f.indexedRange.tailPartialEpoch
f.ptrTailUnindexBlock = f.indexedRange.firstIndexedBlock - f.tailPartialBlocks()
}
if err := f.deleteTailEpoch(firstEpoch); err != nil {
log.Error("Log index tail epoch unindexing failed", "error", err)
return false
}
}
if f.startedTailUnindex {
log.Info("Log index tail unindexing finished",
"first block", f.indexedRange.firstIndexedBlock, "last block", f.indexedRange.afterLastIndexedBlock-1,
"removed maps", f.indexedRange.firstRenderedMap-f.ptrTailUnindexMap,
"removed blocks", f.indexedRange.firstIndexedBlock-f.tailPartialBlocks()-f.ptrTailUnindexBlock,
"elapsed", common.PrettyDuration(time.Since(f.startedTailUnindexAt)))
f.startedTailUnindex = false
}
return true
}
// needTailEpoch returns true if the given tail epoch needs to be kept
// according to the current tail target, false if it can be removed.
func (f *FilterMaps) needTailEpoch(epoch uint32) bool {
firstEpoch := f.indexedRange.firstRenderedMap >> f.logMapsPerEpoch
if epoch > firstEpoch {
return true
}
if epoch+1 < firstEpoch {
return false
}
tailTarget := f.tailTargetBlock()
if tailTarget < f.indexedRange.firstIndexedBlock {
return true
}
tailLvIndex, err := f.getBlockLvPointer(tailTarget)
if err != nil {
log.Error("Could not get log value index of tail block", "error", err)
return true
}
return uint64(epoch+1)<<(f.logValuesPerMap+f.logMapsPerEpoch) >= tailLvIndex
}
// tailTargetBlock returns the target value for the tail block number according
// to the log history parameter and the current index head.
func (f *FilterMaps) tailTargetBlock() uint64 {
if f.history == 0 || f.indexedView.headNumber < f.history {
return 0
}
return f.indexedView.headNumber + 1 - f.history
}
// tailPartialBlocks returns the number of rendered blocks in the partially
// rendered next tail epoch.
func (f *FilterMaps) tailPartialBlocks() uint64 {
if f.indexedRange.tailPartialEpoch == 0 {
return 0
}
end, _, err := f.getLastBlockOfMap(f.indexedRange.firstRenderedMap - f.mapsPerEpoch + f.indexedRange.tailPartialEpoch - 1)
if err != nil {
log.Error("Error fetching last block of map", "mapIndex", f.indexedRange.firstRenderedMap-f.mapsPerEpoch+f.indexedRange.tailPartialEpoch-1, "error", err)
}
var start uint64
if f.indexedRange.firstRenderedMap-f.mapsPerEpoch > 0 {
start, _, err = f.getLastBlockOfMap(f.indexedRange.firstRenderedMap - f.mapsPerEpoch - 1)
if err != nil {
log.Error("Error fetching last block of map", "mapIndex", f.indexedRange.firstRenderedMap-f.mapsPerEpoch-1, "error", err)
}
}
return end - start
}
// targetHeadIndexed returns true if the current log index is consistent with
// targetView with its head block fully rendered.
func (f *FilterMaps) targetHeadIndexed() bool {
return equalViews(f.targetView, f.indexedView) && f.indexedRange.headBlockIndexed
}

View file

@ -0,0 +1,444 @@
// Copyright 2024 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 <http://www.gnu.org/licenses/>.
package filtermaps
import (
crand "crypto/rand"
"crypto/sha256"
"math/big"
"math/rand"
"sync"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/params"
)
var testParams = Params{
logMapHeight: 2,
logMapWidth: 24,
logMapsPerEpoch: 4,
logValuesPerMap: 4,
baseRowGroupLength: 4,
baseRowLengthRatio: 2,
logLayerDiff: 2,
}
func TestIndexerRandomRange(t *testing.T) {
ts := newTestSetup(t)
defer ts.close()
forks := make([][]common.Hash, 10)
ts.chain.addBlocks(1000, 5, 2, 4, false) // 51 log values per block
for i := range forks {
if i != 0 {
forkBlock := rand.Intn(1000)
ts.chain.setHead(forkBlock)
ts.chain.addBlocks(1000-forkBlock, 5, 2, 4, false) // 51 log values per block
}
forks[i] = ts.chain.getCanonicalChain()
}
lvPerBlock := uint64(51)
ts.setHistory(0, false)
var (
history int
noHistory bool
fork, head = len(forks) - 1, 1000
checkSnapshot bool
)
ts.fm.WaitIdle()
for i := 0; i < 200; i++ {
switch rand.Intn(3) {
case 0:
// change history settings
switch rand.Intn(10) {
case 0:
history, noHistory = 0, false
case 1:
history, noHistory = 0, true
default:
history, noHistory = rand.Intn(1000)+1, false
}
ts.testDisableSnapshots = rand.Intn(2) == 0
ts.setHistory(uint64(history), noHistory)
case 1:
// change head to random position of random fork
fork, head = rand.Intn(len(forks)), rand.Intn(1001)
ts.chain.setCanonicalChain(forks[fork][:head+1])
case 2:
if head < 1000 {
checkSnapshot = !noHistory && head != 0 // no snapshot generated for block 0
// add blocks after the current head
head += rand.Intn(1000-head) + 1
ts.fm.testSnapshotUsed = false
ts.chain.setCanonicalChain(forks[fork][:head+1])
}
}
ts.fm.WaitIdle()
if checkSnapshot {
if ts.fm.testSnapshotUsed == ts.fm.testDisableSnapshots {
ts.t.Fatalf("Invalid snapshot used state after head extension (used: %v, disabled: %v)", ts.fm.testSnapshotUsed, ts.fm.testDisableSnapshots)
}
checkSnapshot = false
}
if noHistory {
if ts.fm.indexedRange.initialized {
t.Fatalf("filterMapsRange initialized while indexing is disabled")
}
continue
}
if !ts.fm.indexedRange.initialized {
t.Fatalf("filterMapsRange not initialized while indexing is enabled")
}
var tailBlock uint64
if history > 0 && history <= head {
tailBlock = uint64(head + 1 - history)
}
var tailEpoch uint32
if tailBlock > 0 {
tailLvPtr := (tailBlock - 1) * lvPerBlock // no logs in genesis block, only delimiter
tailEpoch = uint32(tailLvPtr >> (testParams.logValuesPerMap + testParams.logMapsPerEpoch))
}
var expTailBlock uint64
if tailEpoch > 0 {
tailLvPtr := uint64(tailEpoch) << (testParams.logValuesPerMap + testParams.logMapsPerEpoch) // first available lv ptr
// (expTailBlock-1)*lvPerBlock >= tailLvPtr
expTailBlock = (tailLvPtr + lvPerBlock*2 - 1) / lvPerBlock
}
if ts.fm.indexedRange.afterLastIndexedBlock != uint64(head+1) {
ts.t.Fatalf("Invalid index head (expected #%d, got #%d)", head, ts.fm.indexedRange.afterLastIndexedBlock-1)
}
if ts.fm.indexedRange.headBlockDelimiter != uint64(head)*lvPerBlock {
ts.t.Fatalf("Invalid index head delimiter pointer (expected %d, got %d)", uint64(head)*lvPerBlock, ts.fm.indexedRange.headBlockDelimiter)
}
if ts.fm.indexedRange.firstIndexedBlock != expTailBlock {
ts.t.Fatalf("Invalid index tail block (expected #%d, got #%d)", expTailBlock, ts.fm.indexedRange.firstIndexedBlock)
}
}
}
func TestIndexerCompareDb(t *testing.T) {
ts := newTestSetup(t)
defer ts.close()
ts.chain.addBlocks(500, 10, 3, 4, true)
ts.setHistory(0, false)
ts.fm.WaitIdle()
// revert points are stored after block 500
ts.chain.addBlocks(500, 10, 3, 4, true)
ts.fm.WaitIdle()
chain1 := ts.chain.getCanonicalChain()
ts.storeDbHash("chain 1 [0, 1000]")
ts.chain.setHead(600)
ts.fm.WaitIdle()
ts.storeDbHash("chain 1/2 [0, 600]")
ts.chain.addBlocks(600, 10, 3, 4, true)
ts.fm.WaitIdle()
chain2 := ts.chain.getCanonicalChain()
ts.storeDbHash("chain 2 [0, 1200]")
ts.chain.setHead(600)
ts.fm.WaitIdle()
ts.checkDbHash("chain 1/2 [0, 600]")
ts.setHistory(800, false)
ts.chain.setCanonicalChain(chain1)
ts.fm.WaitIdle()
ts.storeDbHash("chain 1 [201, 1000]")
ts.setHistory(0, false)
ts.fm.WaitIdle()
ts.checkDbHash("chain 1 [0, 1000]")
ts.setHistory(800, false)
ts.chain.setCanonicalChain(chain2)
ts.fm.WaitIdle()
ts.storeDbHash("chain 2 [401, 1200]")
ts.setHistory(0, true)
ts.fm.WaitIdle()
ts.storeDbHash("no index")
ts.chain.setCanonicalChain(chain2[:501])
ts.setHistory(0, false)
ts.fm.WaitIdle()
ts.chain.setCanonicalChain(chain2)
ts.fm.WaitIdle()
ts.checkDbHash("chain 2 [0, 1200]")
ts.chain.setCanonicalChain(chain1)
ts.fm.WaitIdle()
ts.setHistory(800, false)
ts.fm.WaitIdle()
ts.checkDbHash("chain 1 [201, 1000]")
ts.chain.setCanonicalChain(chain2)
ts.fm.WaitIdle()
ts.checkDbHash("chain 2 [401, 1200]")
ts.setHistory(0, true)
ts.fm.WaitIdle()
ts.checkDbHash("no index")
}
type testSetup struct {
t *testing.T
fm *FilterMaps
db ethdb.Database
chain *testChain
params Params
dbHashes map[string]common.Hash
testDisableSnapshots bool
}
func newTestSetup(t *testing.T) *testSetup {
params := testParams
params.deriveFields()
ts := &testSetup{
t: t,
db: rawdb.NewMemoryDatabase(),
params: params,
dbHashes: make(map[string]common.Hash),
}
ts.chain = ts.newTestChain()
return ts
}
func (ts *testSetup) setHistory(history uint64, noHistory bool) {
if ts.fm != nil {
ts.fm.Stop()
}
head := ts.chain.CurrentBlock()
view := NewChainView(ts.chain, head.Number.Uint64(), head.Hash())
config := Config{
History: history,
Disabled: noHistory,
}
ts.fm = NewFilterMaps(ts.db, view, ts.params, config)
ts.fm.testDisableSnapshots = ts.testDisableSnapshots
ts.fm.Start()
}
func (ts *testSetup) storeDbHash(id string) {
dbHash := ts.fmDbHash()
for otherId, otherHash := range ts.dbHashes {
if otherHash == dbHash {
ts.t.Fatalf("Unexpected equal database hashes `%s` and `%s`", id, otherId)
}
}
ts.dbHashes[id] = dbHash
}
func (ts *testSetup) checkDbHash(id string) {
if ts.fmDbHash() != ts.dbHashes[id] {
ts.t.Fatalf("Database `%s` hash mismatch", id)
}
}
func (ts *testSetup) fmDbHash() common.Hash {
hasher := sha256.New()
it := ts.db.NewIterator(nil, nil)
for it.Next() {
hasher.Write(it.Key())
hasher.Write(it.Value())
}
it.Release()
var result common.Hash
hasher.Sum(result[:0])
return result
}
func (ts *testSetup) close() {
if ts.fm != nil {
ts.fm.Stop()
}
ts.db.Close()
ts.chain.db.Close()
}
type testChain struct {
ts *testSetup
db ethdb.Database
lock sync.RWMutex
canonical []common.Hash
blocks map[common.Hash]*types.Block
receipts map[common.Hash]types.Receipts
}
func (ts *testSetup) newTestChain() *testChain {
return &testChain{
ts: ts,
blocks: make(map[common.Hash]*types.Block),
receipts: make(map[common.Hash]types.Receipts),
}
}
func (tc *testChain) CurrentBlock() *types.Header {
tc.lock.RLock()
defer tc.lock.RUnlock()
if len(tc.canonical) == 0 {
return nil
}
return tc.blocks[tc.canonical[len(tc.canonical)-1]].Header()
}
func (tc *testChain) GetHeader(hash common.Hash, number uint64) *types.Header {
tc.lock.RLock()
defer tc.lock.RUnlock()
if block := tc.blocks[hash]; block != nil {
return block.Header()
}
return nil
}
func (tc *testChain) GetCanonicalHash(number uint64) common.Hash {
tc.lock.RLock()
defer tc.lock.RUnlock()
if uint64(len(tc.canonical)) <= number {
return common.Hash{}
}
return tc.canonical[number]
}
func (tc *testChain) GetReceiptsByHash(hash common.Hash) types.Receipts {
tc.lock.RLock()
defer tc.lock.RUnlock()
return tc.receipts[hash]
}
func (tc *testChain) addBlocks(count, maxTxPerBlock, maxLogsPerReceipt, maxTopicsPerLog int, random bool) {
tc.lock.Lock()
blockGen := func(i int, gen *core.BlockGen) {
var txCount int
if random {
txCount = rand.Intn(maxTxPerBlock + 1)
} else {
txCount = maxTxPerBlock
}
for k := txCount; k > 0; k-- {
receipt := types.NewReceipt(nil, false, 0)
var logCount int
if random {
logCount = rand.Intn(maxLogsPerReceipt + 1)
} else {
logCount = maxLogsPerReceipt
}
receipt.Logs = make([]*types.Log, logCount)
for i := range receipt.Logs {
log := &types.Log{}
receipt.Logs[i] = log
crand.Read(log.Address[:])
var topicCount int
if random {
topicCount = rand.Intn(maxTopicsPerLog + 1)
} else {
topicCount = maxTopicsPerLog
}
log.Topics = make([]common.Hash, topicCount)
for j := range log.Topics {
crand.Read(log.Topics[j][:])
}
}
gen.AddUncheckedReceipt(receipt)
gen.AddUncheckedTx(types.NewTransaction(999, common.HexToAddress("0x999"), big.NewInt(999), 999, gen.BaseFee(), nil))
}
}
var (
blocks []*types.Block
receipts []types.Receipts
engine = ethash.NewFaker()
)
if len(tc.canonical) == 0 {
gspec := &core.Genesis{
Alloc: types.GenesisAlloc{},
BaseFee: big.NewInt(params.InitialBaseFee),
Config: params.TestChainConfig,
}
tc.db, blocks, receipts = core.GenerateChainWithGenesis(gspec, engine, count, blockGen)
gblock := gspec.ToBlock()
ghash := gblock.Hash()
tc.canonical = []common.Hash{ghash}
tc.blocks[ghash] = gblock
tc.receipts[ghash] = types.Receipts{}
} else {
blocks, receipts = core.GenerateChain(params.TestChainConfig, tc.blocks[tc.canonical[len(tc.canonical)-1]], engine, tc.db, count, blockGen)
}
for i, block := range blocks {
num, hash := int(block.NumberU64()), block.Hash()
if len(tc.canonical) != num {
panic("canonical chain length mismatch")
}
tc.canonical = append(tc.canonical, hash)
tc.blocks[hash] = block
if receipts[i] != nil {
tc.receipts[hash] = receipts[i]
} else {
tc.receipts[hash] = types.Receipts{}
}
}
tc.lock.Unlock()
tc.setTargetHead()
}
func (tc *testChain) setHead(headNum int) {
tc.lock.Lock()
tc.canonical = tc.canonical[:headNum+1]
tc.lock.Unlock()
tc.setTargetHead()
}
func (tc *testChain) setTargetHead() {
head := tc.CurrentBlock()
if tc.ts.fm != nil {
if !tc.ts.fm.disabled {
//tc.ts.fm.targetViewCh <- NewChainView(tc, head.Number.Uint64(), head.Hash())
tc.ts.fm.SetTargetView(NewChainView(tc, head.Number.Uint64(), head.Hash()))
}
}
}
func (tc *testChain) getCanonicalChain() []common.Hash {
tc.lock.RLock()
defer tc.lock.RUnlock()
cc := make([]common.Hash, len(tc.canonical))
copy(cc, tc.canonical)
return cc
}
// restore an earlier state of the chain
func (tc *testChain) setCanonicalChain(cc []common.Hash) {
tc.lock.Lock()
tc.canonical = make([]common.Hash, len(cc))
copy(tc.canonical, cc)
tc.lock.Unlock()
tc.setTargetHead()
}

View file

@ -0,0 +1,768 @@
// Copyright 2024 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 <http://www.gnu.org/licenses/>.
package filtermaps
import (
"errors"
"fmt"
"math"
"sort"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)
const (
maxMapsPerBatch = 32 // maximum number of maps rendered in memory
valuesPerCallback = 1024 // log values processed per event process callback
cachedRowMappings = 10000 // log value to row mappings cached during rendering
// Number of rows written to db in a single batch.
// The map renderer splits up writes like this to ensure that regular
// block processing latency is not affected by large batch writes.
rowsPerBatch = 1024
)
var (
errChainUpdate = errors.New("rendered section of chain updated")
)
// mapRenderer represents a process that renders filter maps in a specified
// range according to the actual targetView.
type mapRenderer struct {
f *FilterMaps
afterLastMap uint32
currentMap *renderedMap
finishedMaps map[uint32]*renderedMap
firstFinished, afterLastFinished uint32
iterator *logIterator
}
// renderedMap represents a single filter map that is being rendered in memory.
type renderedMap struct {
filterMap filterMap
mapIndex uint32
lastBlock uint64
lastBlockId common.Hash
blockLvPtrs []uint64 // start pointers of blocks starting in this map; last one is lastBlock
finished bool // iterator finished; all values rendered
headDelimiter uint64 // if finished then points to the future block delimiter of the head block
}
// firstBlock returns the first block number that starts in the given map.
func (r *renderedMap) firstBlock() uint64 {
return r.lastBlock + 1 - uint64(len(r.blockLvPtrs))
}
// renderMapsBefore creates a mapRenderer that renders the log index until the
// specified map index boundary, starting from the latest available starting
// point that is consistent with the current targetView.
// The renderer ensures that filterMapsRange, indexedView and the actual map
// data are always consistent with each other. If afterLastMap is greater than
// the latest existing rendered map then indexedView is updated to targetView,
// otherwise it is checked that the rendered range is consistent with both
// views.
func (f *FilterMaps) renderMapsBefore(afterLastMap uint32) (*mapRenderer, error) {
nextMap, startBlock, startLvPtr, err := f.lastCanonicalMapBoundaryBefore(afterLastMap)
if err != nil {
return nil, err
}
if snapshot := f.lastCanonicalSnapshotBefore(afterLastMap); snapshot != nil && snapshot.mapIndex >= nextMap {
return f.renderMapsFromSnapshot(snapshot)
}
if nextMap >= afterLastMap {
return nil, nil
}
return f.renderMapsFromMapBoundary(nextMap, afterLastMap, startBlock, startLvPtr)
}
// renderMapsFromSnapshot creates a mapRenderer that starts rendering from a
// snapshot made at a block boundary.
func (f *FilterMaps) renderMapsFromSnapshot(cp *renderedMap) (*mapRenderer, error) {
f.testSnapshotUsed = true
iter, err := f.newLogIteratorFromBlockDelimiter(cp.lastBlock)
if err != nil {
return nil, fmt.Errorf("failed to create log iterator from block delimiter %d: %v", cp.lastBlock, err)
}
return &mapRenderer{
f: f,
currentMap: &renderedMap{
filterMap: cp.filterMap.copy(),
mapIndex: cp.mapIndex,
lastBlock: cp.lastBlock,
blockLvPtrs: cp.blockLvPtrs,
},
finishedMaps: make(map[uint32]*renderedMap),
firstFinished: cp.mapIndex,
afterLastFinished: cp.mapIndex,
afterLastMap: math.MaxUint32,
iterator: iter,
}, nil
}
// renderMapsFromMapBoundary creates a mapRenderer that starts rendering at a
// map boundary.
func (f *FilterMaps) renderMapsFromMapBoundary(firstMap, afterLastMap uint32, startBlock, startLvPtr uint64) (*mapRenderer, error) {
iter, err := f.newLogIteratorFromMapBoundary(firstMap, startBlock, startLvPtr)
if err != nil {
return nil, fmt.Errorf("failed to create log iterator from map boundary %d: %v", firstMap, err)
}
return &mapRenderer{
f: f,
currentMap: &renderedMap{
filterMap: f.emptyFilterMap(),
mapIndex: firstMap,
lastBlock: iter.blockNumber,
},
finishedMaps: make(map[uint32]*renderedMap),
firstFinished: firstMap,
afterLastFinished: firstMap,
afterLastMap: afterLastMap,
iterator: iter,
}, nil
}
// lastCanonicalSnapshotBefore returns the latest cached snapshot that matches
// the current targetView.
func (f *FilterMaps) lastCanonicalSnapshotBefore(afterLastMap uint32) *renderedMap {
var best *renderedMap
for _, blockNumber := range f.renderSnapshots.Keys() {
if cp, _ := f.renderSnapshots.Get(blockNumber); cp != nil && blockNumber < f.indexedRange.afterLastIndexedBlock &&
blockNumber <= f.targetView.headNumber && f.targetView.getBlockId(blockNumber) == cp.lastBlockId &&
cp.mapIndex < afterLastMap && (best == nil || blockNumber > best.lastBlock) {
best = cp
}
}
return best
}
// lastCanonicalMapBoundaryBefore returns the latest map boundary before the
// specified map index that matches the current targetView. This can either
// be a checkpoint (hardcoded or left from a previously unindexed tail epoch)
// or the boundary of a currently rendered map.
// Along with the next map index where the rendering can be started, the number
// and starting log value pointer of the last block is also returned.
func (f *FilterMaps) lastCanonicalMapBoundaryBefore(afterLastMap uint32) (nextMap uint32, startBlock, startLvPtr uint64, err error) {
if !f.indexedRange.initialized {
return 0, 0, 0, nil
}
mapIndex := afterLastMap
for {
var ok bool
if mapIndex, ok = f.lastMapBoundaryBefore(mapIndex); !ok {
return 0, 0, 0, nil
}
lastBlock, lastBlockId, err := f.getLastBlockOfMap(mapIndex)
if err != nil {
return 0, 0, 0, fmt.Errorf("failed to retrieve last block of reverse iterated map %d: %v", mapIndex, err)
}
if lastBlock >= f.indexedView.headNumber || lastBlock >= f.targetView.headNumber ||
lastBlockId != f.targetView.getBlockId(lastBlock) {
// map is not full or inconsistent with targetView; roll back
continue
}
lvPtr, err := f.getBlockLvPointer(lastBlock)
if err != nil {
return 0, 0, 0, fmt.Errorf("failed to retrieve log value pointer of last canonical boundary block %d: %v", lastBlock, err)
}
return mapIndex + 1, lastBlock, lvPtr, nil
}
}
// lastMapBoundaryBefore returns the latest map boundary before the specified
// map index.
func (f *FilterMaps) lastMapBoundaryBefore(mapIndex uint32) (uint32, bool) {
if !f.indexedRange.initialized || f.indexedRange.afterLastRenderedMap == 0 {
return 0, false
}
if mapIndex > f.indexedRange.afterLastRenderedMap {
mapIndex = f.indexedRange.afterLastRenderedMap
}
if mapIndex > f.indexedRange.firstRenderedMap {
return mapIndex - 1, true
}
if mapIndex+f.mapsPerEpoch > f.indexedRange.firstRenderedMap {
if mapIndex > f.indexedRange.firstRenderedMap-f.mapsPerEpoch+f.indexedRange.tailPartialEpoch {
mapIndex = f.indexedRange.firstRenderedMap - f.mapsPerEpoch + f.indexedRange.tailPartialEpoch
}
} else {
mapIndex = (mapIndex >> f.logMapsPerEpoch) << f.logMapsPerEpoch
}
if mapIndex == 0 {
return 0, false
}
return mapIndex - 1, true
}
// emptyFilterMap returns an empty filter map.
func (f *FilterMaps) emptyFilterMap() filterMap {
return make(filterMap, f.mapHeight)
}
// loadHeadSnapshot loads the last rendered map from the database and creates
// a snapshot.
func (f *FilterMaps) loadHeadSnapshot() error {
fm, err := f.getFilterMap(f.indexedRange.afterLastRenderedMap - 1)
if err != nil {
return fmt.Errorf("failed to load head snapshot map %d: %v", f.indexedRange.afterLastRenderedMap-1, err)
}
lastBlock, _, err := f.getLastBlockOfMap(f.indexedRange.afterLastRenderedMap - 1)
if err != nil {
return fmt.Errorf("failed to retrieve last block of head snapshot map %d: %v", f.indexedRange.afterLastRenderedMap-1, err)
}
var firstBlock uint64
if f.indexedRange.afterLastRenderedMap > 1 {
prevLastBlock, _, err := f.getLastBlockOfMap(f.indexedRange.afterLastRenderedMap - 2)
if err != nil {
return fmt.Errorf("failed to retrieve last block of map %d before head snapshot: %v", f.indexedRange.afterLastRenderedMap-2, err)
}
firstBlock = prevLastBlock + 1
}
lvPtrs := make([]uint64, lastBlock+1-firstBlock)
for i := range lvPtrs {
lvPtrs[i], err = f.getBlockLvPointer(firstBlock + uint64(i))
if err != nil {
return fmt.Errorf("failed to retrieve log value pointer of head snapshot block %d: %v", firstBlock+uint64(i), err)
}
}
f.renderSnapshots.Add(f.indexedRange.afterLastIndexedBlock-1, &renderedMap{
filterMap: fm,
mapIndex: f.indexedRange.afterLastRenderedMap - 1,
lastBlock: f.indexedRange.afterLastIndexedBlock - 1,
lastBlockId: f.indexedView.getBlockId(f.indexedRange.afterLastIndexedBlock - 1),
blockLvPtrs: lvPtrs,
finished: true,
headDelimiter: f.indexedRange.headBlockDelimiter,
})
return nil
}
// makeSnapshot creates a snapshot of the current state of the rendered map.
func (r *mapRenderer) makeSnapshot() {
r.f.renderSnapshots.Add(r.iterator.blockNumber, &renderedMap{
filterMap: r.currentMap.filterMap.copy(),
mapIndex: r.currentMap.mapIndex,
lastBlock: r.iterator.blockNumber,
lastBlockId: r.f.targetView.getBlockId(r.currentMap.lastBlock),
blockLvPtrs: r.currentMap.blockLvPtrs,
finished: true,
headDelimiter: r.iterator.lvIndex,
})
}
// run does the actual map rendering. It periodically calls the stopCb callback
// and if it returns true the process is interrupted an can be resumed later
// by calling run again. The writeCb callback is called after new maps have
// been written to disk and the index range has been updated accordingly.
func (r *mapRenderer) run(stopCb func() bool, writeCb func()) (bool, error) {
for {
if done, err := r.renderCurrentMap(stopCb); !done {
return done, err // stopped or failed
}
// map finished
r.finishedMaps[r.currentMap.mapIndex] = r.currentMap
r.afterLastFinished++
if len(r.finishedMaps) >= maxMapsPerBatch || r.afterLastFinished&(r.f.baseRowGroupLength-1) == 0 {
if err := r.writeFinishedMaps(stopCb); err != nil {
return false, err
}
writeCb()
}
if r.afterLastFinished == r.afterLastMap || r.iterator.finished {
if err := r.writeFinishedMaps(stopCb); err != nil {
return false, err
}
writeCb()
return true, nil
}
r.currentMap = &renderedMap{
filterMap: r.f.emptyFilterMap(),
mapIndex: r.afterLastFinished,
}
}
}
// renderCurrentMap renders a single map.
func (r *mapRenderer) renderCurrentMap(stopCb func() bool) (bool, error) {
if !r.iterator.updateChainView(r.f.targetView) {
return false, errChainUpdate
}
var waitCnt int
if r.iterator.lvIndex == 0 {
r.currentMap.blockLvPtrs = []uint64{0}
}
type lvPos struct{ rowIndex, layerIndex uint32 }
rowMappingCache := lru.NewCache[common.Hash, lvPos](cachedRowMappings)
defer rowMappingCache.Purge()
for r.iterator.lvIndex < uint64(r.currentMap.mapIndex+1)<<r.f.logValuesPerMap && !r.iterator.finished {
waitCnt++
if waitCnt >= valuesPerCallback {
if stopCb() {
return false, nil
}
if !r.iterator.updateChainView(r.f.targetView) {
return false, errChainUpdate
}
waitCnt = 0
}
r.currentMap.lastBlock = r.iterator.blockNumber
if r.iterator.delimiter {
r.currentMap.lastBlock++
r.currentMap.blockLvPtrs = append(r.currentMap.blockLvPtrs, r.iterator.lvIndex+1)
}
if logValue := r.iterator.getValueHash(); logValue != (common.Hash{}) {
lvp, cached := rowMappingCache.Get(logValue)
if !cached {
lvp = lvPos{rowIndex: r.f.rowIndex(r.currentMap.mapIndex, 0, logValue)}
}
for uint32(len(r.currentMap.filterMap[lvp.rowIndex])) >= r.f.maxRowLength(lvp.layerIndex) {
lvp.layerIndex++
lvp.rowIndex = r.f.rowIndex(r.currentMap.mapIndex, lvp.layerIndex, logValue)
cached = false
}
r.currentMap.filterMap[lvp.rowIndex] = append(r.currentMap.filterMap[lvp.rowIndex], r.f.columnIndex(r.iterator.lvIndex, &logValue))
if !cached {
rowMappingCache.Add(logValue, lvp)
}
}
if err := r.iterator.next(); err != nil {
return false, fmt.Errorf("failed to advance log iterator at %d while rendering map %d: %v", r.iterator.lvIndex, r.currentMap.mapIndex, err)
}
if !r.f.testDisableSnapshots && r.afterLastMap >= r.f.indexedRange.afterLastRenderedMap &&
(r.iterator.delimiter || r.iterator.finished) {
r.makeSnapshot()
}
}
if r.iterator.finished {
r.currentMap.finished = true
r.currentMap.headDelimiter = r.iterator.lvIndex
}
r.currentMap.lastBlockId = r.f.targetView.getBlockId(r.currentMap.lastBlock)
return true, nil
}
// writeFinishedMaps writes rendered maps to the database and updates
// filterMapsRange and indexedView accordingly.
func (r *mapRenderer) writeFinishedMaps(pauseCb func() bool) error {
if len(r.finishedMaps) == 0 {
return nil
}
r.f.indexLock.Lock()
defer r.f.indexLock.Unlock()
oldRange := r.f.indexedRange
tempRange, err := r.getTempRange()
if err != nil {
return fmt.Errorf("failed to get temporary rendered range: %v", err)
}
newRange, err := r.getUpdatedRange()
if err != nil {
return fmt.Errorf("failed to get updated rendered range: %v", err)
}
renderedView := r.f.targetView // stopCb callback might still change targetView while writing finished maps
batch := r.f.db.NewBatch()
var writeCnt int
checkWriteCnt := func() {
writeCnt++
if writeCnt == rowsPerBatch {
writeCnt = 0
if err := batch.Write(); err != nil {
log.Crit("Error writing log index update batch", "error", err)
}
// do not exit while in partially written state but do allow processing
// events and pausing while block processing is in progress
pauseCb()
batch = r.f.db.NewBatch()
}
}
r.f.setRange(batch, r.f.indexedView, tempRange)
// add or update filter rows
for rowIndex := uint32(0); rowIndex < r.f.mapHeight; rowIndex++ {
var (
mapIndices []uint32
rows []FilterRow
)
for mapIndex := r.firstFinished; mapIndex < r.afterLastFinished; mapIndex++ {
row := r.finishedMaps[mapIndex].filterMap[rowIndex]
if fm, _ := r.f.filterMapCache.Get(mapIndex); fm != nil && row.Equal(fm[rowIndex]) {
continue
}
mapIndices = append(mapIndices, mapIndex)
rows = append(rows, row)
}
if newRange.afterLastRenderedMap == r.afterLastFinished { // head updated; remove future entries
for mapIndex := r.afterLastFinished; mapIndex < oldRange.afterLastRenderedMap; mapIndex++ {
if fm, _ := r.f.filterMapCache.Get(mapIndex); fm != nil && len(fm[rowIndex]) == 0 {
continue
}
mapIndices = append(mapIndices, mapIndex)
rows = append(rows, nil)
}
}
if err := r.f.storeFilterMapRows(batch, mapIndices, rowIndex, rows); err != nil {
return fmt.Errorf("failed to store filter maps %v row %d: %v", mapIndices, rowIndex, err)
}
checkWriteCnt()
}
// update filter map cache
if newRange.afterLastRenderedMap == r.afterLastFinished {
// head updated; cache new head maps and remove future entries
for mapIndex := r.firstFinished; mapIndex < r.afterLastFinished; mapIndex++ {
r.f.filterMapCache.Add(mapIndex, r.finishedMaps[mapIndex].filterMap)
}
for mapIndex := r.afterLastFinished; mapIndex < oldRange.afterLastRenderedMap; mapIndex++ {
r.f.filterMapCache.Remove(mapIndex)
}
} else {
// head not updated; do not cache maps during tail rendering because we
// need head maps to be available in the cache
for mapIndex := r.firstFinished; mapIndex < r.afterLastFinished; mapIndex++ {
r.f.filterMapCache.Remove(mapIndex)
}
}
// add or update block pointers
blockNumber := r.finishedMaps[r.firstFinished].firstBlock()
for mapIndex := r.firstFinished; mapIndex < r.afterLastFinished; mapIndex++ {
renderedMap := r.finishedMaps[mapIndex]
r.f.storeLastBlockOfMap(batch, mapIndex, renderedMap.lastBlock, renderedMap.lastBlockId)
checkWriteCnt()
if blockNumber != renderedMap.firstBlock() {
panic("non-continuous block numbers")
}
for _, lvPtr := range renderedMap.blockLvPtrs {
r.f.storeBlockLvPointer(batch, blockNumber, lvPtr)
checkWriteCnt()
blockNumber++
}
}
if newRange.afterLastRenderedMap == r.afterLastFinished { // head updated; remove future entries
for mapIndex := r.afterLastFinished; mapIndex < oldRange.afterLastRenderedMap; mapIndex++ {
r.f.deleteLastBlockOfMap(batch, mapIndex)
checkWriteCnt()
}
for ; blockNumber < oldRange.afterLastIndexedBlock; blockNumber++ {
r.f.deleteBlockLvPointer(batch, blockNumber)
checkWriteCnt()
}
}
r.finishedMaps = make(map[uint32]*renderedMap)
r.firstFinished = r.afterLastFinished
r.f.setRange(batch, renderedView, newRange)
if err := batch.Write(); err != nil {
log.Crit("Error writing log index update batch", "error", err)
}
return nil
}
// getTempRange returns a temporary filterMapsRange that is committed to the
// database while the newly rendered maps are partially written. Writing all
// processed maps in a single database batch would be a serious hit on db
// performance so instead safety is ensured by first reverting the valid map
// range to the unchanged region until all new map data is committed.
func (r *mapRenderer) getTempRange() (filterMapsRange, error) {
tempRange := r.f.indexedRange
if err := tempRange.addRenderedRange(r.firstFinished, r.firstFinished, r.afterLastMap, r.f.mapsPerEpoch); err != nil {
return filterMapsRange{}, fmt.Errorf("failed to update temporary rendered range: %v", err)
}
if tempRange.firstRenderedMap != r.f.indexedRange.firstRenderedMap {
// first rendered map changed; update first indexed block
if tempRange.firstRenderedMap > 0 {
lastBlock, _, err := r.f.getLastBlockOfMap(tempRange.firstRenderedMap - 1)
if err != nil {
return filterMapsRange{}, fmt.Errorf("failed to retrieve last block of map %d before temporary range: %v", tempRange.firstRenderedMap-1, err)
}
tempRange.firstIndexedBlock = lastBlock + 1
} else {
tempRange.firstIndexedBlock = 0
}
}
if tempRange.afterLastRenderedMap != r.f.indexedRange.afterLastRenderedMap {
// first rendered map changed; update first indexed block
if tempRange.afterLastRenderedMap > 0 {
lastBlock, _, err := r.f.getLastBlockOfMap(tempRange.afterLastRenderedMap - 1)
if err != nil {
return filterMapsRange{}, fmt.Errorf("failed to retrieve last block of map %d at the end of temporary range: %v", tempRange.afterLastRenderedMap-1, err)
}
tempRange.afterLastIndexedBlock = lastBlock
} else {
tempRange.afterLastIndexedBlock = 0
}
tempRange.headBlockDelimiter = 0
}
return tempRange, nil
}
// getUpdatedRange returns the updated filterMapsRange after writing the newly
// rendered maps.
func (r *mapRenderer) getUpdatedRange() (filterMapsRange, error) {
// update filterMapsRange
newRange := r.f.indexedRange
if err := newRange.addRenderedRange(r.firstFinished, r.afterLastFinished, r.afterLastMap, r.f.mapsPerEpoch); err != nil {
return filterMapsRange{}, fmt.Errorf("failed to update rendered range: %v", err)
}
if newRange.firstRenderedMap != r.f.indexedRange.firstRenderedMap {
// first rendered map changed; update first indexed block
if newRange.firstRenderedMap > 0 {
lastBlock, _, err := r.f.getLastBlockOfMap(newRange.firstRenderedMap - 1)
if err != nil {
return filterMapsRange{}, fmt.Errorf("failed to retrieve last block of map %d before rendered range: %v", newRange.firstRenderedMap-1, err)
}
newRange.firstIndexedBlock = lastBlock + 1
} else {
newRange.firstIndexedBlock = 0
}
}
if newRange.afterLastRenderedMap == r.afterLastFinished {
// last rendered map changed; update last indexed block and head pointers
lm := r.finishedMaps[r.afterLastFinished-1]
newRange.headBlockIndexed = lm.finished
if lm.finished {
newRange.afterLastIndexedBlock = r.f.targetView.headNumber + 1
if lm.lastBlock != r.f.targetView.headNumber {
panic("map rendering finished but last block != head block")
}
newRange.headBlockDelimiter = lm.headDelimiter
} else {
newRange.afterLastIndexedBlock = lm.lastBlock
newRange.headBlockDelimiter = 0
}
} else {
// last rendered map not replaced; ensure that target chain view matches
// indexed chain view on the rendered section
if lastBlock := r.finishedMaps[r.afterLastFinished-1].lastBlock; !matchViews(r.f.indexedView, r.f.targetView, lastBlock) {
return filterMapsRange{}, errChainUpdate
}
}
return newRange, nil
}
// addRenderedRange adds the range [firstRendered, afterLastRendered) and
// removes [afterLastRendered, afterLastRemoved) from the set of rendered maps.
func (fmr *filterMapsRange) addRenderedRange(firstRendered, afterLastRendered, afterLastRemoved, mapsPerEpoch uint32) error {
if !fmr.initialized {
return errors.New("log index not initialized")
}
// Here we create a slice of endpoints for the rendered sections. There are two endpoints
// for each section: the index of the first map, and the index after the last map in the
// section. We then iterate the endpoints -- adding d values -- to determine whether the
// sections are contiguous or whether they have a gap.
type endpoint struct {
m uint32
d int
}
endpoints := []endpoint{{fmr.firstRenderedMap, 1}, {fmr.afterLastRenderedMap, -1}, {firstRendered, 1}, {afterLastRendered, -101}, {afterLastRemoved, 100}}
if fmr.tailPartialEpoch > 0 {
endpoints = append(endpoints, []endpoint{{fmr.firstRenderedMap - mapsPerEpoch, 1}, {fmr.firstRenderedMap - mapsPerEpoch + fmr.tailPartialEpoch, -1}}...)
}
sort.Slice(endpoints, func(i, j int) bool { return endpoints[i].m < endpoints[j].m })
var (
sum int
merged []uint32
last bool
)
for i, e := range endpoints {
sum += e.d
if i < len(endpoints)-1 && endpoints[i+1].m == e.m {
continue
}
if (sum > 0) != last {
merged = append(merged, e.m)
last = !last
}
}
switch len(merged) {
case 0:
// Initialized database, but no finished maps yet.
fmr.tailPartialEpoch = 0
fmr.firstRenderedMap = firstRendered
fmr.afterLastRenderedMap = firstRendered
case 2:
// One rendered section (no partial tail epoch).
fmr.tailPartialEpoch = 0
fmr.firstRenderedMap = merged[0]
fmr.afterLastRenderedMap = merged[1]
case 4:
// Two rendered sections (with a gap).
// First section (merged[0]-merged[1]) is for the partial tail epoch,
// and it has to start exactly one epoch before the main section.
if merged[2] != merged[0]+mapsPerEpoch {
return fmt.Errorf("invalid tail partial epoch: %v", merged)
}
fmr.tailPartialEpoch = merged[1] - merged[0]
fmr.firstRenderedMap = merged[2]
fmr.afterLastRenderedMap = merged[3]
default:
return fmt.Errorf("invalid number of rendered sections: %v", merged)
}
return nil
}
// logIterator iterates on the linear log value index range.
type logIterator struct {
chainView *ChainView
blockNumber uint64
receipts types.Receipts
blockStart, delimiter, finished bool
txIndex, logIndex, topicIndex int
lvIndex uint64
}
var errUnindexedRange = errors.New("unindexed range")
// newLogIteratorFromBlockDelimiter creates a logIterator starting at the
// given block's first log value entry (the block delimiter), according to the
// current targetView.
func (f *FilterMaps) newLogIteratorFromBlockDelimiter(blockNumber uint64) (*logIterator, error) {
if blockNumber > f.targetView.headNumber {
return nil, fmt.Errorf("iterator entry point %d after target chain head block %d", blockNumber, f.targetView.headNumber)
}
if blockNumber < f.indexedRange.firstIndexedBlock || blockNumber >= f.indexedRange.afterLastIndexedBlock {
return nil, errUnindexedRange
}
var lvIndex uint64
if f.indexedRange.headBlockIndexed && blockNumber+1 == f.indexedRange.afterLastIndexedBlock {
lvIndex = f.indexedRange.headBlockDelimiter
} else {
var err error
lvIndex, err = f.getBlockLvPointer(blockNumber + 1)
if err != nil {
return nil, fmt.Errorf("failed to retrieve log value pointer of block %d after delimiter: %v", blockNumber+1, err)
}
lvIndex--
}
finished := blockNumber == f.targetView.headNumber
return &logIterator{
chainView: f.targetView,
blockNumber: blockNumber,
finished: finished,
delimiter: !finished,
lvIndex: lvIndex,
}, nil
}
// newLogIteratorFromMapBoundary creates a logIterator starting at the given
// map boundary, according to the current targetView.
func (f *FilterMaps) newLogIteratorFromMapBoundary(mapIndex uint32, startBlock, startLvPtr uint64) (*logIterator, error) {
if startBlock > f.targetView.headNumber {
return nil, fmt.Errorf("iterator entry point %d after target chain head block %d", startBlock, f.targetView.headNumber)
}
// get block receipts
receipts := f.targetView.getReceipts(startBlock)
if receipts == nil {
return nil, fmt.Errorf("receipts not found for start block %d", startBlock)
}
// initialize iterator at block start
l := &logIterator{
chainView: f.targetView,
blockNumber: startBlock,
receipts: receipts,
blockStart: true,
lvIndex: startLvPtr,
}
l.nextValid()
targetIndex := uint64(mapIndex) << f.logValuesPerMap
if l.lvIndex > targetIndex {
return nil, fmt.Errorf("log value pointer %d of last block of map is after map boundary %d", l.lvIndex, targetIndex)
}
// iterate to map boundary
for l.lvIndex < targetIndex {
if l.finished {
return nil, fmt.Errorf("iterator already finished at %d before map boundary target %d", l.lvIndex, targetIndex)
}
if err := l.next(); err != nil {
return nil, fmt.Errorf("failed to advance log iterator at %d before map boundary target %d: %v", l.lvIndex, targetIndex, err)
}
}
return l, nil
}
// updateChainView updates the iterator's chain view if it still matches the
// previous view at the current position. Returns true if successful.
func (l *logIterator) updateChainView(cv *ChainView) bool {
if !matchViews(cv, l.chainView, l.blockNumber) {
return false
}
l.chainView = cv
return true
}
// getValueHash returns the log value hash at the current position.
func (l *logIterator) getValueHash() common.Hash {
if l.delimiter || l.finished {
return common.Hash{}
}
log := l.receipts[l.txIndex].Logs[l.logIndex]
if l.topicIndex == 0 {
return addressValue(log.Address)
}
return topicValue(log.Topics[l.topicIndex-1])
}
// next moves the iterator to the next log value index.
func (l *logIterator) next() error {
if l.finished {
return nil
}
if l.delimiter {
l.delimiter = false
l.blockNumber++
l.receipts = l.chainView.getReceipts(l.blockNumber)
if l.receipts == nil {
return fmt.Errorf("receipts not found for block %d", l.blockNumber)
}
l.txIndex, l.logIndex, l.topicIndex, l.blockStart = 0, 0, 0, true
} else {
l.topicIndex++
l.blockStart = false
}
l.lvIndex++
l.nextValid()
return nil
}
// nextValid updates the internal transaction, log and topic index pointers
// to the next existing log value of the given block if necessary.
// Note that nextValid does not advance the log value index pointer.
func (l *logIterator) nextValid() {
for ; l.txIndex < len(l.receipts); l.txIndex++ {
receipt := l.receipts[l.txIndex]
for ; l.logIndex < len(receipt.Logs); l.logIndex++ {
log := receipt.Logs[l.logIndex]
if l.topicIndex <= len(log.Topics) {
return
}
l.topicIndex = 0
}
l.logIndex = 0
}
if l.blockNumber == l.chainView.headNumber {
l.finished = true
} else {
l.delimiter = true
}
}

934
core/filtermaps/matcher.go Normal file
View file

@ -0,0 +1,934 @@
// Copyright 2024 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 <http://www.gnu.org/licenses/>.
package filtermaps
import (
"context"
"errors"
"fmt"
"math"
"sync"
"sync/atomic"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/mclock"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)
const doRuntimeStats = false
// ErrMatchAll is returned when the specified filter matches everything.
// Handling this case in filtermaps would require an extra special case and
// would actually be slower than reverting to legacy filter.
var ErrMatchAll = errors.New("match all patterns not supported")
// MatcherBackend defines the functions required for searching in the log index
// data structure. It is currently implemented by FilterMapsMatcherBackend but
// once EIP-7745 is implemented and active, these functions can also be trustlessly
// served by a remote prover.
type MatcherBackend interface {
GetParams() *Params
GetBlockLvPointer(ctx context.Context, blockNumber uint64) (uint64, error)
GetFilterMapRow(ctx context.Context, mapIndex, rowIndex uint32, baseLayerOnly bool) (FilterRow, error)
GetLogByLvIndex(ctx context.Context, lvIndex uint64) (*types.Log, error)
SyncLogIndex(ctx context.Context) (SyncRange, error)
Close()
}
// SyncRange is returned by MatcherBackend.SyncLogIndex. It contains the latest
// chain head, the indexed range that is currently consistent with the chain
// and the valid range that has not been changed and has been consistent with
// all states of the chain since the previous SyncLogIndex or the creation of
// the matcher backend.
type SyncRange struct {
HeadNumber uint64
// block range where the index has not changed since the last matcher sync
// and therefore the set of matches found in this region is guaranteed to
// be valid and complete.
Valid bool
FirstValid, LastValid uint64
// block range indexed according to the given chain head.
Indexed bool
FirstIndexed, LastIndexed uint64
}
// GetPotentialMatches returns a list of logs that are potential matches for the
// given filter criteria. If parts of the log index in the searched range are
// missing or changed during the search process then the resulting logs belonging
// to that block range might be missing or incorrect.
// Also note that the returned list may contain false positives.
func GetPotentialMatches(ctx context.Context, backend MatcherBackend, firstBlock, lastBlock uint64, addresses []common.Address, topics [][]common.Hash) ([]*types.Log, error) {
params := backend.GetParams()
var getLogStats runtimeStats
// find the log value index range to search
firstIndex, err := backend.GetBlockLvPointer(ctx, firstBlock)
if err != nil {
return nil, fmt.Errorf("failed to retrieve log value pointer for first block %d: %v", firstBlock, err)
}
lastIndex, err := backend.GetBlockLvPointer(ctx, lastBlock+1)
if err != nil {
return nil, fmt.Errorf("failed to retrieve log value pointer after last block %d: %v", lastBlock, err)
}
if lastIndex > 0 {
lastIndex--
}
firstMap, lastMap := uint32(firstIndex>>params.logValuesPerMap), uint32(lastIndex>>params.logValuesPerMap)
firstEpoch, lastEpoch := firstMap>>params.logMapsPerEpoch, lastMap>>params.logMapsPerEpoch
// build matcher according to the given filter criteria
matchers := make([]matcher, len(topics)+1)
// matchAddress signals a match when there is a match for any of the given
// addresses.
// If the list of addresses is empty then it creates a "wild card" matcher
// that signals every index as a potential match.
matchAddress := make(matchAny, len(addresses))
for i, address := range addresses {
matchAddress[i] = &singleMatcher{backend: backend, value: addressValue(address)}
}
matchers[0] = matchAddress
for i, topicList := range topics {
// matchTopic signals a match when there is a match for any of the topics
// specified for the given position (topicList).
// If topicList is empty then it creates a "wild card" matcher that signals
// every index as a potential match.
matchTopic := make(matchAny, len(topicList))
for j, topic := range topicList {
matchTopic[j] = &singleMatcher{backend: backend, value: topicValue(topic)}
}
matchers[i+1] = matchTopic
}
// matcher is the final sequence matcher that signals a match when all underlying
// matchers signal a match for consecutive log value indices.
matcher := newMatchSequence(params, matchers)
// processEpoch returns the potentially matching logs from the given epoch.
processEpoch := func(epochIndex uint32) ([]*types.Log, error) {
var logs []*types.Log
// create a list of map indices to process
fm, lm := epochIndex<<params.logMapsPerEpoch, (epochIndex+1)<<params.logMapsPerEpoch-1
if fm < firstMap {
fm = firstMap
}
if lm > lastMap {
lm = lastMap
}
//
mapIndices := make([]uint32, lm+1-fm)
for i := range mapIndices {
mapIndices[i] = fm + uint32(i)
}
// find potential matches
matches, err := getAllMatches(ctx, matcher, mapIndices)
if err != nil {
return logs, err
}
// get the actual logs located at the matching log value indices
var st int
getLogStats.setState(&st, stGetLog)
defer getLogStats.setState(&st, stNone)
for _, m := range matches {
if m == nil {
return nil, ErrMatchAll
}
mlogs, err := getLogsFromMatches(ctx, backend, firstIndex, lastIndex, m)
if err != nil {
return logs, err
}
logs = append(logs, mlogs...)
}
getLogStats.addAmount(st, int64(len(logs)))
return logs, nil
}
type task struct {
epochIndex uint32
logs []*types.Log
err error
done chan struct{}
}
taskCh := make(chan *task)
var wg sync.WaitGroup
defer func() {
close(taskCh)
wg.Wait()
}()
worker := func() {
for task := range taskCh {
if task == nil {
break
}
task.logs, task.err = processEpoch(task.epochIndex)
close(task.done)
}
wg.Done()
}
start := time.Now()
for i := 0; i < 4; i++ {
wg.Add(1)
go worker()
}
var logs []*types.Log
// startEpoch is the next task to send whenever a worker can accept it.
// waitEpoch is the next task we are waiting for to finish in order to append
// results in the correct order.
startEpoch, waitEpoch := firstEpoch, firstEpoch
tasks := make(map[uint32]*task)
tasks[startEpoch] = &task{epochIndex: startEpoch, done: make(chan struct{})}
for waitEpoch <= lastEpoch {
select {
case taskCh <- tasks[startEpoch]:
startEpoch++
if startEpoch <= lastEpoch {
if tasks[startEpoch] == nil {
tasks[startEpoch] = &task{epochIndex: startEpoch, done: make(chan struct{})}
}
}
case <-tasks[waitEpoch].done:
logs = append(logs, tasks[waitEpoch].logs...)
if err := tasks[waitEpoch].err; err != nil {
if err == ErrMatchAll {
return logs, err
}
return logs, fmt.Errorf("failed to process log index epoch %d: %v", waitEpoch, err)
}
delete(tasks, waitEpoch)
waitEpoch++
if waitEpoch <= lastEpoch {
if tasks[waitEpoch] == nil {
tasks[waitEpoch] = &task{epochIndex: waitEpoch, done: make(chan struct{})}
}
}
}
}
if doRuntimeStats {
log.Info("Log search finished", "elapsed", time.Since(start))
for i, ma := range matchers {
for j, m := range ma.(matchAny) {
log.Info("Single matcher stats", "matchSequence", i, "matchAny", j)
m.(*singleMatcher).stats.print()
}
}
log.Info("Get log stats")
getLogStats.print()
}
return logs, nil
}
// getLogsFromMatches returns the list of potentially matching logs located at
// the given list of matching log indices. Matches outside the firstIndex to
// lastIndex range are not returned.
func getLogsFromMatches(ctx context.Context, backend MatcherBackend, firstIndex, lastIndex uint64, matches potentialMatches) ([]*types.Log, error) {
var logs []*types.Log
for _, match := range matches {
if match < firstIndex || match > lastIndex {
continue
}
log, err := backend.GetLogByLvIndex(ctx, match)
if err != nil {
return logs, fmt.Errorf("failed to retrieve log at index %d: %v", match, err)
}
if log != nil {
logs = append(logs, log)
}
}
return logs, nil
}
// matcher defines a general abstraction for any matcher configuration that
// can instantiate a matcherInstance.
type matcher interface {
newInstance(mapIndices []uint32) matcherInstance
}
// matcherInstance defines a general abstraction for a matcher configuration
// working on a specific set of map indices and eventually returning a list of
// potentially matching log value indices.
// Note that processing happens per mapping layer, each call returning a set
// of results for the maps where the processing has been finished at the given
// layer. Map indices can also be dropped before a result is returned for them
// in case the result is no longer interesting. Dropping indices twice or after
// a result has been returned has no effect. Exactly one matcherResult is
// returned per requested map index unless dropped.
type matcherInstance interface {
getMatchesForLayer(ctx context.Context, layerIndex uint32) ([]matcherResult, error)
dropIndices(mapIndices []uint32)
}
// matcherResult contains the list of potentially matching log value indices
// for a given map index.
type matcherResult struct {
mapIndex uint32
matches potentialMatches
}
// getAllMatches creates an instance for a given matcher and set of map indices,
// iterates through mapping layers and collects all results, then returns all
// results in the same order as the map indices were specified.
func getAllMatches(ctx context.Context, matcher matcher, mapIndices []uint32) ([]potentialMatches, error) {
instance := matcher.newInstance(mapIndices)
resultsMap := make(map[uint32]potentialMatches)
for layerIndex := uint32(0); len(resultsMap) < len(mapIndices); layerIndex++ {
results, err := instance.getMatchesForLayer(ctx, layerIndex)
if err != nil {
return nil, err
}
for _, result := range results {
resultsMap[result.mapIndex] = result.matches
}
}
matches := make([]potentialMatches, len(mapIndices))
for i, mapIndex := range mapIndices {
matches[i] = resultsMap[mapIndex]
}
return matches, nil
}
// singleMatcher implements matcher by returning matches for a single log value hash.
type singleMatcher struct {
backend MatcherBackend
value common.Hash
stats runtimeStats
}
// singleMatcherInstance is an instance of singleMatcher.
type singleMatcherInstance struct {
*singleMatcher
mapIndices []uint32
filterRows map[uint32][]FilterRow
}
// newInstance creates a new instance of singleMatcher.
func (m *singleMatcher) newInstance(mapIndices []uint32) matcherInstance {
filterRows := make(map[uint32][]FilterRow)
for _, idx := range mapIndices {
filterRows[idx] = []FilterRow{}
}
copiedIndices := make([]uint32, len(mapIndices))
copy(copiedIndices, mapIndices)
return &singleMatcherInstance{
singleMatcher: m,
mapIndices: copiedIndices,
filterRows: filterRows,
}
}
// getMatchesForLayer implements matcherInstance.
func (m *singleMatcherInstance) getMatchesForLayer(ctx context.Context, layerIndex uint32) (results []matcherResult, err error) {
var st int
m.stats.setState(&st, stOther)
params := m.backend.GetParams()
maskedMapIndex, rowIndex := uint32(math.MaxUint32), uint32(0)
for _, mapIndex := range m.mapIndices {
filterRows, ok := m.filterRows[mapIndex]
if !ok {
continue
}
if mm := params.maskedMapIndex(mapIndex, layerIndex); mm != maskedMapIndex {
// only recalculate rowIndex when necessary
maskedMapIndex = mm
rowIndex = params.rowIndex(mapIndex, layerIndex, m.value)
}
if layerIndex == 0 {
m.stats.setState(&st, stFetchFirst)
} else {
m.stats.setState(&st, stFetchMore)
}
filterRow, err := m.backend.GetFilterMapRow(ctx, mapIndex, rowIndex, layerIndex == 0)
if err != nil {
m.stats.setState(&st, stNone)
return nil, fmt.Errorf("failed to retrieve filter map %d row %d: %v", mapIndex, rowIndex, err)
}
m.stats.addAmount(st, int64(len(filterRow)))
m.stats.setState(&st, stOther)
filterRows = append(filterRows, filterRow)
if uint32(len(filterRow)) < params.maxRowLength(layerIndex) {
m.stats.setState(&st, stProcess)
matches := params.potentialMatches(filterRows, mapIndex, m.value)
m.stats.addAmount(st, int64(len(matches)))
results = append(results, matcherResult{
mapIndex: mapIndex,
matches: matches,
})
m.stats.setState(&st, stOther)
delete(m.filterRows, mapIndex)
} else {
m.filterRows[mapIndex] = filterRows
}
}
m.cleanMapIndices()
m.stats.setState(&st, stNone)
return results, nil
}
// dropIndices implements matcherInstance.
func (m *singleMatcherInstance) dropIndices(dropIndices []uint32) {
for _, mapIndex := range dropIndices {
delete(m.filterRows, mapIndex)
}
m.cleanMapIndices()
}
// cleanMapIndices removes map indices from the list if there is no matching
// filterRows entry because a result has been returned or the index has been
// dropped.
func (m *singleMatcherInstance) cleanMapIndices() {
var j int
for i, mapIndex := range m.mapIndices {
if _, ok := m.filterRows[mapIndex]; ok {
if i != j {
m.mapIndices[j] = mapIndex
}
j++
}
}
m.mapIndices = m.mapIndices[:j]
}
// matchAny combinines a set of matchers and returns a match for every position
// where any of the underlying matchers signaled a match. A zero-length matchAny
// acts as a "wild card" that signals a potential match at every position.
type matchAny []matcher
// matchAnyInstance is an instance of matchAny.
type matchAnyInstance struct {
matchAny
childInstances []matcherInstance
childResults map[uint32]matchAnyResults
}
// matchAnyResults is used by matchAnyInstance to collect results from all
// child matchers for a specific map index. Once all results has been received
// a merged result is returned for the given map and this structure is discarded.
type matchAnyResults struct {
matches []potentialMatches
done []bool
needMore int
}
// newInstance creates a new instance of matchAny.
func (m matchAny) newInstance(mapIndices []uint32) matcherInstance {
if len(m) == 1 {
return m[0].newInstance(mapIndices)
}
childResults := make(map[uint32]matchAnyResults)
for _, idx := range mapIndices {
childResults[idx] = matchAnyResults{
matches: make([]potentialMatches, len(m)),
done: make([]bool, len(m)),
needMore: len(m),
}
}
childInstances := make([]matcherInstance, len(m))
for i, matcher := range m {
childInstances[i] = matcher.newInstance(mapIndices)
}
return &matchAnyInstance{
matchAny: m,
childInstances: childInstances,
childResults: childResults,
}
}
// getMatchesForLayer implements matcherInstance.
func (m *matchAnyInstance) getMatchesForLayer(ctx context.Context, layerIndex uint32) (mergedResults []matcherResult, err error) {
if len(m.matchAny) == 0 {
// return "wild card" results (potentialMatches(nil) is interpreted as a
// potential match at every log value index of the map).
mergedResults = make([]matcherResult, len(m.childResults))
var i int
for mapIndex := range m.childResults {
mergedResults[i] = matcherResult{mapIndex: mapIndex, matches: nil}
i++
}
return mergedResults, nil
}
for i, childInstance := range m.childInstances {
results, err := childInstance.getMatchesForLayer(ctx, layerIndex)
if err != nil {
return nil, fmt.Errorf("failed to evaluate child matcher on layer %d: %v", layerIndex, err)
}
for _, result := range results {
mr, ok := m.childResults[result.mapIndex]
if !ok || mr.done[i] {
continue
}
mr.done[i] = true
mr.matches[i] = result.matches
mr.needMore--
if mr.needMore == 0 || result.matches == nil {
mergedResults = append(mergedResults, matcherResult{
mapIndex: result.mapIndex,
matches: mergeResults(mr.matches),
})
delete(m.childResults, result.mapIndex)
} else {
m.childResults[result.mapIndex] = mr
}
}
}
return mergedResults, nil
}
// dropIndices implements matcherInstance.
func (m *matchAnyInstance) dropIndices(dropIndices []uint32) {
for _, childInstance := range m.childInstances {
childInstance.dropIndices(dropIndices)
}
for _, mapIndex := range dropIndices {
delete(m.childResults, mapIndex)
}
}
// mergeResults merges multiple lists of matches into a single one, preserving
// ascending order and filtering out any duplicates.
func mergeResults(results []potentialMatches) potentialMatches {
if len(results) == 0 {
return nil
}
var sumLen int
for _, res := range results {
if res == nil {
// nil is a wild card; all indices in map range are potential matches
return nil
}
sumLen += len(res)
}
merged := make(potentialMatches, 0, sumLen)
for {
best := -1
for i, res := range results {
if len(res) == 0 {
continue
}
if best < 0 || res[0] < results[best][0] {
best = i
}
}
if best < 0 {
return merged
}
if len(merged) == 0 || results[best][0] > merged[len(merged)-1] {
merged = append(merged, results[best][0])
}
results[best] = results[best][1:]
}
}
// matchSequence combines two matchers, a "base" and a "next" matcher with a
// positive integer offset so that the resulting matcher signals a match at log
// value index X when the base matcher returns a match at X and the next matcher
// gives a match at X+offset. Note that matchSequence can be used recursively to
// detect any log value sequence.
type matchSequence struct {
params *Params
base, next matcher
offset uint64
statsLock sync.Mutex
baseStats, nextStats matchOrderStats
}
// newInstance creates a new instance of matchSequence.
func (m *matchSequence) newInstance(mapIndices []uint32) matcherInstance {
// determine set of indices to request from next matcher
nextIndices := make([]uint32, 0, len(mapIndices)*3/2)
needMatched := make(map[uint32]struct{})
baseRequested := make(map[uint32]struct{})
nextRequested := make(map[uint32]struct{})
for _, mapIndex := range mapIndices {
needMatched[mapIndex] = struct{}{}
baseRequested[mapIndex] = struct{}{}
if _, ok := nextRequested[mapIndex]; !ok {
nextIndices = append(nextIndices, mapIndex)
nextRequested[mapIndex] = struct{}{}
}
nextIndices = append(nextIndices, mapIndex+1)
nextRequested[mapIndex+1] = struct{}{}
}
return &matchSequenceInstance{
matchSequence: m,
baseInstance: m.base.newInstance(mapIndices),
nextInstance: m.next.newInstance(nextIndices),
needMatched: needMatched,
baseRequested: baseRequested,
nextRequested: nextRequested,
baseResults: make(map[uint32]potentialMatches),
nextResults: make(map[uint32]potentialMatches),
}
}
// matchOrderStats collects statistics about the evaluating cost and the
// occurrence of empty result sets from both base and next child matchers.
// This allows the optimization of the evaluation order by evaluating the
// child first that is cheaper and/or gives empty results more often and not
// evaluating the other child in most cases.
// Note that matchOrderStats is specific to matchSequence and the results are
// carried over to future instances as the results are mostly useful when
// evaluating layer zero of each instance. For this reason it should be used
// in a thread safe way as is may be accessed from multiple worker goroutines.
type matchOrderStats struct {
totalCount, nonEmptyCount, totalCost uint64
}
// add collects statistics after a child has been evaluated for a certain layer.
func (ms *matchOrderStats) add(empty bool, layerIndex uint32) {
if empty && layerIndex != 0 {
// matchers may be evaluated for higher layers after all results have
// been returned. Also, empty results are not relevant when previous
// layers yielded matches already, so these cases can be ignored.
return
}
ms.totalCount++
if !empty {
ms.nonEmptyCount++
}
ms.totalCost += uint64(layerIndex + 1)
}
// mergeStats merges two sets of matchOrderStats.
func (ms *matchOrderStats) mergeStats(add matchOrderStats) {
ms.totalCount += add.totalCount
ms.nonEmptyCount += add.nonEmptyCount
ms.totalCost += add.totalCost
}
// baseFirst returns true if the base child matcher should be evaluated first.
func (m *matchSequence) baseFirst() bool {
m.statsLock.Lock()
bf := float64(m.baseStats.totalCost)*float64(m.nextStats.totalCount)+
float64(m.baseStats.nonEmptyCount)*float64(m.nextStats.totalCost) <
float64(m.baseStats.totalCost)*float64(m.nextStats.nonEmptyCount)+
float64(m.nextStats.totalCost)*float64(m.baseStats.totalCount)
m.statsLock.Unlock()
return bf
}
// mergeBaseStats merges a set of matchOrderStats into the base matcher stats.
func (m *matchSequence) mergeBaseStats(stats matchOrderStats) {
m.statsLock.Lock()
m.baseStats.mergeStats(stats)
m.statsLock.Unlock()
}
// mergeNextStats merges a set of matchOrderStats into the next matcher stats.
func (m *matchSequence) mergeNextStats(stats matchOrderStats) {
m.statsLock.Lock()
m.nextStats.mergeStats(stats)
m.statsLock.Unlock()
}
// newMatchSequence creates a recursive sequence matcher from a list of underlying
// matchers. The resulting matcher signals a match at log value index X when each
// underlying matcher matchers[i] returns a match at X+i.
func newMatchSequence(params *Params, matchers []matcher) matcher {
if len(matchers) == 0 {
panic("zero length sequence matchers are not allowed")
}
if len(matchers) == 1 {
return matchers[0]
}
return &matchSequence{
params: params,
base: newMatchSequence(params, matchers[:len(matchers)-1]),
next: matchers[len(matchers)-1],
offset: uint64(len(matchers) - 1),
}
}
// matchSequenceInstance is an instance of matchSequence.
type matchSequenceInstance struct {
*matchSequence
baseInstance, nextInstance matcherInstance
baseRequested, nextRequested, needMatched map[uint32]struct{}
baseResults, nextResults map[uint32]potentialMatches
}
// getMatchesForLayer implements matcherInstance.
func (m *matchSequenceInstance) getMatchesForLayer(ctx context.Context, layerIndex uint32) (matchedResults []matcherResult, err error) {
// decide whether to evaluate base or next matcher first
baseFirst := m.baseFirst()
if baseFirst {
if err := m.evalBase(ctx, layerIndex); err != nil {
return nil, err
}
}
if err := m.evalNext(ctx, layerIndex); err != nil {
return nil, err
}
if !baseFirst {
if err := m.evalBase(ctx, layerIndex); err != nil {
return nil, err
}
}
// evaluate and return matched results where possible
for mapIndex := range m.needMatched {
if _, ok := m.baseRequested[mapIndex]; ok {
continue
}
if _, ok := m.nextRequested[mapIndex]; ok {
continue
}
if _, ok := m.nextRequested[mapIndex+1]; ok {
continue
}
matchedResults = append(matchedResults, matcherResult{
mapIndex: mapIndex,
matches: m.params.matchResults(mapIndex, m.offset, m.baseResults[mapIndex], m.nextResults[mapIndex], m.nextResults[mapIndex+1]),
})
delete(m.needMatched, mapIndex)
}
return matchedResults, nil
}
// dropIndices implements matcherInstance.
func (m *matchSequenceInstance) dropIndices(dropIndices []uint32) {
for _, mapIndex := range dropIndices {
delete(m.needMatched, mapIndex)
}
var dropBase, dropNext []uint32
for _, mapIndex := range dropIndices {
if m.dropBase(mapIndex) {
dropBase = append(dropBase, mapIndex)
}
}
m.baseInstance.dropIndices(dropBase)
for _, mapIndex := range dropIndices {
if m.dropNext(mapIndex) {
dropNext = append(dropNext, mapIndex)
}
if m.dropNext(mapIndex + 1) {
dropNext = append(dropNext, mapIndex+1)
}
}
m.nextInstance.dropIndices(dropNext)
}
// evalBase evaluates the base child matcher and drops map indices from the
// next matcher if possible.
func (m *matchSequenceInstance) evalBase(ctx context.Context, layerIndex uint32) error {
results, err := m.baseInstance.getMatchesForLayer(ctx, layerIndex)
if err != nil {
return fmt.Errorf("failed to evaluate base matcher on layer %d: %v", layerIndex, err)
}
var (
dropIndices []uint32
stats matchOrderStats
)
for _, r := range results {
m.baseResults[r.mapIndex] = r.matches
delete(m.baseRequested, r.mapIndex)
stats.add(r.matches != nil && len(r.matches) == 0, layerIndex)
}
m.mergeBaseStats(stats)
for _, r := range results {
if m.dropNext(r.mapIndex) {
dropIndices = append(dropIndices, r.mapIndex)
}
if m.dropNext(r.mapIndex + 1) {
dropIndices = append(dropIndices, r.mapIndex+1)
}
}
if len(dropIndices) > 0 {
m.nextInstance.dropIndices(dropIndices)
}
return nil
}
// evalNext evaluates the next child matcher and drops map indices from the
// base matcher if possible.
func (m *matchSequenceInstance) evalNext(ctx context.Context, layerIndex uint32) error {
results, err := m.nextInstance.getMatchesForLayer(ctx, layerIndex)
if err != nil {
return fmt.Errorf("failed to evaluate next matcher on layer %d: %v", layerIndex, err)
}
var (
dropIndices []uint32
stats matchOrderStats
)
for _, r := range results {
m.nextResults[r.mapIndex] = r.matches
delete(m.nextRequested, r.mapIndex)
stats.add(r.matches != nil && len(r.matches) == 0, layerIndex)
}
m.mergeNextStats(stats)
for _, r := range results {
if r.mapIndex > 0 && m.dropBase(r.mapIndex-1) {
dropIndices = append(dropIndices, r.mapIndex-1)
}
if m.dropBase(r.mapIndex) {
dropIndices = append(dropIndices, r.mapIndex)
}
}
if len(dropIndices) > 0 {
m.baseInstance.dropIndices(dropIndices)
}
return nil
}
// dropBase checks whether the given map index can be dropped from the base
// matcher based on the known results from the next matcher and removes it
// from the internal requested set and returns true if possible.
func (m *matchSequenceInstance) dropBase(mapIndex uint32) bool {
if _, ok := m.baseRequested[mapIndex]; !ok {
return false
}
if _, ok := m.needMatched[mapIndex]; ok {
if next := m.nextResults[mapIndex]; next == nil ||
(len(next) > 0 && next[len(next)-1] >= (uint64(mapIndex)<<m.params.logValuesPerMap)+m.offset) {
return false
}
if nextNext := m.nextResults[mapIndex+1]; nextNext == nil ||
(len(nextNext) > 0 && nextNext[0] < (uint64(mapIndex+1)<<m.params.logValuesPerMap)+m.offset) {
return false
}
}
delete(m.baseRequested, mapIndex)
return true
}
// dropNext checks whether the given map index can be dropped from the next
// matcher based on the known results from the base matcher and removes it
// from the internal requested set and returns true if possible.
func (m *matchSequenceInstance) dropNext(mapIndex uint32) bool {
if _, ok := m.nextRequested[mapIndex]; !ok {
return false
}
if _, ok := m.needMatched[mapIndex-1]; ok {
if prevBase := m.baseResults[mapIndex-1]; prevBase == nil ||
(len(prevBase) > 0 && prevBase[len(prevBase)-1]+m.offset >= (uint64(mapIndex)<<m.params.logValuesPerMap)) {
return false
}
}
if _, ok := m.needMatched[mapIndex]; ok {
if base := m.baseResults[mapIndex]; base == nil ||
(len(base) > 0 && base[0]+m.offset < (uint64(mapIndex+1)<<m.params.logValuesPerMap)) {
return false
}
}
delete(m.nextRequested, mapIndex)
return true
}
// matchResults returns a list of sequence matches for the given mapIndex and
// offset based on the base matcher's results at mapIndex and the next matcher's
// results at mapIndex and mapIndex+1. Note that acquiring nextNextRes may be
// skipped and it can be substituted with an empty list if baseRes has no potential
// matches that could be sequence matched with anything that could be in nextNextRes.
func (params *Params) matchResults(mapIndex uint32, offset uint64, baseRes, nextRes, nextNextRes potentialMatches) potentialMatches {
if nextRes == nil || (baseRes != nil && len(baseRes) == 0) {
// if nextRes is a wild card or baseRes is empty then the sequence matcher
// result equals baseRes.
return baseRes
}
if len(nextRes) > 0 {
// discard items from nextRes whose corresponding base matcher results
// with the negative offset applied would be located at mapIndex-1.
start := 0
for start < len(nextRes) && nextRes[start] < uint64(mapIndex)<<params.logValuesPerMap+offset {
start++
}
nextRes = nextRes[start:]
}
if len(nextNextRes) > 0 {
// discard items from nextNextRes whose corresponding base matcher results
// with the negative offset applied would still be located at mapIndex+1.
stop := 0
for stop < len(nextNextRes) && nextNextRes[stop] < uint64(mapIndex+1)<<params.logValuesPerMap+offset {
stop++
}
nextNextRes = nextNextRes[:stop]
}
maxLen := len(nextRes) + len(nextNextRes)
if maxLen == 0 {
return nextRes
}
if len(baseRes) < maxLen {
maxLen = len(baseRes)
}
// iterate through baseRes, nextRes and nextNextRes and collect matching results.
matchedRes := make(potentialMatches, 0, maxLen)
for _, nextRes := range []potentialMatches{nextRes, nextNextRes} {
if baseRes != nil {
for len(nextRes) > 0 && len(baseRes) > 0 {
if nextRes[0] > baseRes[0]+offset {
baseRes = baseRes[1:]
} else if nextRes[0] < baseRes[0]+offset {
nextRes = nextRes[1:]
} else {
matchedRes = append(matchedRes, baseRes[0])
baseRes = baseRes[1:]
nextRes = nextRes[1:]
}
}
} else {
// baseRes is a wild card so just return next matcher results with
// negative offset.
for len(nextRes) > 0 {
matchedRes = append(matchedRes, nextRes[0]-offset)
nextRes = nextRes[1:]
}
}
}
return matchedRes
}
// runtimeStats collects processing time statistics while searching in the log
// index. Used only when the doRuntimeStats global flag is true.
type runtimeStats struct {
dt, cnt, amount [stCount]int64
}
const (
stNone = iota
stFetchFirst
stFetchMore
stProcess
stGetLog
stOther
stCount
)
var stNames = []string{"", "fetchFirst", "fetchMore", "process", "getLog", "other"}
// set sets the processing state to one of the pre-defined constants.
// Processing time spent in each state is measured separately.
func (ts *runtimeStats) setState(state *int, newState int) {
if !doRuntimeStats || newState == *state {
return
}
now := int64(mclock.Now())
atomic.AddInt64(&ts.dt[*state], now)
atomic.AddInt64(&ts.dt[newState], -now)
atomic.AddInt64(&ts.cnt[newState], 1)
*state = newState
}
func (ts *runtimeStats) addAmount(state int, amount int64) {
atomic.AddInt64(&ts.amount[state], amount)
}
// print prints the collected statistics.
func (ts *runtimeStats) print() {
for i := 1; i < stCount; i++ {
log.Info("Matcher stats", "name", stNames[i], "dt", time.Duration(ts.dt[i]), "count", ts.cnt[i], "amount", ts.amount[i])
}
}

View file

@ -0,0 +1,206 @@
// Copyright 2024 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 <http://www.gnu.org/licenses/>.
package filtermaps
import (
"context"
"github.com/ethereum/go-ethereum/core/types"
)
// FilterMapsMatcherBackend implements MatcherBackend.
type FilterMapsMatcherBackend struct {
f *FilterMaps
// these fields should be accessed under f.matchersLock mutex.
valid bool
firstValid, lastValid uint64
syncCh chan SyncRange
}
// NewMatcherBackend returns a FilterMapsMatcherBackend after registering it in
// the active matcher set.
// Note that Close should always be called when the matcher is no longer used.
func (f *FilterMaps) NewMatcherBackend() *FilterMapsMatcherBackend {
f.indexLock.RLock()
f.matchersLock.Lock()
defer func() {
f.matchersLock.Unlock()
f.indexLock.RUnlock()
}()
fm := &FilterMapsMatcherBackend{
f: f,
valid: f.indexedRange.initialized && f.indexedRange.afterLastIndexedBlock > f.indexedRange.firstIndexedBlock,
firstValid: f.indexedRange.firstIndexedBlock,
lastValid: f.indexedRange.afterLastIndexedBlock - 1,
}
f.matchers[fm] = struct{}{}
return fm
}
// GetParams returns the filtermaps parameters.
// GetParams implements MatcherBackend.
func (fm *FilterMapsMatcherBackend) GetParams() *Params {
return &fm.f.Params
}
// Close removes the matcher from the set of active matchers and ensures that
// any SyncLogIndex calls are cancelled.
// Close implements MatcherBackend.
func (fm *FilterMapsMatcherBackend) Close() {
fm.f.matchersLock.Lock()
defer fm.f.matchersLock.Unlock()
delete(fm.f.matchers, fm)
}
// GetFilterMapRow returns the given row of the given map. If the row is empty
// then a non-nil zero length row is returned. If baseLayerOnly is true then
// only the first baseRowLength entries of the row are guaranteed to be
// returned.
// Note that the returned slices should not be modified, they should be copied
// on write.
// GetFilterMapRow implements MatcherBackend.
func (fm *FilterMapsMatcherBackend) GetFilterMapRow(ctx context.Context, mapIndex, rowIndex uint32, baseLayerOnly bool) (FilterRow, error) {
return fm.f.getFilterMapRow(mapIndex, rowIndex, baseLayerOnly)
}
// GetBlockLvPointer returns the starting log value index where the log values
// generated by the given block are located. If blockNumber is beyond the current
// head then the first unoccupied log value index is returned.
// GetBlockLvPointer implements MatcherBackend.
func (fm *FilterMapsMatcherBackend) GetBlockLvPointer(ctx context.Context, blockNumber uint64) (uint64, error) {
fm.f.indexLock.RLock()
defer fm.f.indexLock.RUnlock()
return fm.f.getBlockLvPointer(blockNumber)
}
// GetLogByLvIndex returns the log at the given log value index.
// Note that this function assumes that the log index structure is consistent
// with the canonical chain at the point where the given log value index points.
// If this is not the case then an invalid result may be returned or certain
// logs might not be returned at all.
// No error is returned though because of an inconsistency between the chain and
// the log index. It is the caller's responsibility to verify this consistency
// using SyncLogIndex and re-process certain blocks if necessary.
// GetLogByLvIndex implements MatcherBackend.
func (fm *FilterMapsMatcherBackend) GetLogByLvIndex(ctx context.Context, lvIndex uint64) (*types.Log, error) {
fm.f.indexLock.RLock()
defer fm.f.indexLock.RUnlock()
return fm.f.getLogByLvIndex(lvIndex)
}
// synced signals to the matcher that has triggered a synchronisation that it
// has been finished and the log index is consistent with the chain head passed
// as a parameter.
// Note that if the log index head was far behind the chain head then it might not
// be synced up to the given head in a single step. Still, the latest chain head
// should be passed as a parameter and the existing log index should be consistent
// with that chain.
func (fm *FilterMapsMatcherBackend) synced() {
fm.f.indexLock.RLock()
fm.f.matchersLock.Lock()
defer func() {
fm.f.matchersLock.Unlock()
fm.f.indexLock.RUnlock()
}()
var (
indexed bool
lastIndexed, subLastIndexed uint64
)
if !fm.f.indexedRange.headBlockIndexed {
subLastIndexed = 1
}
if fm.f.indexedRange.afterLastIndexedBlock-subLastIndexed > fm.f.indexedRange.firstIndexedBlock {
indexed, lastIndexed = true, fm.f.indexedRange.afterLastIndexedBlock-subLastIndexed-1
}
fm.syncCh <- SyncRange{
HeadNumber: fm.f.indexedView.headNumber,
Valid: fm.valid,
FirstValid: fm.firstValid,
LastValid: fm.lastValid,
Indexed: indexed,
FirstIndexed: fm.f.indexedRange.firstIndexedBlock,
LastIndexed: lastIndexed,
}
fm.valid = indexed
fm.firstValid = fm.f.indexedRange.firstIndexedBlock
fm.lastValid = lastIndexed
fm.syncCh = nil
}
// SyncLogIndex ensures that the log index is consistent with the current state
// of the chain and is synced up to the current head. It blocks until this state
// is achieved or the context is cancelled.
// If successful, it returns a SyncRange that contains the latest chain head,
// the indexed range that is currently consistent with the chain and the valid
// range that has not been changed and has been consistent with all states of the
// chain since the previous SyncLogIndex or the creation of the matcher backend.
func (fm *FilterMapsMatcherBackend) SyncLogIndex(ctx context.Context) (SyncRange, error) {
if fm.f.disabled {
return SyncRange{HeadNumber: fm.f.targetView.headNumber}, nil
}
syncCh := make(chan SyncRange, 1)
fm.f.matchersLock.Lock()
fm.syncCh = syncCh
fm.f.matchersLock.Unlock()
select {
case fm.f.matcherSyncCh <- fm:
case <-ctx.Done():
return SyncRange{}, ctx.Err()
}
select {
case vr := <-syncCh:
return vr, nil
case <-ctx.Done():
return SyncRange{}, ctx.Err()
}
}
// updateMatchersValidRange iterates through active matchers and limits their
// valid range with the current indexed range. This function should be called
// whenever a part of the log index has been removed, before adding new blocks
// to it.
// Note that this function assumes that the index read lock is being held.
func (f *FilterMaps) updateMatchersValidRange() {
f.matchersLock.Lock()
defer f.matchersLock.Unlock()
for fm := range f.matchers {
if !f.indexedRange.hasIndexedBlocks() {
fm.valid = false
}
if !fm.valid {
continue
}
if fm.firstValid < f.indexedRange.firstIndexedBlock {
fm.firstValid = f.indexedRange.firstIndexedBlock
}
if fm.lastValid >= f.indexedRange.afterLastIndexedBlock {
fm.lastValid = f.indexedRange.afterLastIndexedBlock - 1
}
if fm.firstValid > fm.lastValid {
fm.valid = false
}
}
}

View file

@ -0,0 +1,87 @@
// Copyright 2024 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 <http://www.gnu.org/licenses/>.
package filtermaps
import (
"context"
crand "crypto/rand"
"math/rand"
"testing"
"github.com/ethereum/go-ethereum/common"
)
func TestMatcher(t *testing.T) {
ts := newTestSetup(t)
defer ts.close()
ts.chain.addBlocks(100, 10, 10, 4, true)
ts.setHistory(0, false)
ts.fm.WaitIdle()
for i := 0; i < 2000; i++ {
bhash := ts.chain.canonical[rand.Intn(len(ts.chain.canonical))]
receipts := ts.chain.receipts[bhash]
if len(receipts) == 0 {
continue
}
receipt := receipts[rand.Intn(len(receipts))]
if len(receipt.Logs) == 0 {
continue
}
log := receipt.Logs[rand.Intn(len(receipt.Logs))]
var ok bool
addresses := make([]common.Address, rand.Intn(3))
for i := range addresses {
crand.Read(addresses[i][:])
}
if len(addresses) > 0 {
addresses[rand.Intn(len(addresses))] = log.Address
ok = true
}
topics := make([][]common.Hash, rand.Intn(len(log.Topics)+1))
for j := range topics {
topics[j] = make([]common.Hash, rand.Intn(3))
for i := range topics[j] {
crand.Read(topics[j][i][:])
}
if len(topics[j]) > 0 {
topics[j][rand.Intn(len(topics[j]))] = log.Topics[j]
ok = true
}
}
if !ok {
continue // cannot search for match-all pattern
}
mb := ts.fm.NewMatcherBackend()
logs, err := GetPotentialMatches(context.Background(), mb, 0, 1000, addresses, topics)
mb.Close()
if err != nil {
t.Fatalf("Log search error: %v", err)
}
var found bool
for _, l := range logs {
if l == log {
found = true
break
}
}
if !found {
t.Fatalf("Log search did not return expected log (addresses: %v, topics: %v, expected log: %v)", addresses, topics, *log)
}
}
}

212
core/filtermaps/math.go Normal file
View file

@ -0,0 +1,212 @@
// Copyright 2024 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 <http://www.gnu.org/licenses/>.
package filtermaps
import (
"crypto/sha256"
"encoding/binary"
"hash/fnv"
"math"
"sort"
"github.com/ethereum/go-ethereum/common"
)
// Params defines the basic parameters of the log index structure.
type Params struct {
logMapHeight uint // log2(mapHeight)
logMapWidth uint // log2(mapWidth)
logMapsPerEpoch uint // log2(mapsPerEpoch)
logValuesPerMap uint // log2(logValuesPerMap)
baseRowLengthRatio uint // baseRowLength / average row length
logLayerDiff uint // maxRowLength log2 growth per layer
// derived fields
mapHeight uint32 // filter map height (number of rows)
mapsPerEpoch uint32 // number of maps in an epoch
baseRowLength uint32 // maximum number of log values per row on layer 0
valuesPerMap uint64 // number of log values marked on each filter map
// not affecting consensus
baseRowGroupLength uint32 // length of base row groups in local database
}
// DefaultParams is the set of parameters used on mainnet.
var DefaultParams = Params{
logMapHeight: 16,
logMapWidth: 24,
logMapsPerEpoch: 10,
logValuesPerMap: 16,
baseRowGroupLength: 32,
baseRowLengthRatio: 8,
logLayerDiff: 4,
}
// RangeTestParams puts one log value per epoch, ensuring block exact tail unindexing for testing
var RangeTestParams = Params{
logMapHeight: 4,
logMapWidth: 24,
logMapsPerEpoch: 0,
logValuesPerMap: 0,
baseRowGroupLength: 32,
baseRowLengthRatio: 16, // baseRowLength >= 1
logLayerDiff: 4,
}
// deriveFields calculates the derived fields of the parameter set.
func (p *Params) deriveFields() {
p.mapHeight = uint32(1) << p.logMapHeight
p.mapsPerEpoch = uint32(1) << p.logMapsPerEpoch
p.valuesPerMap = uint64(1) << p.logValuesPerMap
p.baseRowLength = uint32(p.valuesPerMap * uint64(p.baseRowLengthRatio) / uint64(p.mapHeight))
}
// addressValue returns the log value hash of a log emitting address.
func addressValue(address common.Address) common.Hash {
var result common.Hash
hasher := sha256.New()
hasher.Write(address[:])
hasher.Sum(result[:0])
return result
}
// topicValue returns the log value hash of a log topic.
func topicValue(topic common.Hash) common.Hash {
var result common.Hash
hasher := sha256.New()
hasher.Write(topic[:])
hasher.Sum(result[:0])
return result
}
// rowIndex returns the row index in which the given log value should be marked
// on the given map and mapping layer. Note that row assignments are re-shuffled
// with a different frequency on each mapping layer, allowing efficient disk
// access and Merkle proofs for long sections of short rows on lower order
// layers while avoiding putting too many heavy rows next to each other on
// higher order layers.
func (p *Params) rowIndex(mapIndex, layerIndex uint32, logValue common.Hash) uint32 {
hasher := sha256.New()
hasher.Write(logValue[:])
var indexEnc [8]byte
binary.LittleEndian.PutUint32(indexEnc[0:4], p.maskedMapIndex(mapIndex, layerIndex))
binary.LittleEndian.PutUint32(indexEnc[4:8], layerIndex)
hasher.Write(indexEnc[:])
var hash common.Hash
hasher.Sum(hash[:0])
return binary.LittleEndian.Uint32(hash[:4]) % p.mapHeight
}
// columnIndex returns the column index where the given log value at the given
// position should be marked.
func (p *Params) columnIndex(lvIndex uint64, logValue *common.Hash) uint32 {
var indexEnc [8]byte
binary.LittleEndian.PutUint64(indexEnc[:], lvIndex)
// Note: reusing the hasher brings practically no performance gain and would
// require passing it through the entire matcher logic because of multi-thread
// matching
hasher := fnv.New64a()
hasher.Write(indexEnc[:])
hasher.Write(logValue[:])
hash := hasher.Sum64()
hashBits := p.logMapWidth - p.logValuesPerMap
return uint32(lvIndex%p.valuesPerMap)<<hashBits + (uint32(hash>>(64-hashBits)) ^ uint32(hash)>>(32-hashBits))
}
// maxRowLength returns the maximum length filter rows are populated up to
// when using the given mapping layer. A log value can be marked on the map
// according to a given mapping layer if the row mapping on that layer points
// to a row that has not yet reached the maxRowLength belonging to that layer.
// This means that a row that is considered full on a given layer may still be
// extended further on a higher order layer.
// Each value is marked on the lowest order layer possible, assuming that marks
// are added in ascending log value index order.
// When searching for a log value one should consider all layers and process
// corresponding rows up until the first one where the row mapped to the given
// layer is not full.
func (p *Params) maxRowLength(layerIndex uint32) uint32 {
logLayerDiff := uint(layerIndex) * p.logLayerDiff
if logLayerDiff > p.logMapsPerEpoch {
logLayerDiff = p.logMapsPerEpoch
}
return p.baseRowLength << logLayerDiff
}
// maskedMapIndex returns the index used for row mapping calculation on the
// given layer. On layer zero the mapping changes once per epoch, then the
// frequency of re-mapping increases with every new layer until it reaches
// the frequency where it is different for every mapIndex.
func (p *Params) maskedMapIndex(mapIndex, layerIndex uint32) uint32 {
logLayerDiff := uint(layerIndex) * p.logLayerDiff
if logLayerDiff > p.logMapsPerEpoch {
logLayerDiff = p.logMapsPerEpoch
}
return mapIndex & (uint32(math.MaxUint32) << (p.logMapsPerEpoch - logLayerDiff))
}
// potentialMatches returns the list of log value indices potentially matching
// the given log value hash in the range of the filter map the row belongs to.
// Note that the list of indices is always sorted and potential duplicates are
// removed. Though the column indices are stored in the same order they were
// added and therefore the true matches are automatically reverse transformed
// in the right order, false positives can ruin this property. Since these can
// only be separated from true matches after the combined pattern matching of the
// outputs of individual log value matchers and this pattern matcher assumes a
// sorted and duplicate-free list of indices, we should ensure these properties
// here.
func (p *Params) potentialMatches(rows []FilterRow, mapIndex uint32, logValue common.Hash) potentialMatches {
results := make(potentialMatches, 0, 8)
mapFirst := uint64(mapIndex) << p.logValuesPerMap
for i, row := range rows {
rowLen, maxLen := len(row), int(p.maxRowLength(uint32(i)))
if rowLen > maxLen {
rowLen = maxLen // any additional entries are generated by another log value on a higher mapping layer
}
for i := 0; i < rowLen; i++ {
if potentialMatch := mapFirst + uint64(row[i]>>(p.logMapWidth-p.logValuesPerMap)); row[i] == p.columnIndex(potentialMatch, &logValue) {
results = append(results, potentialMatch)
}
}
if rowLen < maxLen {
break
}
if i == len(rows)-1 {
panic("potentialMatches: insufficient list of row alternatives")
}
}
sort.Sort(results)
// remove duplicates
j := 0
for i, match := range results {
if i == 0 || match != results[i-1] {
results[j] = results[i]
j++
}
}
return results[:j]
}
// potentialMatches is a strictly monotonically increasing list of log value
// indices in the range of a filter map that are potential matches for certain
// filter criteria.
// potentialMatches implements sort.Interface.
// Note that nil is used as a wildcard and therefore means that all log value
// indices in the filter map range are potential matches. If there are no
// potential matches in the given map's range then an empty slice should be used.
type potentialMatches []uint64
func (p potentialMatches) Len() int { return len(p) }
func (p potentialMatches) Less(i, j int) bool { return p[i] < p[j] }
func (p potentialMatches) Swap(i, j int) { p[i], p[j] = p[j], p[i] }

View file

@ -0,0 +1,149 @@
// Copyright 2024 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 <http://www.gnu.org/licenses/>.
package filtermaps
import (
crand "crypto/rand"
"math/rand"
"testing"
"github.com/ethereum/go-ethereum/common"
)
func TestSingleMatch(t *testing.T) {
params := DefaultParams
params.deriveFields()
for count := 0; count < 100000; count++ {
// generate a row with a single random entry
mapIndex := rand.Uint32()
lvIndex := uint64(mapIndex)<<params.logValuesPerMap + uint64(rand.Intn(int(params.valuesPerMap)))
var lvHash common.Hash
crand.Read(lvHash[:])
row := FilterRow{params.columnIndex(lvIndex, &lvHash)}
matches := params.potentialMatches([]FilterRow{row}, mapIndex, lvHash)
// check if it has been reverse transformed correctly
if len(matches) != 1 {
t.Fatalf("Invalid length of matches (got %d, expected 1)", len(matches))
}
if matches[0] != lvIndex {
if len(matches) != 1 {
t.Fatalf("Incorrect match returned (got %d, expected %d)", matches[0], lvIndex)
}
}
}
}
const (
testPmCount = 50
testPmLen = 1000
)
func TestPotentialMatches(t *testing.T) {
params := DefaultParams
params.deriveFields()
var falsePositives int
for count := 0; count < testPmCount; count++ {
mapIndex := rand.Uint32()
lvStart := uint64(mapIndex) << params.logValuesPerMap
var row FilterRow
lvIndices := make([]uint64, testPmLen)
lvHashes := make([]common.Hash, testPmLen+1)
for i := range lvIndices {
// add testPmLen single entries with different log value hashes at different indices
lvIndices[i] = lvStart + uint64(rand.Intn(int(params.valuesPerMap)))
crand.Read(lvHashes[i][:])
row = append(row, params.columnIndex(lvIndices[i], &lvHashes[i]))
}
// add the same log value hash at the first testPmLen log value indices of the map's range
crand.Read(lvHashes[testPmLen][:])
for lvIndex := lvStart; lvIndex < lvStart+testPmLen; lvIndex++ {
row = append(row, params.columnIndex(lvIndex, &lvHashes[testPmLen]))
}
// randomly duplicate some entries
for i := 0; i < testPmLen; i++ {
row = append(row, row[rand.Intn(len(row))])
}
// randomly mix up order of elements
for i := len(row) - 1; i > 0; i-- {
j := rand.Intn(i)
row[i], row[j] = row[j], row[i]
}
// split up into a list of rows if longer than allowed
var rows []FilterRow
for layerIndex := uint32(0); row != nil; layerIndex++ {
maxLen := int(params.maxRowLength(layerIndex))
if len(row) > maxLen {
rows = append(rows, row[:maxLen])
row = row[maxLen:]
} else {
rows = append(rows, row)
row = nil
}
}
// check retrieved matches while also counting false positives
for i, lvHash := range lvHashes {
matches := params.potentialMatches(rows, mapIndex, lvHash)
if i < testPmLen {
// check single entry match
if len(matches) < 1 {
t.Fatalf("Invalid length of matches (got %d, expected >=1)", len(matches))
}
var found bool
for _, lvi := range matches {
if lvi == lvIndices[i] {
found = true
} else {
falsePositives++
}
}
if !found {
t.Fatalf("Expected match not found (got %v, expected %d)", matches, lvIndices[i])
}
} else {
// check "long series" match
if len(matches) < testPmLen {
t.Fatalf("Invalid length of matches (got %d, expected >=%d)", len(matches), testPmLen)
}
// since results are ordered, first testPmLen entries should always match exactly
for j := 0; j < testPmLen; j++ {
if matches[j] != lvStart+uint64(j) {
t.Fatalf("Incorrect match at index %d (got %d, expected %d)", j, matches[j], lvStart+uint64(j))
}
}
// the rest are false positives
falsePositives += len(matches) - testPmLen
}
}
}
// Whenever looking for a certain log value hash, each entry in the row that
// was generated by another log value hash (a "foreign entry") has a
// valuesPerMap // 2^32 chance of yielding a false positive if the reverse
// transformed 32 bit integer is by random chance less than valuesPerMap and
// is therefore considered a potentially valid match.
// We have testPmLen unique hash entries and a testPmLen long series of entries
// for the same hash. For each of the testPmLen unique hash entries there are
// testPmLen*2-1 foreign entries while for the long series there are testPmLen
// foreign entries. This means that after performing all these filtering runs,
// we have processed 2*testPmLen^2 foreign entries, which given us an estimate
// of how many false positives to expect.
expFalse := int(uint64(testPmCount*testPmLen*testPmLen*2) * params.valuesPerMap >> params.logMapWidth)
if falsePositives < expFalse/2 || falsePositives > expFalse*3/2 {
t.Fatalf("False positive rate out of expected range (got %d, expected %d +-50%%)", falsePositives, expFalse)
}
}

View file

@ -256,7 +256,7 @@ func (e *GenesisMismatchError) Error() string {
// ChainOverrides contains the changes to chain config.
type ChainOverrides struct {
OverrideCancun *uint64
OverridePrague *uint64
OverrideVerkle *uint64
}
@ -265,8 +265,8 @@ func (o *ChainOverrides) apply(cfg *params.ChainConfig) error {
if o == nil || cfg == nil {
return nil
}
if o.OverrideCancun != nil {
cfg.CancunTime = o.OverrideCancun
if o.OverridePrague != nil {
cfg.PragueTime = o.OverridePrague
}
if o.OverrideVerkle != nil {
cfg.VerkleTime = o.OverrideVerkle
@ -386,7 +386,7 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *triedb.Database, g
// LoadChainConfig loads the stored chain config if it is already present in
// database, otherwise, return the config in the provided genesis specification.
func LoadChainConfig(db ethdb.Database, genesis *Genesis) (*params.ChainConfig, error) {
func LoadChainConfig(db ethdb.Database, genesis *Genesis) (cfg *params.ChainConfig, ghash common.Hash, err error) {
// Load the stored chain config from the database. It can be nil
// in case the database is empty. Notably, we only care about the
// chain config corresponds to the canonical chain.
@ -394,27 +394,28 @@ func LoadChainConfig(db ethdb.Database, genesis *Genesis) (*params.ChainConfig,
if stored != (common.Hash{}) {
storedcfg := rawdb.ReadChainConfig(db, stored)
if storedcfg != nil {
return storedcfg, nil
return storedcfg, stored, nil
}
}
// Load the config from the provided genesis specification
if genesis != nil {
// Reject invalid genesis spec without valid chain config
if genesis.Config == nil {
return nil, errGenesisNoConfig
return nil, common.Hash{}, errGenesisNoConfig
}
// If the canonical genesis header is present, but the chain
// config is missing(initialize the empty leveldb with an
// external ancient chain segment), ensure the provided genesis
// is matched.
if stored != (common.Hash{}) && genesis.ToBlock().Hash() != stored {
return nil, &GenesisMismatchError{stored, genesis.ToBlock().Hash()}
ghash := genesis.ToBlock().Hash()
if stored != (common.Hash{}) && ghash != stored {
return nil, ghash, &GenesisMismatchError{stored, ghash}
}
return genesis.Config, nil
return genesis.Config, ghash, nil
}
// There is no stored chain config and no new config provided,
// In this case the default chain config(mainnet) will be used
return params.MainnetChainConfig, nil
return params.MainnetChainConfig, params.MainnetGenesisHash, nil
}
// chainConfigOrDefault retrieves the attached chain configuration. If the genesis
@ -636,15 +637,23 @@ func DeveloperGenesisBlock(gasLimit uint64, faucet *common.Address) *Genesis {
BaseFee: big.NewInt(params.InitialBaseFee),
Difficulty: big.NewInt(0),
Alloc: map[common.Address]types.Account{
common.BytesToAddress([]byte{1}): {Balance: big.NewInt(1)}, // ECRecover
common.BytesToAddress([]byte{2}): {Balance: big.NewInt(1)}, // SHA256
common.BytesToAddress([]byte{3}): {Balance: big.NewInt(1)}, // RIPEMD
common.BytesToAddress([]byte{4}): {Balance: big.NewInt(1)}, // Identity
common.BytesToAddress([]byte{5}): {Balance: big.NewInt(1)}, // ModExp
common.BytesToAddress([]byte{6}): {Balance: big.NewInt(1)}, // ECAdd
common.BytesToAddress([]byte{7}): {Balance: big.NewInt(1)}, // ECScalarMul
common.BytesToAddress([]byte{8}): {Balance: big.NewInt(1)}, // ECPairing
common.BytesToAddress([]byte{9}): {Balance: big.NewInt(1)}, // BLAKE2b
common.BytesToAddress([]byte{0x01}): {Balance: big.NewInt(1)}, // ECRecover
common.BytesToAddress([]byte{0x02}): {Balance: big.NewInt(1)}, // SHA256
common.BytesToAddress([]byte{0x03}): {Balance: big.NewInt(1)}, // RIPEMD
common.BytesToAddress([]byte{0x04}): {Balance: big.NewInt(1)}, // Identity
common.BytesToAddress([]byte{0x05}): {Balance: big.NewInt(1)}, // ModExp
common.BytesToAddress([]byte{0x06}): {Balance: big.NewInt(1)}, // ECAdd
common.BytesToAddress([]byte{0x07}): {Balance: big.NewInt(1)}, // ECScalarMul
common.BytesToAddress([]byte{0x08}): {Balance: big.NewInt(1)}, // ECPairing
common.BytesToAddress([]byte{0x09}): {Balance: big.NewInt(1)}, // BLAKE2b
common.BytesToAddress([]byte{0x0a}): {Balance: big.NewInt(1)}, // KZGPointEval
common.BytesToAddress([]byte{0x0b}): {Balance: big.NewInt(1)}, // BLSG1Add
common.BytesToAddress([]byte{0x0c}): {Balance: big.NewInt(1)}, // BLSG1MultiExp
common.BytesToAddress([]byte{0x0d}): {Balance: big.NewInt(1)}, // BLSG2Add
common.BytesToAddress([]byte{0x0e}): {Balance: big.NewInt(1)}, // BLSG2MultiExp
common.BytesToAddress([]byte{0x0f}): {Balance: big.NewInt(1)}, // BLSG1Pairing
common.BytesToAddress([]byte{0x10}): {Balance: big.NewInt(1)}, // BLSG1MapG1
common.BytesToAddress([]byte{0x11}): {Balance: big.NewInt(1)}, // BLSG2MapG2
// Pre-deploy system contracts
params.BeaconRootsAddress: {Nonce: 1, Code: params.BeaconRootsCode, Balance: common.Big0},
params.HistoryStorageAddress: {Nonce: 1, Code: params.HistoryStorageCode, Balance: common.Big0},

View file

@ -18,6 +18,8 @@ package rawdb
import (
"bytes"
"encoding/binary"
"errors"
"math/big"
"github.com/ethereum/go-ethereum/common"
@ -179,3 +181,306 @@ func DeleteBloombits(db ethdb.Database, bit uint, from uint64, to uint64) {
log.Crit("Failed to delete bloom bits", "err", it.Error())
}
}
// ReadFilterMapRow retrieves a filter map row at the given mapRowIndex
// (see filtermaps.mapRowIndex for the storage index encoding).
// Note that zero length rows are not stored in the database and therefore all
// non-existent entries are interpreted as empty rows and return no error.
// Also note that the mapRowIndex indexing scheme is the same as the one
// proposed in EIP-7745 for tree-hashing the filter map structure and for the
// same data proximity reasons it is also suitable for database representation.
// See also:
// https://eips.ethereum.org/EIPS/eip-7745#hash-tree-structure
func ReadFilterMapExtRow(db ethdb.KeyValueReader, mapRowIndex uint64, bitLength uint) ([]uint32, error) {
byteLength := int(bitLength) / 8
if int(bitLength) != byteLength*8 {
panic("invalid bit length")
}
key := filterMapRowKey(mapRowIndex, false)
has, err := db.Has(key)
if err != nil {
return nil, err
}
if !has {
return nil, nil
}
encRow, err := db.Get(key)
if err != nil {
return nil, err
}
if len(encRow)%byteLength != 0 {
return nil, errors.New("Invalid encoded extended filter row length")
}
row := make([]uint32, len(encRow)/byteLength)
var b [4]byte
for i := range row {
copy(b[:byteLength], encRow[i*byteLength:(i+1)*byteLength])
row[i] = binary.LittleEndian.Uint32(b[:])
}
return row, nil
}
func ReadFilterMapBaseRows(db ethdb.KeyValueReader, mapRowIndex uint64, rowCount uint32, bitLength uint) ([][]uint32, error) {
byteLength := int(bitLength) / 8
if int(bitLength) != byteLength*8 {
panic("invalid bit length")
}
key := filterMapRowKey(mapRowIndex, true)
has, err := db.Has(key)
if err != nil {
return nil, err
}
rows := make([][]uint32, rowCount)
if !has {
return rows, nil
}
encRows, err := db.Get(key)
if err != nil {
return nil, err
}
encLen := len(encRows)
var (
entryCount, entriesInRow, rowIndex, headerLen, headerBits int
headerByte byte
)
for headerLen+byteLength*entryCount < encLen {
if headerBits == 0 {
headerByte = encRows[headerLen]
headerLen++
headerBits = 8
}
if headerByte&1 > 0 {
entriesInRow++
entryCount++
} else {
if entriesInRow > 0 {
rows[rowIndex] = make([]uint32, entriesInRow)
entriesInRow = 0
}
rowIndex++
}
headerByte >>= 1
headerBits--
}
if headerLen+byteLength*entryCount > encLen {
return nil, errors.New("Invalid encoded base filter rows length")
}
if entriesInRow > 0 {
rows[rowIndex] = make([]uint32, entriesInRow)
}
nextEntry := headerLen
for _, row := range rows {
for i := range row {
var b [4]byte
copy(b[:byteLength], encRows[nextEntry:nextEntry+byteLength])
row[i] = binary.LittleEndian.Uint32(b[:])
nextEntry += byteLength
}
}
return rows, nil
}
// WriteFilterMapRow stores a filter map row at the given mapRowIndex or deletes
// any existing entry if the row is empty.
func WriteFilterMapExtRow(db ethdb.KeyValueWriter, mapRowIndex uint64, row []uint32, bitLength uint) {
byteLength := int(bitLength) / 8
if int(bitLength) != byteLength*8 {
panic("invalid bit length")
}
var err error
if len(row) > 0 {
encRow := make([]byte, len(row)*byteLength)
for i, c := range row {
var b [4]byte
binary.LittleEndian.PutUint32(b[:], c)
copy(encRow[i*byteLength:(i+1)*byteLength], b[:byteLength])
}
err = db.Put(filterMapRowKey(mapRowIndex, false), encRow)
} else {
err = db.Delete(filterMapRowKey(mapRowIndex, false))
}
if err != nil {
log.Crit("Failed to store extended filter map row", "err", err)
}
}
func WriteFilterMapBaseRows(db ethdb.KeyValueWriter, mapRowIndex uint64, rows [][]uint32, bitLength uint) {
byteLength := int(bitLength) / 8
if int(bitLength) != byteLength*8 {
panic("invalid bit length")
}
var entryCount, zeroBits int
for i, row := range rows {
if len(row) > 0 {
entryCount += len(row)
zeroBits = i
}
}
var err error
if entryCount > 0 {
headerLen := (zeroBits + entryCount + 7) / 8
encRows := make([]byte, headerLen+entryCount*byteLength)
nextEntry := headerLen
headerPtr, headerByte := 0, byte(1)
addHeaderBit := func(bit bool) {
if bit {
encRows[headerPtr] += headerByte
}
if headerByte += headerByte; headerByte == 0 {
headerPtr++
headerByte = 1
}
}
for _, row := range rows {
for _, entry := range row {
var b [4]byte
binary.LittleEndian.PutUint32(b[:], entry)
copy(encRows[nextEntry:nextEntry+byteLength], b[:byteLength])
nextEntry += byteLength
addHeaderBit(true)
}
if zeroBits == 0 {
break
}
addHeaderBit(false)
zeroBits--
}
err = db.Put(filterMapRowKey(mapRowIndex, true), encRows)
} else {
err = db.Delete(filterMapRowKey(mapRowIndex, true))
}
if err != nil {
log.Crit("Failed to store base filter map rows", "err", err)
}
}
func DeleteFilterMapRows(db ethdb.KeyValueRangeDeleter, firstMapRowIndex, afterLastMapRowIndex uint64) {
if err := db.DeleteRange(filterMapRowKey(firstMapRowIndex, false), filterMapRowKey(afterLastMapRowIndex, false)); err != nil {
log.Crit("Failed to delete range of filter map rows", "err", err)
}
}
// ReadFilterMapLastBlock retrieves the number of the block that generated the
// last log value entry of the given map.
func ReadFilterMapLastBlock(db ethdb.KeyValueReader, mapIndex uint32) (uint64, common.Hash, error) {
enc, err := db.Get(filterMapLastBlockKey(mapIndex))
if err != nil {
return 0, common.Hash{}, err
}
if len(enc) != 40 {
return 0, common.Hash{}, errors.New("Invalid block number and id encoding")
}
var id common.Hash
copy(id[:], enc[8:])
return binary.BigEndian.Uint64(enc[:8]), id, nil
}
// WriteFilterMapLastBlock stores the number of the block that generated the
// last log value entry of the given map.
func WriteFilterMapLastBlock(db ethdb.KeyValueWriter, mapIndex uint32, blockNumber uint64, id common.Hash) {
var enc [40]byte
binary.BigEndian.PutUint64(enc[:8], blockNumber)
copy(enc[8:], id[:])
if err := db.Put(filterMapLastBlockKey(mapIndex), enc[:]); err != nil {
log.Crit("Failed to store filter map last block pointer", "err", err)
}
}
// DeleteFilterMapLastBlock deletes the number of the block that generated the
// last log value entry of the given map.
func DeleteFilterMapLastBlock(db ethdb.KeyValueWriter, mapIndex uint32) {
if err := db.Delete(filterMapLastBlockKey(mapIndex)); err != nil {
log.Crit("Failed to delete filter map last block pointer", "err", err)
}
}
func DeleteFilterMapLastBlocks(db ethdb.KeyValueRangeDeleter, firstMapIndex, afterLastMapIndex uint32) {
if err := db.DeleteRange(filterMapLastBlockKey(firstMapIndex), filterMapLastBlockKey(afterLastMapIndex)); err != nil {
log.Crit("Failed to delete range of filter map last block pointers", "err", err)
}
}
// ReadBlockLvPointer retrieves the starting log value index where the log values
// generated by the given block are located.
func ReadBlockLvPointer(db ethdb.KeyValueReader, blockNumber uint64) (uint64, error) {
encPtr, err := db.Get(filterMapBlockLVKey(blockNumber))
if err != nil {
return 0, err
}
if len(encPtr) != 8 {
return 0, errors.New("Invalid log value pointer encoding")
}
return binary.BigEndian.Uint64(encPtr), nil
}
// WriteBlockLvPointer stores the starting log value index where the log values
// generated by the given block are located.
func WriteBlockLvPointer(db ethdb.KeyValueWriter, blockNumber, lvPointer uint64) {
var encPtr [8]byte
binary.BigEndian.PutUint64(encPtr[:], lvPointer)
if err := db.Put(filterMapBlockLVKey(blockNumber), encPtr[:]); err != nil {
log.Crit("Failed to store block log value pointer", "err", err)
}
}
// DeleteBlockLvPointer deletes the starting log value index where the log values
// generated by the given block are located.
func DeleteBlockLvPointer(db ethdb.KeyValueWriter, blockNumber uint64) {
if err := db.Delete(filterMapBlockLVKey(blockNumber)); err != nil {
log.Crit("Failed to delete block log value pointer", "err", err)
}
}
func DeleteBlockLvPointers(db ethdb.KeyValueRangeDeleter, firstBlockNumber, afterLastBlockNumber uint64) {
if err := db.DeleteRange(filterMapBlockLVKey(firstBlockNumber), filterMapBlockLVKey(afterLastBlockNumber)); err != nil {
log.Crit("Failed to delete range of block log value pointers", "err", err)
}
}
// FilterMapsRange is a storage representation of the block range covered by the
// filter maps structure and the corresponting log value index range.
type FilterMapsRange struct {
HeadBlockIndexed bool
HeadBlockDelimiter uint64
FirstIndexedBlock, AfterLastIndexedBlock uint64
FirstRenderedMap, AfterLastRenderedMap, TailPartialEpoch uint32
}
// ReadFilterMapsRange retrieves the filter maps range data. Note that if the
// database entry is not present, that is interpreted as a valid non-initialized
// state and returns a blank range structure and no error.
func ReadFilterMapsRange(db ethdb.KeyValueReader) (FilterMapsRange, bool, error) {
if has, err := db.Has(filterMapsRangeKey); !has || err != nil {
return FilterMapsRange{}, false, err
}
encRange, err := db.Get(filterMapsRangeKey)
if err != nil {
return FilterMapsRange{}, false, err
}
var fmRange FilterMapsRange
if err := rlp.DecodeBytes(encRange, &fmRange); err != nil {
return FilterMapsRange{}, false, err
}
return fmRange, true, err
}
// WriteFilterMapsRange stores the filter maps range data.
func WriteFilterMapsRange(db ethdb.KeyValueWriter, fmRange FilterMapsRange) {
encRange, err := rlp.EncodeToBytes(&fmRange)
if err != nil {
log.Crit("Failed to encode filter maps range", "err", err)
}
if err := db.Put(filterMapsRangeKey, encRange); err != nil {
log.Crit("Failed to store filter maps range", "err", err)
}
}
// DeleteFilterMapsRange deletes the filter maps range data which is interpreted
// as reverting to the un-initialized state.
func DeleteFilterMapsRange(db ethdb.KeyValueWriter) {
if err := db.Delete(filterMapsRangeKey); err != nil {
log.Crit("Failed to delete filter maps range", "err", err)
}
}

View file

@ -27,6 +27,11 @@ import (
// ReadPreimage retrieves a single preimage of the provided hash.
func ReadPreimage(db ethdb.KeyValueReader, hash common.Hash) []byte {
data, _ := db.Get(preimageKey(hash))
if len(data) == 0 {
preimageMissCounter.Inc(1)
} else {
preimageHitsCounter.Inc(1)
}
return data
}
@ -38,7 +43,6 @@ func WritePreimages(db ethdb.KeyValueWriter, preimages map[common.Hash][]byte) {
}
}
preimageCounter.Inc(int64(len(preimages)))
preimageHitCounter.Inc(int64(len(preimages)))
}
// ReadCode retrieves the contract code of the provided code hash.

View file

@ -376,6 +376,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
storageSnaps stat
preimages stat
bloomBits stat
filterMaps stat
beaconHeaders stat
cliqueSnaps stat
@ -440,6 +441,8 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
bloomBits.Add(size)
case bytes.HasPrefix(key, BloomBitsIndexPrefix):
bloomBits.Add(size)
case bytes.HasPrefix(key, []byte(FilterMapsPrefix)):
filterMaps.Add(size)
case bytes.HasPrefix(key, skeletonHeaderPrefix) && len(key) == (len(skeletonHeaderPrefix)+8):
beaconHeaders.Add(size)
case bytes.HasPrefix(key, CliqueSnapshotPrefix) && len(key) == 7+common.HashLength:
@ -505,6 +508,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
{"Key-Value store", "Block hash->number", hashNumPairings.Size(), hashNumPairings.Count()},
{"Key-Value store", "Transaction index", txLookups.Size(), txLookups.Count()},
{"Key-Value store", "Bloombit index", bloomBits.Size(), bloomBits.Count()},
{"Key-Value store", "Log search index", filterMaps.Size(), filterMaps.Count()},
{"Key-Value store", "Contract codes", codes.Size(), codes.Count()},
{"Key-Value store", "Hash trie nodes", legacyTries.Size(), legacyTries.Count()},
{"Key-Value store", "Path trie state lookups", stateLookups.Size(), stateLookups.Count()},

View file

@ -145,8 +145,15 @@ var (
FixedCommitteeRootKey = []byte("fixedRoot-") // bigEndian64(syncPeriod) -> committee root hash
SyncCommitteeKey = []byte("committee-") // bigEndian64(syncPeriod) -> serialized committee
FilterMapsPrefix = "fm-"
filterMapsRangeKey = []byte(FilterMapsPrefix + "R")
filterMapRowPrefix = []byte(FilterMapsPrefix + "r") // filterMapRowPrefix + mapRowIndex (uint64 big endian) -> filter row
filterMapLastBlockPrefix = []byte(FilterMapsPrefix + "b") // filterMapLastBlockPrefix + mapIndex (uint32 big endian) -> block number (uint64 big endian)
filterMapBlockLVPrefix = []byte(FilterMapsPrefix + "p") // filterMapBlockLVPrefix + num (uint64 big endian) -> log value pointer (uint64 big endian)
preimageCounter = metrics.NewRegisteredCounter("db/preimage/total", nil)
preimageHitCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil)
preimageHitsCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil)
preimageMissCounter = metrics.NewRegisteredCounter("db/preimage/miss", nil)
)
// LegacyTxLookupEntry is the legacy TxLookupEntry definition with some unnecessary
@ -341,3 +348,34 @@ func IsStorageTrieNode(key []byte) bool {
ok, _, _ := ResolveStorageTrieNode(key)
return ok
}
// filterMapRowKey = filterMapRowPrefix + mapRowIndex (uint64 big endian)
func filterMapRowKey(mapRowIndex uint64, base bool) []byte {
extLen := 8
if base {
extLen = 9
}
l := len(filterMapRowPrefix)
key := make([]byte, l+extLen)
copy(key[:l], filterMapRowPrefix)
binary.BigEndian.PutUint64(key[l:l+8], mapRowIndex)
return key
}
// filterMapLastBlockKey = filterMapLastBlockPrefix + mapIndex (uint32 big endian)
func filterMapLastBlockKey(mapIndex uint32) []byte {
l := len(filterMapLastBlockPrefix)
key := make([]byte, l+4)
copy(key[:l], filterMapLastBlockPrefix)
binary.BigEndian.PutUint32(key[l:], mapIndex)
return key
}
// filterMapBlockLVKey = filterMapBlockLVPrefix + num (uint64 big endian)
func filterMapBlockLVKey(number uint64) []byte {
l := len(filterMapBlockLVPrefix)
key := make([]byte, l+8)
copy(key[:l], filterMapBlockLVPrefix)
binary.BigEndian.PutUint64(key[l:], number)
return key
}

View file

@ -19,7 +19,7 @@ package state
import "github.com/ethereum/go-ethereum/metrics"
var (
accountReadMeters = metrics.NewRegisteredMeter("state/read/accounts", nil)
accountReadMeters = metrics.NewRegisteredMeter("state/read/account", nil)
storageReadMeters = metrics.NewRegisteredMeter("state/read/storage", nil)
accountUpdatedMeter = metrics.NewRegisteredMeter("state/update/account", nil)
storageUpdatedMeter = metrics.NewRegisteredMeter("state/update/storage", nil)

View file

@ -19,6 +19,7 @@ package snapshot
import (
"encoding/binary"
"fmt"
"maps"
"math"
"math/rand"
"slices"
@ -30,7 +31,6 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
bloomfilter "github.com/holiman/bloomfilter/v2"
"golang.org/x/exp/maps"
)
var (
@ -431,8 +431,7 @@ func (dl *diffLayer) AccountList() []common.Hash {
dl.lock.Lock()
defer dl.lock.Unlock()
dl.accountList = maps.Keys(dl.accountData)
slices.SortFunc(dl.accountList, common.Hash.Cmp)
dl.accountList = slices.SortedFunc(maps.Keys(dl.accountData), common.Hash.Cmp)
dl.memory += uint64(len(dl.accountList) * common.HashLength)
return dl.accountList
}
@ -464,8 +463,7 @@ func (dl *diffLayer) StorageList(accountHash common.Hash) []common.Hash {
dl.lock.Lock()
defer dl.lock.Unlock()
storageList := maps.Keys(dl.storageData[accountHash])
slices.SortFunc(storageList, common.Hash.Cmp)
storageList := slices.SortedFunc(maps.Keys(dl.storageData[accountHash]), common.Hash.Cmp)
dl.storageList[accountHash] = storageList
dl.memory += uint64(len(dl.storageList)*common.HashLength + common.HashLength)
return storageList

View file

@ -83,8 +83,8 @@ func TestHooks(t *testing.T) {
inner.SetTxContext(common.Hash{0x11}, 100) // For the log
var result []string
var wants = []string{
"0xaa00000000000000000000000000000000000000.balance: 0->100 (BalanceChangeUnspecified)",
"0xaa00000000000000000000000000000000000000.balance: 100->50 (BalanceChangeTransfer)",
"0xaa00000000000000000000000000000000000000.balance: 0->100 (Unspecified)",
"0xaa00000000000000000000000000000000000000.balance: 100->50 (Transfer)",
"0xaa00000000000000000000000000000000000000.nonce: 0->1337",
"0xaa00000000000000000000000000000000000000.code: (0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470) ->0x1325 (0xa12ae05590de0c93a00bc7ac773c2fdb621e44f814985e72194f921c0050f728)",
"0xaa00000000000000000000000000000000000000.storage slot 0x0000000000000000000000000000000000000000000000000000000000000001: 0x0000000000000000000000000000000000000000000000000000000000000000 ->0x0000000000000000000000000000000000000000000000000000000000000011",

View file

@ -305,12 +305,14 @@ func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte
*requests = append(*requests, requestsData)
}
var depositTopic = common.HexToHash("0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5")
// ParseDepositLogs extracts the EIP-6110 deposit values from logs emitted by
// BeaconDepositContract.
func ParseDepositLogs(requests *[][]byte, logs []*types.Log, config *params.ChainConfig) error {
deposits := make([]byte, 1) // note: first byte is 0x00 (== deposit request type)
for _, log := range logs {
if log.Address == config.DepositContractAddress {
if log.Address == config.DepositContractAddress && len(log.Topics) > 0 && log.Topics[0] == depositTopic {
request, err := types.DepositLogToRequest(log.Data)
if err != nil {
return fmt.Errorf("unable to parse deposit data: %v", err)

View file

@ -1,4 +1,4 @@
// Code generated by "stringer -type=BalanceChangeReason -output gen_balance_change_reason_stringer.go"; DO NOT EDIT.
// Code generated by "stringer -type=BalanceChangeReason -trimprefix=BalanceChange -output gen_balance_change_reason_stringer.go"; DO NOT EDIT.
package tracing
@ -26,9 +26,9 @@ func _() {
_ = x[BalanceChangeRevert-15]
}
const _BalanceChangeReason_name = "BalanceChangeUnspecifiedBalanceIncreaseRewardMineUncleBalanceIncreaseRewardMineBlockBalanceIncreaseWithdrawalBalanceIncreaseGenesisBalanceBalanceIncreaseRewardTransactionFeeBalanceDecreaseGasBuyBalanceIncreaseGasReturnBalanceIncreaseDaoContractBalanceDecreaseDaoAccountBalanceChangeTransferBalanceChangeTouchAccountBalanceIncreaseSelfdestructBalanceDecreaseSelfdestructBalanceDecreaseSelfdestructBurnBalanceChangeRevert"
const _BalanceChangeReason_name = "UnspecifiedBalanceIncreaseRewardMineUncleBalanceIncreaseRewardMineBlockBalanceIncreaseWithdrawalBalanceIncreaseGenesisBalanceBalanceIncreaseRewardTransactionFeeBalanceDecreaseGasBuyBalanceIncreaseGasReturnBalanceIncreaseDaoContractBalanceDecreaseDaoAccountTransferTouchAccountBalanceIncreaseSelfdestructBalanceDecreaseSelfdestructBalanceDecreaseSelfdestructBurnRevert"
var _BalanceChangeReason_index = [...]uint16{0, 24, 54, 84, 109, 138, 173, 194, 218, 244, 269, 290, 315, 342, 369, 400, 419}
var _BalanceChangeReason_index = [...]uint16{0, 11, 41, 71, 96, 125, 160, 181, 205, 231, 256, 264, 276, 303, 330, 361, 367}
func (i BalanceChangeReason) String() string {
if i >= BalanceChangeReason(len(_BalanceChangeReason_index)-1) {

View file

@ -0,0 +1,52 @@
// Code generated by "stringer -type=GasChangeReason -trimprefix=GasChange -output gen_gas_change_reason_stringer.go"; DO NOT EDIT.
package tracing
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[GasChangeUnspecified-0]
_ = x[GasChangeTxInitialBalance-1]
_ = x[GasChangeTxIntrinsicGas-2]
_ = x[GasChangeTxRefunds-3]
_ = x[GasChangeTxLeftOverReturned-4]
_ = x[GasChangeCallInitialBalance-5]
_ = x[GasChangeCallLeftOverReturned-6]
_ = x[GasChangeCallLeftOverRefunded-7]
_ = x[GasChangeCallContractCreation-8]
_ = x[GasChangeCallContractCreation2-9]
_ = x[GasChangeCallCodeStorage-10]
_ = x[GasChangeCallOpCode-11]
_ = x[GasChangeCallPrecompiledContract-12]
_ = x[GasChangeCallStorageColdAccess-13]
_ = x[GasChangeCallFailedExecution-14]
_ = x[GasChangeWitnessContractInit-15]
_ = x[GasChangeWitnessContractCreation-16]
_ = x[GasChangeWitnessCodeChunk-17]
_ = x[GasChangeWitnessContractCollisionCheck-18]
_ = x[GasChangeTxDataFloor-19]
_ = x[GasChangeIgnored-255]
}
const (
_GasChangeReason_name_0 = "UnspecifiedTxInitialBalanceTxIntrinsicGasTxRefundsTxLeftOverReturnedCallInitialBalanceCallLeftOverReturnedCallLeftOverRefundedCallContractCreationCallContractCreation2CallCodeStorageCallOpCodeCallPrecompiledContractCallStorageColdAccessCallFailedExecutionWitnessContractInitWitnessContractCreationWitnessCodeChunkWitnessContractCollisionCheckTxDataFloor"
_GasChangeReason_name_1 = "Ignored"
)
var (
_GasChangeReason_index_0 = [...]uint16{0, 11, 27, 41, 50, 68, 86, 106, 126, 146, 167, 182, 192, 215, 236, 255, 274, 297, 313, 342, 353}
)
func (i GasChangeReason) String() string {
switch {
case i <= 19:
return _GasChangeReason_name_0[_GasChangeReason_index_0[i]:_GasChangeReason_index_0[i+1]]
case i == 255:
return _GasChangeReason_name_1
default:
return "GasChangeReason(" + strconv.FormatInt(int64(i), 10) + ")"
}
}

View file

@ -0,0 +1,29 @@
// Code generated by "stringer -type=NonceChangeReason -trimprefix NonceChange -output gen_nonce_change_reason_stringer.go"; DO NOT EDIT.
package tracing
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[NonceChangeUnspecified-0]
_ = x[NonceChangeGenesis-1]
_ = x[NonceChangeEoACall-2]
_ = x[NonceChangeContractCreator-3]
_ = x[NonceChangeNewContract-4]
_ = x[NonceChangeAuthorization-5]
_ = x[NonceChangeRevert-6]
}
const _NonceChangeReason_name = "UnspecifiedGenesisEoACallContractCreatorNewContractAuthorizationRevert"
var _NonceChangeReason_index = [...]uint8{0, 11, 18, 25, 40, 51, 64, 70}
func (i NonceChangeReason) String() string {
if i >= NonceChangeReason(len(_NonceChangeReason_index)-1) {
return "NonceChangeReason(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _NonceChangeReason_name[_NonceChangeReason_index[i]:_NonceChangeReason_index[i+1]]
}

View file

@ -223,7 +223,7 @@ type Hooks struct {
// for tracing and reporting.
type BalanceChangeReason byte
//go:generate go run golang.org/x/tools/cmd/stringer -type=BalanceChangeReason -output gen_balance_change_reason_stringer.go
//go:generate go run golang.org/x/tools/cmd/stringer -type=BalanceChangeReason -trimprefix=BalanceChange -output gen_balance_change_reason_stringer.go
const (
BalanceChangeUnspecified BalanceChangeReason = 0
@ -284,6 +284,8 @@ const (
// once per transaction, while those that start with `GasChangeCall` are emitted on a call basis.
type GasChangeReason byte
//go:generate go run golang.org/x/tools/cmd/stringer -type=GasChangeReason -trimprefix=GasChange -output gen_gas_change_reason_stringer.go
const (
GasChangeUnspecified GasChangeReason = 0
@ -348,6 +350,8 @@ const (
// NonceChangeReason is used to indicate the reason for a nonce change.
type NonceChangeReason byte
//go:generate go run golang.org/x/tools/cmd/stringer -type=NonceChangeReason -trimprefix NonceChange -output gen_nonce_change_reason_stringer.go
const (
NonceChangeUnspecified NonceChangeReason = 0

View file

@ -1085,19 +1085,24 @@ func (p *BlobPool) SetGasTip(tip *big.Int) {
p.updateStorageMetrics()
}
// validateTx checks whether a transaction is valid according to the consensus
// rules and adheres to some heuristic limits of the local node (price and size).
func (p *BlobPool) validateTx(tx *types.Transaction) error {
// Ensure the transaction adheres to basic pool filters (type, size, tip) and
// consensus rules
baseOpts := &txpool.ValidationOptions{
// ValidateTxBasics checks whether a transaction is valid according to the consensus
// rules, but does not check state-dependent validation such as sufficient balance.
// This check is meant as an early check which only needs to be performed once,
// and does not require the pool mutex to be held.
func (p *BlobPool) ValidateTxBasics(tx *types.Transaction) error {
opts := &txpool.ValidationOptions{
Config: p.chain.Config(),
Accept: 1 << types.BlobTxType,
MaxSize: txMaxSize,
MinTip: p.gasTip.ToBig(),
}
return txpool.ValidateTransaction(tx, p.head, p.signer, opts)
}
if err := p.txValidationFn(tx, p.head, p.signer, baseOpts); err != nil {
// validateTx checks whether a transaction is valid according to the consensus
// rules and adheres to some heuristic limits of the local node (price and size).
func (p *BlobPool) validateTx(tx *types.Transaction) error {
if err := p.ValidateTxBasics(tx); err != nil {
return err
}
// Ensure the transaction adheres to the stateful pool filters (nonce, balance)

View file

@ -21,6 +21,7 @@ import (
"crypto/ecdsa"
"crypto/sha256"
"errors"
"fmt"
"math"
"math/big"
"os"
@ -452,8 +453,7 @@ func TestOpenDrops(t *testing.T) {
//log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
// Create a temporary folder for the persistent backend
storage, _ := os.MkdirTemp("", "blobpool-")
defer os.RemoveAll(storage)
storage := t.TempDir()
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(testMaxBlobsPerBlock), nil)
@ -775,8 +775,7 @@ func TestOpenIndex(t *testing.T) {
//log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
// Create a temporary folder for the persistent backend
storage, _ := os.MkdirTemp("", "blobpool-")
defer os.RemoveAll(storage)
storage := t.TempDir()
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(testMaxBlobsPerBlock), nil)
@ -864,8 +863,7 @@ func TestOpenHeap(t *testing.T) {
//log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
// Create a temporary folder for the persistent backend
storage, _ := os.MkdirTemp("", "blobpool-")
defer os.RemoveAll(storage)
storage := t.TempDir()
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(testMaxBlobsPerBlock), nil)
@ -951,8 +949,7 @@ func TestOpenCap(t *testing.T) {
//log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
// Create a temporary folder for the persistent backend
storage, _ := os.MkdirTemp("", "blobpool-")
defer os.RemoveAll(storage)
storage := t.TempDir()
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(testMaxBlobsPerBlock), nil)
@ -1041,8 +1038,7 @@ func TestChangingSlotterSize(t *testing.T) {
//log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
// Create a temporary folder for the persistent backend
storage, _ := os.MkdirTemp("", "blobpool-")
defer os.RemoveAll(storage)
storage := t.TempDir()
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(6), nil)
@ -1508,8 +1504,7 @@ func TestAdd(t *testing.T) {
}
for i, tt := range tests {
// Create a temporary folder for the persistent backend
storage, _ := os.MkdirTemp("", "blobpool-")
defer os.RemoveAll(storage) // late defer, still ok
storage := filepath.Join(t.TempDir(), fmt.Sprintf("test-%d", i))
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(testMaxBlobsPerBlock), nil)

View file

@ -18,12 +18,12 @@ package blobpool
import (
"container/heap"
"maps"
"math"
"slices"
"github.com/ethereum/go-ethereum/common"
"github.com/holiman/uint256"
"golang.org/x/exp/maps"
)
// evictHeap is a helper data structure to keep track of the cheapest bottleneck
@ -54,8 +54,7 @@ func newPriceHeap(basefee *uint256.Int, blobfee *uint256.Int, index map[common.A
// Populate the heap in account sort order. Not really needed in practice,
// but it makes the heap initialization deterministic and less annoying to
// test in unit tests.
heap.addrs = maps.Keys(index)
slices.SortFunc(heap.addrs, common.Address.Cmp)
heap.addrs = slices.SortedFunc(maps.Keys(index), common.Address.Cmp)
for i, addr := range heap.addrs {
heap.index[addr] = i
}

View file

@ -51,22 +51,9 @@ var (
// making the transaction invalid, rather a DOS protection.
ErrOversizedData = errors.New("oversized data")
// ErrFutureReplacePending is returned if a future transaction replaces a pending
// one. Future transactions should only be able to replace other future transactions.
ErrFutureReplacePending = errors.New("future transaction tries to replace pending")
// ErrAlreadyReserved is returned if the sender address has a pending transaction
// in a different subpool. For example, this error is returned in response to any
// input transaction of non-blob type when a blob transaction from this sender
// remains pending (and vice-versa).
ErrAlreadyReserved = errors.New("address already reserved")
// ErrAuthorityReserved is returned if a transaction has an authorization
// signed by an address which already has in-flight transactions known to the
// pool.
ErrAuthorityReserved = errors.New("authority already reserved")
// ErrAuthorityNonce is returned if a transaction has an authorization with
// a nonce that is not currently valid for the authority.
ErrAuthorityNonceTooLow = errors.New("authority nonce too low")
)

View file

@ -19,6 +19,7 @@ package legacypool
import (
"errors"
"maps"
"math"
"math/big"
"slices"
@ -40,7 +41,6 @@ import (
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
"golang.org/x/exp/maps"
)
const (
@ -61,6 +61,19 @@ var (
// ErrTxPoolOverflow is returned if the transaction pool is full and can't accept
// another remote transaction.
ErrTxPoolOverflow = errors.New("txpool is full")
// ErrInflightTxLimitReached is returned when the maximum number of in-flight
// transactions is reached for specific accounts.
ErrInflightTxLimitReached = errors.New("in-flight transaction limit reached for delegated accounts")
// ErrAuthorityReserved is returned if a transaction has an authorization
// signed by an address which already has in-flight transactions known to the
// pool.
ErrAuthorityReserved = errors.New("authority already reserved")
// ErrFutureReplacePending is returned if a future transaction replaces a pending
// one. Future transactions should only be able to replace other future transactions.
ErrFutureReplacePending = errors.New("future transaction tries to replace pending")
)
var (
@ -545,11 +558,11 @@ func (pool *LegacyPool) Pending(filter txpool.PendingFilter) map[common.Address]
return pending
}
// validateTxBasics checks whether a transaction is valid according to the consensus
// ValidateTxBasics checks whether a transaction is valid according to the consensus
// rules, but does not check state-dependent validation such as sufficient balance.
// This check is meant as an early check which only needs to be performed once,
// and does not require the pool mutex to be held.
func (pool *LegacyPool) validateTxBasics(tx *types.Transaction) error {
func (pool *LegacyPool) ValidateTxBasics(tx *types.Transaction) error {
opts := &txpool.ValidationOptions{
Config: pool.chainconfig,
Accept: 0 |
@ -560,10 +573,7 @@ func (pool *LegacyPool) validateTxBasics(tx *types.Transaction) error {
MaxSize: txMaxSize,
MinTip: pool.gasTip.Load().ToBig(),
}
if err := txpool.ValidateTransaction(tx, pool.currentHead.Load(), pool.signer, opts); err != nil {
return err
}
return nil
return txpool.ValidateTransaction(tx, pool.currentHead.Load(), pool.signer, opts)
}
// validateTx checks whether a transaction is valid according to the consensus
@ -573,21 +583,7 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction) error {
State: pool.currentState,
FirstNonceGap: nil, // Pool allows arbitrary arrival order, don't invalidate nonce gaps
UsedAndLeftSlots: func(addr common.Address) (int, int) {
var have int
if list := pool.pending[addr]; list != nil {
have += list.Len()
}
if list := pool.queue[addr]; list != nil {
have += list.Len()
}
if pool.currentState.GetCodeHash(addr) != types.EmptyCodeHash || len(pool.all.auths[addr]) != 0 {
// Allow at most one in-flight tx for delegated accounts or those with
// a pending authorization.
return have, max(0, 1-have)
}
return have, math.MaxInt
},
UsedAndLeftSlots: nil, // Pool has own mechanism to limit the number of transactions
ExistingExpenditure: func(addr common.Address) *big.Int {
if list := pool.pending[addr]; list != nil {
return list.totalcost.ToBig()
@ -602,22 +598,49 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction) error {
}
return nil
},
KnownConflicts: func(from common.Address, auths []common.Address) []common.Address {
var conflicts []common.Address
// Authorities cannot conflict with any pending or queued transactions.
for _, addr := range auths {
if list := pool.pending[addr]; list != nil {
conflicts = append(conflicts, addr)
} else if list := pool.queue[addr]; list != nil {
conflicts = append(conflicts, addr)
}
}
return conflicts
},
}
if err := txpool.ValidateTransactionWithState(tx, pool.signer, opts); err != nil {
return err
}
return pool.validateAuth(tx)
}
// validateAuth verifies that the transaction complies with code authorization
// restrictions brought by SetCode transaction type.
func (pool *LegacyPool) validateAuth(tx *types.Transaction) error {
from, _ := types.Sender(pool.signer, tx) // validated
// Allow at most one in-flight tx for delegated accounts or those with a
// pending authorization.
if pool.currentState.GetCodeHash(from) != types.EmptyCodeHash || len(pool.all.auths[from]) != 0 {
var (
count int
exists bool
)
pending := pool.pending[from]
if pending != nil {
count += pending.Len()
exists = pending.Contains(tx.Nonce())
}
queue := pool.queue[from]
if queue != nil {
count += queue.Len()
exists = exists || queue.Contains(tx.Nonce())
}
// Replace the existing in-flight transaction for delegated accounts
// are still supported
if count >= 1 && !exists {
return ErrInflightTxLimitReached
}
}
// Authorities cannot conflict with any pending or queued transactions.
if auths := tx.SetCodeAuthorities(); len(auths) > 0 {
for _, auth := range auths {
if pool.pending[auth] != nil || pool.queue[auth] != nil {
return ErrAuthorityReserved
}
}
}
return nil
}
@ -709,7 +732,7 @@ func (pool *LegacyPool) add(tx *types.Transaction) (replaced bool, err error) {
pool.priced.Put(dropTx)
}
log.Trace("Discarding future transaction replacing pending tx", "hash", hash)
return false, txpool.ErrFutureReplacePending
return false, ErrFutureReplacePending
}
}
@ -903,7 +926,7 @@ func (pool *LegacyPool) Add(txs []*types.Transaction, sync bool) []error {
// Exclude transactions with basic errors, e.g invalid signatures and
// insufficient intrinsic gas as soon as possible and cache senders
// in transactions before obtaining lock
if err := pool.validateTxBasics(tx); err != nil {
if err := pool.ValidateTxBasics(tx); err != nil {
errs[i] = err
log.Trace("Discarding invalid transaction", "hash", tx.Hash(), "err", err)
invalidTxMeter.Mark(1)
@ -1581,7 +1604,7 @@ func (pool *LegacyPool) demoteUnexecutables() {
gapped := list.Cap(0)
for _, tx := range gapped {
hash := tx.Hash()
log.Error("Demoting invalidated transaction", "hash", hash)
log.Warn("Demoting invalidated transaction", "hash", hash)
// Internal shuffle shouldn't touch the lookup set.
pool.enqueueTx(hash, tx, false)
@ -1648,7 +1671,7 @@ func (as *accountSet) addTx(tx *types.Transaction) {
// reuse. The returned slice should not be changed!
func (as *accountSet) flatten() []common.Address {
if as.cache == nil {
as.cache = maps.Keys(as.accounts)
as.cache = slices.Collect(maps.Keys(as.accounts))
}
return as.cache
}
@ -1739,12 +1762,12 @@ func (t *lookup) Remove(hash common.Hash) {
t.lock.Lock()
defer t.lock.Unlock()
t.removeAuthorities(hash)
tx, ok := t.txs[hash]
if !ok {
log.Error("No transaction found to be deleted", "hash", hash)
return
}
t.removeAuthorities(tx)
t.slots -= numSlots(tx)
slotsGauge.Update(int64(t.slots))
@ -1782,8 +1805,9 @@ func (t *lookup) addAuthorities(tx *types.Transaction) {
// removeAuthorities stops tracking the supplied tx in relation to its
// authorities.
func (t *lookup) removeAuthorities(hash common.Hash) {
for addr := range t.auths {
func (t *lookup) removeAuthorities(tx *types.Transaction) {
hash := tx.Hash()
for _, addr := range tx.SetCodeAuthorities() {
list := t.auths[addr]
// Remove tx from tracker.
if i := slices.Index(list, hash); i >= 0 {

View file

@ -23,6 +23,7 @@ import (
"fmt"
"math/big"
"math/rand"
"slices"
"sync"
"sync/atomic"
"testing"
@ -238,6 +239,23 @@ func validatePoolInternals(pool *LegacyPool) error {
return fmt.Errorf("pending nonce mismatch: have %v, want %v", nonce, last+1)
}
}
// Ensure all auths in pool are tracked
for _, tx := range pool.all.txs {
for _, addr := range tx.SetCodeAuthorities() {
list := pool.all.auths[addr]
if i := slices.Index(list, tx.Hash()); i < 0 {
return fmt.Errorf("authority not tracked: addr %s, tx %s", addr, tx.Hash())
}
}
}
// Ensure all auths in pool have an associated tx.
for addr, hashes := range pool.all.auths {
for _, hash := range hashes {
if _, ok := pool.all.txs[hash]; !ok {
return fmt.Errorf("dangling authority, missing originating tx: addr %s, hash %s", addr, hash.Hex())
}
}
}
return nil
}
@ -1645,8 +1663,8 @@ func TestUnderpricing(t *testing.T) {
t.Fatalf("failed to add well priced transaction: %v", err)
}
// Ensure that replacing a pending transaction with a future transaction fails
if err := pool.addRemoteSync(pricedTransaction(5, 100000, big.NewInt(6), keys[1])); err != txpool.ErrFutureReplacePending {
t.Fatalf("adding future replace transaction error mismatch: have %v, want %v", err, txpool.ErrFutureReplacePending)
if err := pool.addRemoteSync(pricedTransaction(5, 100000, big.NewInt(6), keys[1])); err != ErrFutureReplacePending {
t.Fatalf("adding future replace transaction error mismatch: have %v, want %v", err, ErrFutureReplacePending)
}
pending, queued = pool.Stats()
if pending != 4 {
@ -2248,12 +2266,12 @@ func TestSetCodeTransactions(t *testing.T) {
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), keyA)); err != nil {
t.Fatalf("%s: failed to add remote transaction: %v", name, err)
}
if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyA)); !errors.Is(err, txpool.ErrAccountLimitExceeded) {
t.Fatalf("%s: error mismatch: want %v, have %v", name, txpool.ErrAccountLimitExceeded, err)
if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrInflightTxLimitReached) {
t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err)
}
// Also check gapped transaction.
if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1), keyA)); !errors.Is(err, txpool.ErrAccountLimitExceeded) {
t.Fatalf("%s: error mismatch: want %v, have %v", name, txpool.ErrAccountLimitExceeded, err)
if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrInflightTxLimitReached) {
t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err)
}
// Replace by fee.
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(10), keyA)); err != nil {
@ -2287,8 +2305,8 @@ func TestSetCodeTransactions(t *testing.T) {
t.Fatalf("%s: failed to add with pending delegatio: %v", name, err)
}
// Also check gapped transaction is rejected.
if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyC)); !errors.Is(err, txpool.ErrAccountLimitExceeded) {
t.Fatalf("%s: error mismatch: want %v, have %v", name, txpool.ErrAccountLimitExceeded, err)
if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyC)); !errors.Is(err, ErrInflightTxLimitReached) {
t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err)
}
},
},
@ -2363,7 +2381,7 @@ func TestSetCodeTransactions(t *testing.T) {
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1000), keyC)); err != nil {
t.Fatalf("%s: failed to added single pooled for account with pending delegation: %v", name, err)
}
if err, want := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1000), keyC)), txpool.ErrAccountLimitExceeded; !errors.Is(err, want) {
if err, want := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1000), keyC)), ErrInflightTxLimitReached; !errors.Is(err, want) {
t.Fatalf("%s: error mismatch: want %v, have %v", name, want, err)
}
},
@ -2376,11 +2394,37 @@ func TestSetCodeTransactions(t *testing.T) {
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1000), keyC)); err != nil {
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
}
if err, want := pool.addRemoteSync(setCodeTx(0, keyA, []unsignedAuth{{1, keyC}})), txpool.ErrAuthorityReserved; !errors.Is(err, want) {
if err, want := pool.addRemoteSync(setCodeTx(0, keyA, []unsignedAuth{{1, keyC}})), ErrAuthorityReserved; !errors.Is(err, want) {
t.Fatalf("%s: error mismatch: want %v, have %v", name, want, err)
}
},
},
{
name: "remove-hash-from-authority-tracker",
pending: 10,
run: func(name string) {
var keys []*ecdsa.PrivateKey
for i := 0; i < 30; i++ {
key, _ := crypto.GenerateKey()
keys = append(keys, key)
addr := crypto.PubkeyToAddress(key.PublicKey)
testAddBalance(pool, addr, big.NewInt(params.Ether))
}
// Create a transactions with 3 unique auths so the lookup's auth map is
// filled with addresses.
for i := 0; i < 30; i += 3 {
if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, uint256.NewInt(10), uint256.NewInt(3), keys[i], []unsignedAuth{{0, keys[i]}, {0, keys[i+1]}, {0, keys[i+2]}})); err != nil {
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
}
}
// Replace one of the transactions with a normal transaction so that the
// original hash is removed from the tracker. The hash should be
// associated with 3 different authorities.
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1000), keys[0])); err != nil {
t.Fatalf("%s: failed to replace with remote transaction: %v", name, err)
}
},
},
} {
tt.run(tt.name)
pending, queued := pool.Stats()
@ -2440,8 +2484,8 @@ func TestSetCodeTransactionsReorg(t *testing.T) {
t.Fatalf("failed to add with remote setcode transaction: %v", err)
}
// Try to add a transactions in
if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1000), keyA)); !errors.Is(err, txpool.ErrAccountLimitExceeded) {
t.Fatalf("unexpected error %v, expecting %v", err, txpool.ErrAccountLimitExceeded)
if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1000), keyA)); !errors.Is(err, ErrInflightTxLimitReached) {
t.Fatalf("unexpected error %v, expecting %v", err, ErrInflightTxLimitReached)
}
// Simulate the chain moving
blockchain.statedb.SetNonce(addrA, 2, tracing.NonceChangeAuthorization)

View file

@ -18,6 +18,7 @@
package locals
import (
"slices"
"sync"
"time"
@ -28,7 +29,6 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/params"
"golang.org/x/exp/slices"
)
var (
@ -74,28 +74,45 @@ func New(journalPath string, journalTime time.Duration, chainConfig *params.Chai
// Track adds a transaction to the tracked set.
// Note: blob-type transactions are ignored.
func (tracker *TxTracker) Track(tx *types.Transaction) {
tracker.TrackAll([]*types.Transaction{tx})
func (tracker *TxTracker) Track(tx *types.Transaction) error {
return tracker.TrackAll([]*types.Transaction{tx})[0]
}
// TrackAll adds a list of transactions to the tracked set.
// Note: blob-type transactions are ignored.
func (tracker *TxTracker) TrackAll(txs []*types.Transaction) {
func (tracker *TxTracker) TrackAll(txs []*types.Transaction) []error {
tracker.mu.Lock()
defer tracker.mu.Unlock()
var errors []error
for _, tx := range txs {
if tx.Type() == types.BlobTxType {
errors = append(errors, nil)
continue
}
// Ignore the transactions which are failed for fundamental
// validation such as invalid parameters.
if err := tracker.pool.ValidateTxBasics(tx); err != nil {
log.Debug("Invalid transaction submitted", "hash", tx.Hash(), "err", err)
errors = append(errors, err)
continue
}
// If we're already tracking it, it's a no-op
if _, ok := tracker.all[tx.Hash()]; ok {
errors = append(errors, nil)
continue
}
// Theoretically, checking the error here is unnecessary since sender recovery
// is already part of basic validation. However, retrieving the sender address
// from the transaction cache is effectively a no-op if it was previously verified.
// Therefore, the error is still checked just in case.
addr, err := types.Sender(tracker.signer, tx)
if err != nil { // Ignore this tx
if err != nil {
errors = append(errors, err)
continue
}
errors = append(errors, nil)
tracker.all[tx.Hash()] = tx
if tracker.byAddr[addr] == nil {
tracker.byAddr[addr] = legacypool.NewSortedMap()
@ -107,6 +124,7 @@ func (tracker *TxTracker) TrackAll(txs []*types.Transaction) {
}
}
localGauge.Update(int64(len(tracker.all)))
return errors
}
// recheck checks and returns any transactions that needs to be resubmitted.

View file

@ -129,6 +129,12 @@ type SubPool interface {
// retrieve blobs from the pools directly instead of the network.
GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Proof)
// ValidateTxBasics checks whether a transaction is valid according to the consensus
// rules, but does not check state-dependent validation such as sufficient balance.
// This check is meant as a static check which can be performed without holding the
// pool mutex.
ValidateTxBasics(tx *types.Transaction) error
// Add enqueues a batch of transactions into the pool if they are valid. Due
// to the large transaction churn, add may postpone fully integrating the tx
// to a later point to batch multiple ones together.

View file

@ -325,6 +325,17 @@ func (p *TxPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Pr
return nil, nil
}
// ValidateTxBasics checks whether a transaction is valid according to the consensus
// rules, but does not check state-dependent validation such as sufficient balance.
func (p *TxPool) ValidateTxBasics(tx *types.Transaction) error {
for _, subpool := range p.subpools {
if subpool.Filter(tx) {
return subpool.ValidateTxBasics(tx)
}
}
return fmt.Errorf("%w: received type %d", core.ErrTxTypeNotSupported, tx.Type())
}
// Add enqueues a batch of transactions into the pool if they are valid. Due
// to the large transaction churn, add may postpone fully integrating the tx
// to a later point to batch multiple ones together.

View file

@ -17,7 +17,6 @@
package txpool
import (
"crypto/sha256"
"errors"
"fmt"
"math/big"
@ -170,20 +169,11 @@ func validateBlobSidecar(hashes []common.Hash, sidecar *types.BlobTxSidecar) err
if len(sidecar.Blobs) != len(hashes) {
return fmt.Errorf("invalid number of %d blobs compared to %d blob hashes", len(sidecar.Blobs), len(hashes))
}
if len(sidecar.Commitments) != len(hashes) {
return fmt.Errorf("invalid number of %d blob commitments compared to %d blob hashes", len(sidecar.Commitments), len(hashes))
}
if len(sidecar.Proofs) != len(hashes) {
return fmt.Errorf("invalid number of %d blob proofs compared to %d blob hashes", len(sidecar.Proofs), len(hashes))
}
// Blob quantities match up, validate that the provers match with the
// transaction hash before getting to the cryptography
hasher := sha256.New()
for i, vhash := range hashes {
computed := kzg4844.CalcBlobHashV1(hasher, &sidecar.Commitments[i])
if vhash != computed {
return fmt.Errorf("blob %d: computed hash %#x mismatches transaction one %#x", i, computed, vhash)
}
if err := sidecar.ValidateBlobCommitmentHashes(hashes); err != nil {
return err
}
// Blob commitments match with the hashes in the transaction, verify the
// blobs themselves via KZG
@ -206,7 +196,7 @@ type ValidationOptionsWithState struct {
// nonce gaps will be ignored and permitted.
FirstNonceGap func(addr common.Address) uint64
// UsedAndLeftSlots is a mandatory callback to retrieve the number of tx slots
// UsedAndLeftSlots is an optional callback to retrieve the number of tx slots
// used and the number still permitted for an account. New transactions will
// be rejected once the number of remaining slots reaches zero.
UsedAndLeftSlots func(addr common.Address) (int, int)
@ -218,11 +208,6 @@ type ValidationOptionsWithState struct {
// ExistingCost is a mandatory callback to retrieve an already pooled
// transaction's cost with the given nonce to check for overdrafts.
ExistingCost func(addr common.Address, nonce uint64) *big.Int
// KnownConflicts is an optional callback which iterates over the list of
// addresses and returns all addresses known to the pool with in-flight
// transactions.
KnownConflicts func(sender common.Address, authorizers []common.Address) []common.Address
}
// ValidateTransactionWithState is a helper method to check whether a transaction
@ -273,16 +258,10 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op
// Transaction takes a new nonce value out of the pool. Ensure it doesn't
// overflow the number of permitted transactions from a single account
// (i.e. max cancellable via out-of-bound transaction).
if opts.UsedAndLeftSlots != nil {
if used, left := opts.UsedAndLeftSlots(from); left <= 0 {
return fmt.Errorf("%w: pooled %d txs", ErrAccountLimitExceeded, used)
}
// Verify no authorizations will invalidate existing transactions known to
// the pool.
if opts.KnownConflicts != nil {
if conflicts := opts.KnownConflicts(from, tx.SetCodeAuthorities()); len(conflicts) > 0 {
return fmt.Errorf("%w: authorization conflicts with other known tx", ErrAuthorityReserved)
}
}
}
return nil

View file

@ -483,15 +483,23 @@ func (tx *Transaction) SetCodeAuthorizations() []SetCodeAuthorization {
return setcodetx.AuthList
}
// SetCodeAuthorities returns a list of each authorization's corresponding authority.
// SetCodeAuthorities returns a list of unique authorities from the
// authorization list.
func (tx *Transaction) SetCodeAuthorities() []common.Address {
setcodetx, ok := tx.inner.(*SetCodeTx)
if !ok {
return nil
}
auths := make([]common.Address, 0, len(setcodetx.AuthList))
var (
marks = make(map[common.Address]bool)
auths = make([]common.Address, 0, len(setcodetx.AuthList))
)
for _, auth := range setcodetx.AuthList {
if addr, err := auth.Authority(); err == nil {
if marks[addr] {
continue
}
marks[addr] = true
auths = append(auths, addr)
}
}

View file

@ -514,6 +514,5 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
// Now set the inner transaction.
tx.setDecoded(inner, 0)
// TODO: check hash here?
return nil
}

View file

@ -19,6 +19,7 @@ package types
import (
"bytes"
"crypto/sha256"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
@ -85,6 +86,22 @@ func (sc *BlobTxSidecar) encodedSize() uint64 {
return rlp.ListSize(blobs) + rlp.ListSize(commitments) + rlp.ListSize(proofs)
}
// ValidateBlobCommitmentHashes checks whether the given hashes correspond to the
// commitments in the sidecar
func (sc *BlobTxSidecar) ValidateBlobCommitmentHashes(hashes []common.Hash) error {
if len(sc.Commitments) != len(hashes) {
return fmt.Errorf("invalid number of %d blob commitments compared to %d blob hashes", len(sc.Commitments), len(hashes))
}
hasher := sha256.New()
for i, vhash := range hashes {
computed := kzg4844.CalcBlobHashV1(hasher, &sc.Commitments[i])
if vhash != computed {
return fmt.Errorf("blob %d: computed hash %#x mismatches transaction one %#x", i, computed, vhash)
}
}
return nil
}
// blobTxWithBlobs is used for encoding of transactions when blobs are present.
type blobTxWithBlobs struct {
BlobTx *BlobTx

View file

@ -184,6 +184,9 @@ func UnmarshalPubkey(pub []byte) (*ecdsa.PublicKey, error) {
return &ecdsa.PublicKey{Curve: S256(), X: x, Y: y}, nil
}
// FromECDSAPub converts a secp256k1 public key to bytes.
// Note: it does not use the curve from pub, instead it always
// encodes using secp256k1.
func FromECDSAPub(pub *ecdsa.PublicKey) []byte {
if pub == nil || pub.X == nil || pub.Y == nil {
return nil

View file

@ -181,7 +181,7 @@ func TestLoadECDSA(t *testing.T) {
}
for _, test := range tests {
f, err := os.CreateTemp("", "loadecdsa_test.*.txt")
f, err := os.CreateTemp(t.TempDir(), "loadecdsa_test.*.txt")
if err != nil {
t.Fatal(err)
}
@ -202,7 +202,7 @@ func TestLoadECDSA(t *testing.T) {
}
func TestSaveECDSA(t *testing.T) {
f, err := os.CreateTemp("", "saveecdsa_test.*.txt")
f, err := os.CreateTemp(t.TempDir(), "saveecdsa_test.*.txt")
if err != nil {
t.Fatal(err)
}

View file

@ -109,8 +109,8 @@ int secp256k1_ext_scalar_mul(const secp256k1_context* ctx, unsigned char *point,
ARG_CHECK(scalar != NULL);
(void)ctx;
secp256k1_fe_set_b32(&feX, point);
secp256k1_fe_set_b32(&feY, point+32);
secp256k1_fe_set_b32_limit(&feX, point);
secp256k1_fe_set_b32_limit(&feY, point+32);
secp256k1_ge_set_xy(&ge, &feX, &feY);
secp256k1_scalar_set_b32(&s, scalar, &overflow);
if (overflow || secp256k1_scalar_is_zero(&s)) {

View file

@ -0,0 +1,101 @@
env:
### cirrus config
CIRRUS_CLONE_DEPTH: 1
### compiler options
HOST:
WRAPPER_CMD:
# Specific warnings can be disabled with -Wno-error=foo.
# -pedantic-errors is not equivalent to -Werror=pedantic and thus not implied by -Werror according to the GCC manual.
WERROR_CFLAGS: -Werror -pedantic-errors
MAKEFLAGS: -j4
BUILD: check
### secp256k1 config
ECMULTWINDOW: 15
ECMULTGENKB: 22
ASM: no
WIDEMUL: auto
WITH_VALGRIND: yes
EXTRAFLAGS:
### secp256k1 modules
EXPERIMENTAL: no
ECDH: no
RECOVERY: no
EXTRAKEYS: no
SCHNORRSIG: no
MUSIG: no
ELLSWIFT: no
### test options
SECP256K1_TEST_ITERS: 64
BENCH: yes
SECP256K1_BENCH_ITERS: 2
CTIMETESTS: yes
# Compile and run the tests
EXAMPLES: yes
cat_logs_snippet: &CAT_LOGS
always:
cat_tests_log_script:
- cat tests.log || true
cat_noverify_tests_log_script:
- cat noverify_tests.log || true
cat_exhaustive_tests_log_script:
- cat exhaustive_tests.log || true
cat_ctime_tests_log_script:
- cat ctime_tests.log || true
cat_bench_log_script:
- cat bench.log || true
cat_config_log_script:
- cat config.log || true
cat_test_env_script:
- cat test_env.log || true
cat_ci_env_script:
- env
linux_arm64_container_snippet: &LINUX_ARM64_CONTAINER
env_script:
- env | tee /tmp/env
build_script:
- DOCKER_BUILDKIT=1 docker build --file "ci/linux-debian.Dockerfile" --tag="ci_secp256k1_arm"
- docker image prune --force # Cleanup stale layers
test_script:
- docker run --rm --mount "type=bind,src=./,dst=/ci_secp256k1" --env-file /tmp/env --replace --name "ci_secp256k1_arm" "ci_secp256k1_arm" bash -c "cd /ci_secp256k1/ && ./ci/ci.sh"
task:
name: "ARM64: Linux (Debian stable)"
persistent_worker:
labels:
type: arm64
env:
ECDH: yes
RECOVERY: yes
EXTRAKEYS: yes
SCHNORRSIG: yes
MUSIG: yes
ELLSWIFT: yes
matrix:
# Currently only gcc-snapshot, the other compilers are tested on GHA with QEMU
- env: { CC: 'gcc-snapshot' }
<< : *LINUX_ARM64_CONTAINER
<< : *CAT_LOGS
task:
name: "ARM64: Linux (Debian stable), Valgrind"
persistent_worker:
labels:
type: arm64
env:
ECDH: yes
RECOVERY: yes
EXTRAKEYS: yes
SCHNORRSIG: yes
MUSIG: yes
ELLSWIFT: yes
WRAPPER_CMD: 'valgrind --error-exitcode=42'
SECP256K1_TEST_ITERS: 2
matrix:
- env: { CC: 'gcc' }
- env: { CC: 'clang' }
- env: { CC: 'gcc-snapshot' }
- env: { CC: 'clang-snapshot' }
<< : *LINUX_ARM64_CONTAINER
<< : *CAT_LOGS

View file

@ -0,0 +1,2 @@
src/precomputed_ecmult.c linguist-generated
src/precomputed_ecmult_gen.c linguist-generated

View file

@ -0,0 +1,33 @@
name: "Install Valgrind"
description: "Install Homebrew's Valgrind package and cache it."
runs:
using: "composite"
steps:
- run: |
brew tap LouisBrunner/valgrind
brew fetch --HEAD LouisBrunner/valgrind/valgrind
echo "CI_HOMEBREW_CELLAR_VALGRIND=$(brew --cellar valgrind)" >> "$GITHUB_ENV"
shell: bash
- run: |
sw_vers > valgrind_fingerprint
brew --version >> valgrind_fingerprint
git -C "$(brew --cache)/valgrind--git" rev-parse HEAD >> valgrind_fingerprint
cat valgrind_fingerprint
shell: bash
- uses: actions/cache@v4
id: cache
with:
path: ${{ env.CI_HOMEBREW_CELLAR_VALGRIND }}
key: ${{ github.job }}-valgrind-${{ hashFiles('valgrind_fingerprint') }}
- if: steps.cache.outputs.cache-hit != 'true'
run: |
brew install --HEAD LouisBrunner/valgrind/valgrind
shell: bash
- if: steps.cache.outputs.cache-hit == 'true'
run: |
brew link valgrind
shell: bash

View file

@ -0,0 +1,54 @@
name: 'Run in Docker with environment'
description: 'Run a command in a Docker container, while passing explicitly set environment variables into the container.'
inputs:
dockerfile:
description: 'A Dockerfile that defines an image'
required: true
tag:
description: 'A tag of an image'
required: true
command:
description: 'A command to run in a container'
required: false
default: ./ci/ci.sh
runs:
using: "composite"
steps:
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v5
id: main_builder
continue-on-error: true
with:
context: .
file: ${{ inputs.dockerfile }}
tags: ${{ inputs.tag }}
load: true
cache-from: type=gha
- uses: docker/build-push-action@v5
id: retry_builder
if: steps.main_builder.outcome == 'failure'
with:
context: .
file: ${{ inputs.dockerfile }}
tags: ${{ inputs.tag }}
load: true
cache-from: type=gha
- # Workaround for https://github.com/google/sanitizers/issues/1614 .
# The underlying issue has been fixed in clang 18.1.3.
run: sudo sysctl -w vm.mmap_rnd_bits=28
shell: bash
- # Tell Docker to pass environment variables in `env` into the container.
run: >
docker run \
$(echo '${{ toJSON(env) }}' | jq -r 'keys[] | "--env \(.) "') \
--volume ${{ github.workspace }}:${{ github.workspace }} \
--workdir ${{ github.workspace }} \
${{ inputs.tag }} bash -c "
git config --global --add safe.directory ${{ github.workspace }}
${{ inputs.command }}
"
shell: bash

View file

@ -0,0 +1,890 @@
name: CI
on:
pull_request:
push:
branches:
- '**'
tags-ignore:
- '**'
concurrency:
group: ${{ github.event_name != 'pull_request' && github.run_id || github.ref }}
cancel-in-progress: true
env:
### compiler options
HOST:
WRAPPER_CMD:
# Specific warnings can be disabled with -Wno-error=foo.
# -pedantic-errors is not equivalent to -Werror=pedantic and thus not implied by -Werror according to the GCC manual.
WERROR_CFLAGS: '-Werror -pedantic-errors'
MAKEFLAGS: '-j4'
BUILD: 'check'
### secp256k1 config
ECMULTWINDOW: 15
ECMULTGENKB: 86
ASM: 'no'
WIDEMUL: 'auto'
WITH_VALGRIND: 'yes'
EXTRAFLAGS:
### secp256k1 modules
EXPERIMENTAL: 'no'
ECDH: 'no'
RECOVERY: 'no'
EXTRAKEYS: 'no'
SCHNORRSIG: 'no'
MUSIG: 'no'
ELLSWIFT: 'no'
### test options
SECP256K1_TEST_ITERS: 64
BENCH: 'yes'
SECP256K1_BENCH_ITERS: 2
CTIMETESTS: 'yes'
# Compile and run the examples.
EXAMPLES: 'yes'
jobs:
docker_cache:
name: "Build Docker image"
runs-on: ubuntu-latest
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
# See: https://github.com/moby/buildkit/issues/3969.
driver-opts: |
network=host
- name: Build container
uses: docker/build-push-action@v5
with:
file: ./ci/linux-debian.Dockerfile
tags: linux-debian-image
cache-from: type=gha
cache-to: type=gha,mode=min
linux_debian:
name: "x86_64: Linux (Debian stable)"
runs-on: ubuntu-latest
needs: docker_cache
strategy:
fail-fast: false
matrix:
configuration:
- env_vars: { WIDEMUL: 'int64', RECOVERY: 'yes' }
- env_vars: { WIDEMUL: 'int64', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' }
- env_vars: { WIDEMUL: 'int128' }
- env_vars: { WIDEMUL: 'int128_struct', ELLSWIFT: 'yes' }
- env_vars: { WIDEMUL: 'int128', RECOVERY: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' }
- env_vars: { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes' }
- env_vars: { WIDEMUL: 'int128', ASM: 'x86_64', ELLSWIFT: 'yes' }
- env_vars: { RECOVERY: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes' }
- env_vars: { CTIMETESTS: 'no', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', CPPFLAGS: '-DVERIFY' }
- env_vars: { BUILD: 'distcheck', WITH_VALGRIND: 'no', CTIMETESTS: 'no', BENCH: 'no' }
- env_vars: { CPPFLAGS: '-DDETERMINISTIC' }
- env_vars: { CFLAGS: '-O0', CTIMETESTS: 'no' }
- env_vars: { CFLAGS: '-O1', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' }
- env_vars: { ECMULTGENKB: 2, ECMULTWINDOW: 2 }
- env_vars: { ECMULTGENKB: 86, ECMULTWINDOW: 4 }
cc:
- 'gcc'
- 'clang'
- 'gcc-snapshot'
- 'clang-snapshot'
env:
CC: ${{ matrix.cc }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: CI script
env: ${{ matrix.configuration.env_vars }}
uses: ./.github/actions/run-in-docker-action
with:
dockerfile: ./ci/linux-debian.Dockerfile
tag: linux-debian-image
- run: cat tests.log || true
if: ${{ always() }}
- run: cat noverify_tests.log || true
if: ${{ always() }}
- run: cat exhaustive_tests.log || true
if: ${{ always() }}
- run: cat ctime_tests.log || true
if: ${{ always() }}
- run: cat bench.log || true
if: ${{ always() }}
- run: cat config.log || true
if: ${{ always() }}
- run: cat test_env.log || true
if: ${{ always() }}
- name: CI env
run: env
if: ${{ always() }}
i686_debian:
name: "i686: Linux (Debian stable)"
runs-on: ubuntu-latest
needs: docker_cache
strategy:
fail-fast: false
matrix:
cc:
- 'i686-linux-gnu-gcc'
- 'clang --target=i686-pc-linux-gnu -isystem /usr/i686-linux-gnu/include'
env:
HOST: 'i686-linux-gnu'
ECDH: 'yes'
RECOVERY: 'yes'
EXTRAKEYS: 'yes'
SCHNORRSIG: 'yes'
MUSIG: 'yes'
ELLSWIFT: 'yes'
CC: ${{ matrix.cc }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: CI script
uses: ./.github/actions/run-in-docker-action
with:
dockerfile: ./ci/linux-debian.Dockerfile
tag: linux-debian-image
- run: cat tests.log || true
if: ${{ always() }}
- run: cat noverify_tests.log || true
if: ${{ always() }}
- run: cat exhaustive_tests.log || true
if: ${{ always() }}
- run: cat ctime_tests.log || true
if: ${{ always() }}
- run: cat bench.log || true
if: ${{ always() }}
- run: cat config.log || true
if: ${{ always() }}
- run: cat test_env.log || true
if: ${{ always() }}
- name: CI env
run: env
if: ${{ always() }}
s390x_debian:
name: "s390x (big-endian): Linux (Debian stable, QEMU)"
runs-on: ubuntu-latest
needs: docker_cache
env:
WRAPPER_CMD: 'qemu-s390x'
SECP256K1_TEST_ITERS: 16
HOST: 's390x-linux-gnu'
WITH_VALGRIND: 'no'
ECDH: 'yes'
RECOVERY: 'yes'
EXTRAKEYS: 'yes'
SCHNORRSIG: 'yes'
MUSIG: 'yes'
ELLSWIFT: 'yes'
CTIMETESTS: 'no'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: CI script
uses: ./.github/actions/run-in-docker-action
with:
dockerfile: ./ci/linux-debian.Dockerfile
tag: linux-debian-image
- run: cat tests.log || true
if: ${{ always() }}
- run: cat noverify_tests.log || true
if: ${{ always() }}
- run: cat exhaustive_tests.log || true
if: ${{ always() }}
- run: cat ctime_tests.log || true
if: ${{ always() }}
- run: cat bench.log || true
if: ${{ always() }}
- run: cat config.log || true
if: ${{ always() }}
- run: cat test_env.log || true
if: ${{ always() }}
- name: CI env
run: env
if: ${{ always() }}
arm32_debian:
name: "ARM32: Linux (Debian stable, QEMU)"
runs-on: ubuntu-latest
needs: docker_cache
strategy:
fail-fast: false
matrix:
configuration:
- env_vars: {}
- env_vars: { EXPERIMENTAL: 'yes', ASM: 'arm32' }
env:
WRAPPER_CMD: 'qemu-arm'
SECP256K1_TEST_ITERS: 16
HOST: 'arm-linux-gnueabihf'
WITH_VALGRIND: 'no'
ECDH: 'yes'
RECOVERY: 'yes'
EXTRAKEYS: 'yes'
SCHNORRSIG: 'yes'
MUSIG: 'yes'
ELLSWIFT: 'yes'
CTIMETESTS: 'no'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: CI script
env: ${{ matrix.configuration.env_vars }}
uses: ./.github/actions/run-in-docker-action
with:
dockerfile: ./ci/linux-debian.Dockerfile
tag: linux-debian-image
- run: cat tests.log || true
if: ${{ always() }}
- run: cat noverify_tests.log || true
if: ${{ always() }}
- run: cat exhaustive_tests.log || true
if: ${{ always() }}
- run: cat ctime_tests.log || true
if: ${{ always() }}
- run: cat bench.log || true
if: ${{ always() }}
- run: cat config.log || true
if: ${{ always() }}
- run: cat test_env.log || true
if: ${{ always() }}
- name: CI env
run: env
if: ${{ always() }}
arm64_debian:
name: "ARM64: Linux (Debian stable, QEMU)"
runs-on: ubuntu-latest
needs: docker_cache
env:
WRAPPER_CMD: 'qemu-aarch64'
SECP256K1_TEST_ITERS: 16
HOST: 'aarch64-linux-gnu'
WITH_VALGRIND: 'no'
ECDH: 'yes'
RECOVERY: 'yes'
EXTRAKEYS: 'yes'
SCHNORRSIG: 'yes'
MUSIG: 'yes'
ELLSWIFT: 'yes'
CTIMETESTS: 'no'
strategy:
fail-fast: false
matrix:
configuration:
- env_vars: { } # gcc
- env_vars: # clang
CC: 'clang --target=aarch64-linux-gnu'
- env_vars: # clang-snapshot
CC: 'clang-snapshot --target=aarch64-linux-gnu'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: CI script
env: ${{ matrix.configuration.env_vars }}
uses: ./.github/actions/run-in-docker-action
with:
dockerfile: ./ci/linux-debian.Dockerfile
tag: linux-debian-image
- run: cat tests.log || true
if: ${{ always() }}
- run: cat noverify_tests.log || true
if: ${{ always() }}
- run: cat exhaustive_tests.log || true
if: ${{ always() }}
- run: cat ctime_tests.log || true
if: ${{ always() }}
- run: cat bench.log || true
if: ${{ always() }}
- run: cat config.log || true
if: ${{ always() }}
- run: cat test_env.log || true
if: ${{ always() }}
- name: CI env
run: env
if: ${{ always() }}
ppc64le_debian:
name: "ppc64le: Linux (Debian stable, QEMU)"
runs-on: ubuntu-latest
needs: docker_cache
env:
WRAPPER_CMD: 'qemu-ppc64le'
SECP256K1_TEST_ITERS: 16
HOST: 'powerpc64le-linux-gnu'
WITH_VALGRIND: 'no'
ECDH: 'yes'
RECOVERY: 'yes'
EXTRAKEYS: 'yes'
SCHNORRSIG: 'yes'
MUSIG: 'yes'
ELLSWIFT: 'yes'
CTIMETESTS: 'no'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: CI script
uses: ./.github/actions/run-in-docker-action
with:
dockerfile: ./ci/linux-debian.Dockerfile
tag: linux-debian-image
- run: cat tests.log || true
if: ${{ always() }}
- run: cat noverify_tests.log || true
if: ${{ always() }}
- run: cat exhaustive_tests.log || true
if: ${{ always() }}
- run: cat ctime_tests.log || true
if: ${{ always() }}
- run: cat bench.log || true
if: ${{ always() }}
- run: cat config.log || true
if: ${{ always() }}
- run: cat test_env.log || true
if: ${{ always() }}
- name: CI env
run: env
if: ${{ always() }}
valgrind_debian:
name: "Valgrind (memcheck)"
runs-on: ubuntu-latest
needs: docker_cache
strategy:
fail-fast: false
matrix:
configuration:
- env_vars: { CC: 'clang', ASM: 'auto' }
- env_vars: { CC: 'i686-linux-gnu-gcc', HOST: 'i686-linux-gnu', ASM: 'auto' }
- env_vars: { CC: 'clang', ASM: 'no', ECMULTGENKB: 2, ECMULTWINDOW: 2 }
- env_vars: { CC: 'i686-linux-gnu-gcc', HOST: 'i686-linux-gnu', ASM: 'no', ECMULTGENKB: 2, ECMULTWINDOW: 2 }
env:
# The `--error-exitcode` is required to make the test fail if valgrind found errors,
# otherwise it will return 0 (https://www.valgrind.org/docs/manual/manual-core.html).
WRAPPER_CMD: 'valgrind --error-exitcode=42'
ECDH: 'yes'
RECOVERY: 'yes'
EXTRAKEYS: 'yes'
SCHNORRSIG: 'yes'
MUSIG: 'yes'
ELLSWIFT: 'yes'
CTIMETESTS: 'no'
SECP256K1_TEST_ITERS: 2
steps:
- name: Checkout
uses: actions/checkout@v4
- name: CI script
env: ${{ matrix.configuration.env_vars }}
uses: ./.github/actions/run-in-docker-action
with:
dockerfile: ./ci/linux-debian.Dockerfile
tag: linux-debian-image
- run: cat tests.log || true
if: ${{ always() }}
- run: cat noverify_tests.log || true
if: ${{ always() }}
- run: cat exhaustive_tests.log || true
if: ${{ always() }}
- run: cat ctime_tests.log || true
if: ${{ always() }}
- run: cat bench.log || true
if: ${{ always() }}
- run: cat config.log || true
if: ${{ always() }}
- run: cat test_env.log || true
if: ${{ always() }}
- name: CI env
run: env
if: ${{ always() }}
sanitizers_debian:
name: "UBSan, ASan, LSan"
runs-on: ubuntu-latest
needs: docker_cache
strategy:
fail-fast: false
matrix:
configuration:
- env_vars: { CC: 'clang', ASM: 'auto' }
- env_vars: { CC: 'i686-linux-gnu-gcc', HOST: 'i686-linux-gnu', ASM: 'auto' }
- env_vars: { CC: 'clang', ASM: 'no', ECMULTGENKB: 2, ECMULTWINDOW: 2 }
- env_vars: { CC: 'i686-linux-gnu-gcc', HOST: 'i686-linux-gnu', ASM: 'no', ECMULTGENKB: 2, ECMULTWINDOW: 2 }
env:
ECDH: 'yes'
RECOVERY: 'yes'
EXTRAKEYS: 'yes'
SCHNORRSIG: 'yes'
MUSIG: 'yes'
ELLSWIFT: 'yes'
CTIMETESTS: 'no'
CFLAGS: '-fsanitize=undefined,address -g'
UBSAN_OPTIONS: 'print_stacktrace=1:halt_on_error=1'
ASAN_OPTIONS: 'strict_string_checks=1:detect_stack_use_after_return=1:detect_leaks=1'
LSAN_OPTIONS: 'use_unaligned=1'
SECP256K1_TEST_ITERS: 32
steps:
- name: Checkout
uses: actions/checkout@v4
- name: CI script
env: ${{ matrix.configuration.env_vars }}
uses: ./.github/actions/run-in-docker-action
with:
dockerfile: ./ci/linux-debian.Dockerfile
tag: linux-debian-image
- run: cat tests.log || true
if: ${{ always() }}
- run: cat noverify_tests.log || true
if: ${{ always() }}
- run: cat exhaustive_tests.log || true
if: ${{ always() }}
- run: cat ctime_tests.log || true
if: ${{ always() }}
- run: cat bench.log || true
if: ${{ always() }}
- run: cat config.log || true
if: ${{ always() }}
- run: cat test_env.log || true
if: ${{ always() }}
- name: CI env
run: env
if: ${{ always() }}
msan_debian:
name: "MSan"
runs-on: ubuntu-latest
needs: docker_cache
strategy:
fail-fast: false
matrix:
configuration:
- env_vars:
CTIMETESTS: 'yes'
CFLAGS: '-fsanitize=memory -fsanitize-recover=memory -g'
- env_vars:
ECMULTGENKB: 2
ECMULTWINDOW: 2
CTIMETESTS: 'yes'
CFLAGS: '-fsanitize=memory -fsanitize-recover=memory -g -O3'
- env_vars:
# -fsanitize-memory-param-retval is clang's default, but our build system disables it
# when ctime_tests when enabled.
CFLAGS: '-fsanitize=memory -fsanitize-recover=memory -fsanitize-memory-param-retval -g'
CTIMETESTS: 'no'
env:
ECDH: 'yes'
RECOVERY: 'yes'
EXTRAKEYS: 'yes'
SCHNORRSIG: 'yes'
MUSIG: 'yes'
ELLSWIFT: 'yes'
CC: 'clang'
SECP256K1_TEST_ITERS: 32
ASM: 'no'
WITH_VALGRIND: 'no'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: CI script
env: ${{ matrix.configuration.env_vars }}
uses: ./.github/actions/run-in-docker-action
with:
dockerfile: ./ci/linux-debian.Dockerfile
tag: linux-debian-image
- run: cat tests.log || true
if: ${{ always() }}
- run: cat noverify_tests.log || true
if: ${{ always() }}
- run: cat exhaustive_tests.log || true
if: ${{ always() }}
- run: cat ctime_tests.log || true
if: ${{ always() }}
- run: cat bench.log || true
if: ${{ always() }}
- run: cat config.log || true
if: ${{ always() }}
- run: cat test_env.log || true
if: ${{ always() }}
- name: CI env
run: env
if: ${{ always() }}
mingw_debian:
name: ${{ matrix.configuration.job_name }}
runs-on: ubuntu-latest
needs: docker_cache
env:
WRAPPER_CMD: 'wine'
WITH_VALGRIND: 'no'
ECDH: 'yes'
RECOVERY: 'yes'
EXTRAKEYS: 'yes'
SCHNORRSIG: 'yes'
MUSIG: 'yes'
ELLSWIFT: 'yes'
CTIMETESTS: 'no'
strategy:
fail-fast: false
matrix:
configuration:
- job_name: 'x86_64 (mingw32-w64): Windows (Debian stable, Wine)'
env_vars:
HOST: 'x86_64-w64-mingw32'
- job_name: 'i686 (mingw32-w64): Windows (Debian stable, Wine)'
env_vars:
HOST: 'i686-w64-mingw32'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: CI script
env: ${{ matrix.configuration.env_vars }}
uses: ./.github/actions/run-in-docker-action
with:
dockerfile: ./ci/linux-debian.Dockerfile
tag: linux-debian-image
- run: cat tests.log || true
if: ${{ always() }}
- run: cat noverify_tests.log || true
if: ${{ always() }}
- run: cat exhaustive_tests.log || true
if: ${{ always() }}
- run: cat ctime_tests.log || true
if: ${{ always() }}
- run: cat bench.log || true
if: ${{ always() }}
- run: cat config.log || true
if: ${{ always() }}
- run: cat test_env.log || true
if: ${{ always() }}
- name: CI env
run: env
if: ${{ always() }}
x86_64-macos-native:
name: "x86_64: macOS Ventura, Valgrind"
# See: https://github.com/actions/runner-images#available-images.
runs-on: macos-13
env:
CC: 'clang'
HOMEBREW_NO_AUTO_UPDATE: 1
HOMEBREW_NO_INSTALL_CLEANUP: 1
strategy:
fail-fast: false
matrix:
env_vars:
- { WIDEMUL: 'int64', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' }
- { WIDEMUL: 'int128_struct', ECMULTGENKB: 2, ECMULTWINDOW: 4 }
- { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' }
- { WIDEMUL: 'int128', RECOVERY: 'yes' }
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' }
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', CC: 'gcc' }
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 }
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', CC: 'gcc', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 }
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', CPPFLAGS: '-DVERIFY', CTIMETESTS: 'no' }
- BUILD: 'distcheck'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Homebrew packages
run: |
brew install --quiet automake libtool gcc
ln -s $(brew --prefix gcc)/bin/gcc-?? /usr/local/bin/gcc
- name: Install and cache Valgrind
uses: ./.github/actions/install-homebrew-valgrind
- name: CI script
env: ${{ matrix.env_vars }}
run: ./ci/ci.sh
- run: cat tests.log || true
if: ${{ always() }}
- run: cat noverify_tests.log || true
if: ${{ always() }}
- run: cat exhaustive_tests.log || true
if: ${{ always() }}
- run: cat ctime_tests.log || true
if: ${{ always() }}
- run: cat bench.log || true
if: ${{ always() }}
- run: cat config.log || true
if: ${{ always() }}
- run: cat test_env.log || true
if: ${{ always() }}
- name: CI env
run: env
if: ${{ always() }}
arm64-macos-native:
name: "ARM64: macOS Sonoma"
# See: https://github.com/actions/runner-images#available-images.
runs-on: macos-14
env:
CC: 'clang'
HOMEBREW_NO_AUTO_UPDATE: 1
HOMEBREW_NO_INSTALL_CLEANUP: 1
WITH_VALGRIND: 'no'
CTIMETESTS: 'no'
strategy:
fail-fast: false
matrix:
env_vars:
- { WIDEMUL: 'int64', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes' }
- { WIDEMUL: 'int128_struct', ECMULTGENPRECISION: 2, ECMULTWINDOW: 4 }
- { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes' }
- { WIDEMUL: 'int128', RECOVERY: 'yes' }
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes' }
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', CC: 'gcc' }
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', CPPFLAGS: '-DVERIFY' }
- BUILD: 'distcheck'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Homebrew packages
run: |
brew install --quiet automake libtool gcc
ln -s $(brew --prefix gcc)/bin/gcc-?? /usr/local/bin/gcc
- name: CI script
env: ${{ matrix.env_vars }}
run: ./ci/ci.sh
- run: cat tests.log || true
if: ${{ always() }}
- run: cat noverify_tests.log || true
if: ${{ always() }}
- run: cat exhaustive_tests.log || true
if: ${{ always() }}
- run: cat ctime_tests.log || true
if: ${{ always() }}
- run: cat bench.log || true
if: ${{ always() }}
- run: cat config.log || true
if: ${{ always() }}
- run: cat test_env.log || true
if: ${{ always() }}
- name: CI env
run: env
if: ${{ always() }}
win64-native:
name: ${{ matrix.configuration.job_name }}
# See: https://github.com/actions/runner-images#available-images.
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
configuration:
- job_name: 'x64 (MSVC): Windows (VS 2022, shared)'
cmake_options: '-A x64 -DBUILD_SHARED_LIBS=ON'
- job_name: 'x64 (MSVC): Windows (VS 2022, static)'
cmake_options: '-A x64 -DBUILD_SHARED_LIBS=OFF'
- job_name: 'x64 (MSVC): Windows (VS 2022, int128_struct)'
cmake_options: '-A x64 -DSECP256K1_TEST_OVERRIDE_WIDE_MULTIPLY=int128_struct'
- job_name: 'x64 (MSVC): Windows (VS 2022, int128_struct with __(u)mulh)'
cmake_options: '-A x64 -DSECP256K1_TEST_OVERRIDE_WIDE_MULTIPLY=int128_struct'
cpp_flags: '/DSECP256K1_MSVC_MULH_TEST_OVERRIDE'
- job_name: 'x86 (MSVC): Windows (VS 2022)'
cmake_options: '-A Win32'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Generate buildsystem
run: cmake -E env CFLAGS="/WX ${{ matrix.configuration.cpp_flags }}" cmake -B build -DSECP256K1_ENABLE_MODULE_RECOVERY=ON -DSECP256K1_BUILD_EXAMPLES=ON ${{ matrix.configuration.cmake_options }}
- name: Build
run: cmake --build build --config RelWithDebInfo -- /p:UseMultiToolTask=true /maxCpuCount
- name: Binaries info
# Use the bash shell included with Git for Windows.
shell: bash
run: |
cd build/bin/RelWithDebInfo && file *tests.exe bench*.exe libsecp256k1-*.dll || true
- name: Check
run: |
ctest -C RelWithDebInfo --test-dir build -j ([int]$env:NUMBER_OF_PROCESSORS + 1)
build\bin\RelWithDebInfo\bench_ecmult.exe
build\bin\RelWithDebInfo\bench_internal.exe
build\bin\RelWithDebInfo\bench.exe
win64-native-headers:
name: "x64 (MSVC): C++ (public headers)"
# See: https://github.com/actions/runner-images#available-images.
runs-on: windows-2022
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Add cl.exe to PATH
uses: ilammy/msvc-dev-cmd@v1
- name: C++ (public headers)
run: |
cl.exe -c -WX -TP include/*.h
cxx_fpermissive_debian:
name: "C++ -fpermissive (entire project)"
runs-on: ubuntu-latest
needs: docker_cache
env:
CC: 'g++'
CFLAGS: '-fpermissive -g'
CPPFLAGS: '-DSECP256K1_CPLUSPLUS_TEST_OVERRIDE'
WERROR_CFLAGS:
ECDH: 'yes'
RECOVERY: 'yes'
EXTRAKEYS: 'yes'
SCHNORRSIG: 'yes'
MUSIG: 'yes'
ELLSWIFT: 'yes'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: CI script
uses: ./.github/actions/run-in-docker-action
with:
dockerfile: ./ci/linux-debian.Dockerfile
tag: linux-debian-image
- run: cat tests.log || true
if: ${{ always() }}
- run: cat noverify_tests.log || true
if: ${{ always() }}
- run: cat exhaustive_tests.log || true
if: ${{ always() }}
- run: cat ctime_tests.log || true
if: ${{ always() }}
- run: cat bench.log || true
if: ${{ always() }}
- run: cat config.log || true
if: ${{ always() }}
- run: cat test_env.log || true
if: ${{ always() }}
- name: CI env
run: env
if: ${{ always() }}
cxx_headers_debian:
name: "C++ (public headers)"
runs-on: ubuntu-latest
needs: docker_cache
steps:
- name: Checkout
uses: actions/checkout@v4
- name: CI script
uses: ./.github/actions/run-in-docker-action
with:
dockerfile: ./ci/linux-debian.Dockerfile
tag: linux-debian-image
command: |
g++ -Werror include/*.h
clang -Werror -x c++-header include/*.h
sage:
name: "SageMath prover"
runs-on: ubuntu-latest
container:
image: sagemath/sagemath:latest
options: --user root
steps:
- name: Checkout
uses: actions/checkout@v4
- name: CI script
run: |
cd sage
sage prove_group_implementations.sage
release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- run: ./autogen.sh && ./configure --enable-dev-mode && make distcheck
- name: Check installation with Autotools
env:
CI_INSTALL: ${{ runner.temp }}/${{ github.run_id }}${{ github.action }}/install
run: |
./autogen.sh && ./configure --prefix=${{ env.CI_INSTALL }} && make clean && make install && ls -RlAh ${{ env.CI_INSTALL }}
gcc -o ecdsa examples/ecdsa.c $(PKG_CONFIG_PATH=${{ env.CI_INSTALL }}/lib/pkgconfig pkg-config --cflags --libs libsecp256k1) -Wl,-rpath,"${{ env.CI_INSTALL }}/lib" && ./ecdsa
- name: Check installation with CMake
env:
CI_BUILD: ${{ runner.temp }}/${{ github.run_id }}${{ github.action }}/build
CI_INSTALL: ${{ runner.temp }}/${{ github.run_id }}${{ github.action }}/install
run: |
cmake -B ${{ env.CI_BUILD }} -DCMAKE_INSTALL_PREFIX=${{ env.CI_INSTALL }} && cmake --build ${{ env.CI_BUILD }} && cmake --install ${{ env.CI_BUILD }} && ls -RlAh ${{ env.CI_INSTALL }}
gcc -o ecdsa examples/ecdsa.c -I ${{ env.CI_INSTALL }}/include -L ${{ env.CI_INSTALL }}/lib*/ -l secp256k1 -Wl,-rpath,"${{ env.CI_INSTALL }}/lib",-rpath,"${{ env.CI_INSTALL }}/lib64" && ./ecdsa

View file

@ -1,17 +1,24 @@
bench_inv
bench_ecdh
bench_sign
bench_verify
bench_schnorr_verify
bench_recover
bench
bench_ecmult
bench_internal
noverify_tests
tests
exhaustive_tests
gen_context
precompute_ecmult_gen
precompute_ecmult
ctime_tests
ecdh_example
ecdsa_example
schnorr_example
ellswift_example
musig_example
*.exe
*.so
*.a
!.gitignore
*.csv
*.log
*.trs
*.sage.py
Makefile
configure
@ -21,6 +28,7 @@ aclocal.m4
autom4te.cache/
config.log
config.status
conftest*
*.tar.gz
*.la
libtool
@ -29,9 +37,15 @@ libtool
*.lo
*.o
*~
src/libsecp256k1-config.h
src/libsecp256k1-config.h.in
src/ecmult_static_context.h
coverage/
coverage.html
coverage.*.html
*.gcda
*.gcno
*.gcov
build-aux/ar-lib
build-aux/config.guess
build-aux/config.sub
build-aux/depcomp
@ -45,5 +59,9 @@ build-aux/m4/ltversion.m4
build-aux/missing
build-aux/compile
build-aux/test-driver
src/stamp-h1
libsecp256k1.pc
### CMake
/CMakeUserPresets.json
# Default CMake build directory.
/build

View file

@ -1,69 +0,0 @@
language: c
sudo: false
addons:
apt:
packages: libgmp-dev
compiler:
- clang
- gcc
cache:
directories:
- src/java/guava/
env:
global:
- FIELD=auto BIGNUM=auto SCALAR=auto ENDOMORPHISM=no STATICPRECOMPUTATION=yes ASM=no BUILD=check EXTRAFLAGS= HOST= ECDH=no RECOVERY=no EXPERIMENTAL=no
- GUAVA_URL=https://search.maven.org/remotecontent?filepath=com/google/guava/guava/18.0/guava-18.0.jar GUAVA_JAR=src/java/guava/guava-18.0.jar
matrix:
- SCALAR=32bit RECOVERY=yes
- SCALAR=32bit FIELD=32bit ECDH=yes EXPERIMENTAL=yes
- SCALAR=64bit
- FIELD=64bit RECOVERY=yes
- FIELD=64bit ENDOMORPHISM=yes
- FIELD=64bit ENDOMORPHISM=yes ECDH=yes EXPERIMENTAL=yes
- FIELD=64bit ASM=x86_64
- FIELD=64bit ENDOMORPHISM=yes ASM=x86_64
- FIELD=32bit ENDOMORPHISM=yes
- BIGNUM=no
- BIGNUM=no ENDOMORPHISM=yes RECOVERY=yes EXPERIMENTAL=yes
- BIGNUM=no STATICPRECOMPUTATION=no
- BUILD=distcheck
- EXTRAFLAGS=CPPFLAGS=-DDETERMINISTIC
- EXTRAFLAGS=CFLAGS=-O0
- BUILD=check-java ECDH=yes EXPERIMENTAL=yes
matrix:
fast_finish: true
include:
- compiler: clang
env: HOST=i686-linux-gnu ENDOMORPHISM=yes
addons:
apt:
packages:
- gcc-multilib
- libgmp-dev:i386
- compiler: clang
env: HOST=i686-linux-gnu
addons:
apt:
packages:
- gcc-multilib
- compiler: gcc
env: HOST=i686-linux-gnu ENDOMORPHISM=yes
addons:
apt:
packages:
- gcc-multilib
- compiler: gcc
env: HOST=i686-linux-gnu
addons:
apt:
packages:
- gcc-multilib
- libgmp-dev:i386
before_install: mkdir -p `dirname $GUAVA_JAR`
install: if [ ! -f $GUAVA_JAR ]; then wget $GUAVA_URL -O $GUAVA_JAR; fi
before_script: ./autogen.sh
script:
- if [ -n "$HOST" ]; then export USE_HOST="--host=$HOST"; fi
- if [ "x$HOST" = "xi686-linux-gnu" ]; then export CC="$CC -m32"; fi
- ./configure --enable-experimental=$EXPERIMENTAL --enable-endomorphism=$ENDOMORPHISM --with-field=$FIELD --with-bignum=$BIGNUM --with-scalar=$SCALAR --enable-ecmult-static-precomputation=$STATICPRECOMPUTATION --enable-module-ecdh=$ECDH --enable-module-recovery=$RECOVERY $EXTRAFLAGS $USE_HOST && make -j2 $BUILD
os: linux

View file

@ -0,0 +1,177 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.6.0] - 2024-11-04
#### Added
- New module `musig` implements the MuSig2 multisignature scheme according to the [BIP 327 specification](https://github.com/bitcoin/bips/blob/master/bip-0327.mediawiki). See:
- Header file `include/secp256k1_musig.h` which defines the new API.
- Document `doc/musig.md` for further notes on API usage.
- Usage example `examples/musig.c`.
- New CMake variable `SECP256K1_APPEND_LDFLAGS` for appending linker flags to the build command.
#### Changed
- API functions now use a significantly more robust method to clear secrets from the stack before returning. However, secret clearing remains a best-effort security measure and cannot guarantee complete removal.
- Any type `secp256k1_foo` can now be forward-declared using `typedef struct secp256k1_foo secp256k1_foo;` (or also `struct secp256k1_foo;` in C++).
- Organized CMake build artifacts into dedicated directories (`bin/` for executables, `lib/` for libraries) to improve build output structure and Windows shared library compatibility.
#### Removed
- Removed the `secp256k1_scratch_space` struct and its associated functions `secp256k1_scratch_space_create` and `secp256k1_scratch_space_destroy` because the scratch space was unused in the API.
#### ABI Compatibility
The symbols `secp256k1_scratch_space_create` and `secp256k1_scratch_space_destroy` were removed.
Otherwise, the library maintains backward compatibility with versions 0.3.x through 0.5.x.
## [0.5.1] - 2024-08-01
#### Added
- Added usage example for an ElligatorSwift key exchange.
#### Changed
- The default size of the precomputed table for signing was changed from 22 KiB to 86 KiB. The size can be changed with the configure option `--ecmult-gen-kb` (`SECP256K1_ECMULT_GEN_KB` for CMake).
- "auto" is no longer an accepted value for the `--with-ecmult-window` and `--with-ecmult-gen-kb` configure options (this also applies to `SECP256K1_ECMULT_WINDOW_SIZE` and `SECP256K1_ECMULT_GEN_KB` in CMake). To achieve the same configuration as previously provided by the "auto" value, omit setting the configure option explicitly.
#### Fixed
- Fixed compilation when the extrakeys module is disabled.
#### ABI Compatibility
The ABI is backward compatible with versions 0.5.0, 0.4.x and 0.3.x.
## [0.5.0] - 2024-05-06
#### Added
- New function `secp256k1_ec_pubkey_sort` that sorts public keys using lexicographic (of compressed serialization) order.
#### Changed
- The implementation of the point multiplication algorithm used for signing and public key generation was changed, resulting in improved performance for those operations.
- The related configure option `--ecmult-gen-precision` was replaced with `--ecmult-gen-kb` (`SECP256K1_ECMULT_GEN_KB` for CMake).
- This changes the supported precomputed table sizes for these operations. The new supported sizes are 2 KiB, 22 KiB, or 86 KiB (while the old supported sizes were 32 KiB, 64 KiB, or 512 KiB).
#### ABI Compatibility
The ABI is backward compatible with versions 0.4.x and 0.3.x.
## [0.4.1] - 2023-12-21
#### Changed
- The point multiplication algorithm used for ECDH operations (module `ecdh`) was replaced with a slightly faster one.
- Optional handwritten x86_64 assembly for field operations was removed because modern C compilers are able to output more efficient assembly. This change results in a significant speedup of some library functions when handwritten x86_64 assembly is enabled (`--with-asm=x86_64` in GNU Autotools, `-DSECP256K1_ASM=x86_64` in CMake), which is the default on x86_64. Benchmarks with GCC 10.5.0 show a 10% speedup for `secp256k1_ecdsa_verify` and `secp256k1_schnorrsig_verify`.
#### ABI Compatibility
The ABI is backward compatible with versions 0.4.0 and 0.3.x.
## [0.4.0] - 2023-09-04
#### Added
- New module `ellswift` implements ElligatorSwift encoding for public keys and x-only Diffie-Hellman key exchange for them.
ElligatorSwift permits representing secp256k1 public keys as 64-byte arrays which cannot be distinguished from uniformly random. See:
- Header file `include/secp256k1_ellswift.h` which defines the new API.
- Document `doc/ellswift.md` which explains the mathematical background of the scheme.
- The [paper](https://eprint.iacr.org/2022/759) on which the scheme is based.
- We now test the library with unreleased development snapshots of GCC and Clang. This gives us an early chance to catch miscompilations and constant-time issues introduced by the compiler (such as those that led to the previous two releases).
#### Fixed
- Fixed symbol visibility in Windows DLL builds, where three internal library symbols were wrongly exported.
#### Changed
- When consuming libsecp256k1 as a static library on Windows, the user must now define the `SECP256K1_STATIC` macro before including `secp256k1.h`.
#### ABI Compatibility
This release is backward compatible with the ABI of 0.3.0, 0.3.1, and 0.3.2. Symbol visibility is now believed to be handled properly on supported platforms and is now considered to be part of the ABI. Please report any improperly exported symbols as a bug.
## [0.3.2] - 2023-05-13
We strongly recommend updating to 0.3.2 if you use or plan to use GCC >=13 to compile libsecp256k1. When in doubt, check the GCC version using `gcc -v`.
#### Security
- Module `ecdh`: Fix "constant-timeness" issue with GCC 13.1 (and potentially future versions of GCC) that could leave applications using libsecp256k1's ECDH module vulnerable to a timing side-channel attack. The fix avoids secret-dependent control flow during ECDH computations when libsecp256k1 is compiled with GCC 13.1.
#### Fixed
- Fixed an old bug that permitted compilers to potentially output bad assembly code on x86_64. In theory, it could lead to a crash or a read of unrelated memory, but this has never been observed on any compilers so far.
#### Changed
- Various improvements and changes to CMake builds. CMake builds remain experimental.
- Made API versioning consistent with GNU Autotools builds.
- Switched to `BUILD_SHARED_LIBS` variable for controlling whether to build a static or a shared library.
- Added `SECP256K1_INSTALL` variable for the controlling whether to install the build artefacts.
- Renamed asm build option `arm` to `arm32`. Use `--with-asm=arm32` instead of `--with-asm=arm` (GNU Autotools), and `-DSECP256K1_ASM=arm32` instead of `-DSECP256K1_ASM=arm` (CMake).
#### ABI Compatibility
The ABI is compatible with versions 0.3.0 and 0.3.1.
## [0.3.1] - 2023-04-10
We strongly recommend updating to 0.3.1 if you use or plan to use Clang >=14 to compile libsecp256k1, e.g., Xcode >=14 on macOS has Clang >=14. When in doubt, check the Clang version using `clang -v`.
#### Security
- Fix "constant-timeness" issue with Clang >=14 that could leave applications using libsecp256k1 vulnerable to a timing side-channel attack. The fix avoids secret-dependent control flow and secret-dependent memory accesses in conditional moves of memory objects when libsecp256k1 is compiled with Clang >=14.
#### Added
- Added tests against [Project Wycheproof's](https://github.com/google/wycheproof/) set of ECDSA test vectors (Bitcoin "low-S" variant), a fixed set of test cases designed to trigger various edge cases.
#### Changed
- Increased minimum required CMake version to 3.13. CMake builds remain experimental.
#### ABI Compatibility
The ABI is compatible with version 0.3.0.
## [0.3.0] - 2023-03-08
#### Added
- Added experimental support for CMake builds. Traditional GNU Autotools builds (`./configure` and `make`) remain fully supported.
- Usage examples: Added a recommended method for securely clearing sensitive data, e.g., secret keys, from memory.
- Tests: Added a new test binary `noverify_tests`. This binary runs the tests without some additional checks present in the ordinary `tests` binary and is thereby closer to production binaries. The `noverify_tests` binary is automatically run as part of the `make check` target.
#### Fixed
- Fixed declarations of API variables for MSVC (`__declspec(dllimport)`). This fixes MSVC builds of programs which link against a libsecp256k1 DLL dynamically and use API variables (and not only API functions). Unfortunately, the MSVC linker now will emit warning `LNK4217` when trying to link against libsecp256k1 statically. Pass `/ignore:4217` to the linker to suppress this warning.
#### Changed
- Forbade cloning or destroying `secp256k1_context_static`. Create a new context instead of cloning the static context. (If this change breaks your code, your code is probably wrong.)
- Forbade randomizing (copies of) `secp256k1_context_static`. Randomizing a copy of `secp256k1_context_static` did not have any effect and did not provide defense-in-depth protection against side-channel attacks. Create a new context if you want to benefit from randomization.
#### Removed
- Removed the configuration header `src/libsecp256k1-config.h`. We recommend passing flags to `./configure` or `cmake` to set configuration options (see `./configure --help` or `cmake -LH`). If you cannot or do not want to use one of the supported build systems, pass configuration flags such as `-DSECP256K1_ENABLE_MODULE_SCHNORRSIG` manually to the compiler (see the file `configure.ac` for supported flags).
#### ABI Compatibility
Due to changes in the API regarding `secp256k1_context_static` described above, the ABI is *not* compatible with previous versions.
## [0.2.0] - 2022-12-12
#### Added
- Added usage examples for common use cases in a new `examples/` directory.
- Added `secp256k1_selftest`, to be used in conjunction with `secp256k1_context_static`.
- Added support for 128-bit wide multiplication on MSVC for x86_64 and arm64, giving roughly a 20% speedup on those platforms.
#### Changed
- Enabled modules `schnorrsig`, `extrakeys` and `ecdh` by default in `./configure`.
- The `secp256k1_nonce_function_rfc6979` nonce function, used by default by `secp256k1_ecdsa_sign`, now reduces the message hash modulo the group order to match the specification. This only affects improper use of ECDSA signing API.
#### Deprecated
- Deprecated context flags `SECP256K1_CONTEXT_VERIFY` and `SECP256K1_CONTEXT_SIGN`. Use `SECP256K1_CONTEXT_NONE` instead.
- Renamed `secp256k1_context_no_precomp` to `secp256k1_context_static`.
- Module `schnorrsig`: renamed `secp256k1_schnorrsig_sign` to `secp256k1_schnorrsig_sign32`.
#### ABI Compatibility
Since this is the first release, we do not compare application binary interfaces.
However, there are earlier unreleased versions of libsecp256k1 that are *not* ABI compatible with this version.
## [0.1.0] - 2013-03-05 to 2021-12-25
This version was in fact never released.
The number was given by the build system since the introduction of autotools in Jan 2014 (ea0fe5a5bf0c04f9cc955b2966b614f5f378c6f6).
Therefore, this version number does not uniquely identify a set of source files.
[unreleased]: https://github.com/bitcoin-core/secp256k1/compare/v0.6.0...HEAD
[0.6.0]: https://github.com/bitcoin-core/secp256k1/compare/v0.5.1...v0.6.0
[0.5.1]: https://github.com/bitcoin-core/secp256k1/compare/v0.5.0...v0.5.1
[0.5.0]: https://github.com/bitcoin-core/secp256k1/compare/v0.4.1...v0.5.0
[0.4.1]: https://github.com/bitcoin-core/secp256k1/compare/v0.4.0...v0.4.1
[0.4.0]: https://github.com/bitcoin-core/secp256k1/compare/v0.3.2...v0.4.0
[0.3.2]: https://github.com/bitcoin-core/secp256k1/compare/v0.3.1...v0.3.2
[0.3.1]: https://github.com/bitcoin-core/secp256k1/compare/v0.3.0...v0.3.1
[0.3.0]: https://github.com/bitcoin-core/secp256k1/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/bitcoin-core/secp256k1/compare/423b6d19d373f1224fd671a982584d7e7900bc93..v0.2.0
[0.1.0]: https://github.com/bitcoin-core/secp256k1/commit/423b6d19d373f1224fd671a982584d7e7900bc93

Some files were not shown because too many files have changed in this diff Show more