mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-27 15:16:43 +00:00
Merge remote-tracking branch 'origin/master' into eof-opcodes
This commit is contained in:
commit
0e115ad457
321 changed files with 66592 additions and 10506 deletions
2
.github/workflows/go.yml
vendored
2
.github/workflows/go.yml
vendored
|
|
@ -29,7 +29,7 @@ jobs:
|
||||||
- name: Run linters
|
- name: Run linters
|
||||||
run: |
|
run: |
|
||||||
go run build/ci.go lint
|
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 check_baddeps
|
||||||
|
|
||||||
build:
|
build:
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ linters:
|
||||||
- gocheckcompilerdirectives
|
- gocheckcompilerdirectives
|
||||||
- reassign
|
- reassign
|
||||||
- mirror
|
- mirror
|
||||||
- tenv
|
- usetesting
|
||||||
### linters we tried and will not be using:
|
### linters we tried and will not be using:
|
||||||
###
|
###
|
||||||
# - structcheck # lots of false positives
|
# - structcheck # lots of false positives
|
||||||
|
|
|
||||||
|
|
@ -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).
|
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
|
them using your favourite package manager. Once the dependencies are installed, run
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ for:
|
||||||
- image: Ubuntu
|
- image: Ubuntu
|
||||||
build_script:
|
build_script:
|
||||||
- go run build/ci.go lint
|
- 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_generate
|
||||||
- go run build/ci.go check_baddeps
|
- go run build/ci.go check_baddeps
|
||||||
- go run build/ci.go install -dlgo
|
- go run build/ci.go install -dlgo
|
||||||
|
|
|
||||||
|
|
@ -156,8 +156,9 @@ func (s *beaconBlockSync) updateEventFeed() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.chainHeadFeed.Send(types.ChainHeadEvent{
|
s.chainHeadFeed.Send(types.ChainHeadEvent{
|
||||||
BeaconHead: optimistic.Attested.Header,
|
BeaconHead: optimistic.Attested.Header,
|
||||||
Block: execBlock,
|
Block: execBlock,
|
||||||
Finalized: finalizedHash,
|
ExecRequests: headBlock.ExecutionRequestsList(),
|
||||||
|
Finalized: finalizedHash,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,11 @@ func (ec *engineClient) callNewPayload(fork string, event types.ChainHeadEvent)
|
||||||
params = []any{execData}
|
params = []any{execData}
|
||||||
)
|
)
|
||||||
switch fork {
|
switch fork {
|
||||||
|
case "electra":
|
||||||
|
method = "engine_newPayloadV4"
|
||||||
|
parentBeaconRoot := event.BeaconHead.ParentRoot
|
||||||
|
blobHashes := collectBlobHashes(event.Block)
|
||||||
|
params = append(params, blobHashes, parentBeaconRoot, event.ExecRequests)
|
||||||
case "deneb":
|
case "deneb":
|
||||||
method = "engine_newPayloadV3"
|
method = "engine_newPayloadV3"
|
||||||
parentBeaconRoot := event.BeaconHead.ParentRoot
|
parentBeaconRoot := event.BeaconHead.ParentRoot
|
||||||
|
|
@ -135,7 +140,7 @@ func (ec *engineClient) callForkchoiceUpdated(fork string, event types.ChainHead
|
||||||
|
|
||||||
var method string
|
var method string
|
||||||
switch fork {
|
switch fork {
|
||||||
case "deneb":
|
case "deneb", "electra":
|
||||||
method = "engine_forkchoiceUpdatedV3"
|
method = "engine_forkchoiceUpdatedV3"
|
||||||
case "capella":
|
case "capella":
|
||||||
method = "engine_forkchoiceUpdatedV2"
|
method = "engine_forkchoiceUpdatedV2"
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,6 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
type CommitteeUpdate struct {
|
type CommitteeUpdate struct {
|
||||||
Version string
|
|
||||||
Update types.LightClientUpdate
|
Update types.LightClientUpdate
|
||||||
NextSyncCommittee types.SerializedSyncCommittee
|
NextSyncCommittee types.SerializedSyncCommittee
|
||||||
}
|
}
|
||||||
|
|
@ -81,9 +80,9 @@ func (u *CommitteeUpdate) UnmarshalJSON(input []byte) error {
|
||||||
if err := json.Unmarshal(input, &dec); err != nil {
|
if err := json.Unmarshal(input, &dec); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
u.Version = dec.Version
|
|
||||||
u.NextSyncCommittee = dec.Data.NextSyncCommittee
|
u.NextSyncCommittee = dec.Data.NextSyncCommittee
|
||||||
u.Update = types.LightClientUpdate{
|
u.Update = types.LightClientUpdate{
|
||||||
|
Version: dec.Version,
|
||||||
AttestedHeader: types.SignedHeader{
|
AttestedHeader: types.SignedHeader{
|
||||||
Header: dec.Data.Header.Beacon,
|
Header: dec.Data.Header.Beacon,
|
||||||
Signature: dec.Data.SyncAggregate,
|
Signature: dec.Data.SyncAggregate,
|
||||||
|
|
@ -206,7 +205,7 @@ func (api *BeaconLightApi) GetOptimisticUpdate() (types.OptimisticUpdate, error)
|
||||||
|
|
||||||
func decodeOptimisticUpdate(enc []byte) (types.OptimisticUpdate, error) {
|
func decodeOptimisticUpdate(enc []byte) (types.OptimisticUpdate, error) {
|
||||||
var data struct {
|
var data struct {
|
||||||
Version string
|
Version string `json:"version"`
|
||||||
Data struct {
|
Data struct {
|
||||||
Attested jsonHeaderWithExecProof `json:"attested_header"`
|
Attested jsonHeaderWithExecProof `json:"attested_header"`
|
||||||
Aggregate types.SyncAggregate `json:"sync_aggregate"`
|
Aggregate types.SyncAggregate `json:"sync_aggregate"`
|
||||||
|
|
@ -259,7 +258,7 @@ func (api *BeaconLightApi) GetFinalityUpdate() (types.FinalityUpdate, error) {
|
||||||
|
|
||||||
func decodeFinalityUpdate(enc []byte) (types.FinalityUpdate, error) {
|
func decodeFinalityUpdate(enc []byte) (types.FinalityUpdate, error) {
|
||||||
var data struct {
|
var data struct {
|
||||||
Version string
|
Version string `json:"version"`
|
||||||
Data struct {
|
Data struct {
|
||||||
Attested jsonHeaderWithExecProof `json:"attested_header"`
|
Attested jsonHeaderWithExecProof `json:"attested_header"`
|
||||||
Finalized jsonHeaderWithExecProof `json:"finalized_header"`
|
Finalized jsonHeaderWithExecProof `json:"finalized_header"`
|
||||||
|
|
@ -289,6 +288,7 @@ func decodeFinalityUpdate(enc []byte) (types.FinalityUpdate, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return types.FinalityUpdate{
|
return types.FinalityUpdate{
|
||||||
|
Version: data.Version,
|
||||||
Attested: types.HeaderWithExecProof{
|
Attested: types.HeaderWithExecProof{
|
||||||
Header: data.Data.Attested.Beacon,
|
Header: data.Data.Attested.Beacon,
|
||||||
PayloadHeader: attestedExecHeader,
|
PayloadHeader: attestedExecHeader,
|
||||||
|
|
@ -355,7 +355,8 @@ func (api *BeaconLightApi) GetCheckpointData(checkpointHash common.Hash) (*types
|
||||||
// See data structure definition here:
|
// See data structure definition here:
|
||||||
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientbootstrap
|
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientbootstrap
|
||||||
type bootstrapData struct {
|
type bootstrapData struct {
|
||||||
Data struct {
|
Version string `json:"version"`
|
||||||
|
Data struct {
|
||||||
Header jsonBeaconHeader `json:"header"`
|
Header jsonBeaconHeader `json:"header"`
|
||||||
Committee *types.SerializedSyncCommittee `json:"current_sync_committee"`
|
Committee *types.SerializedSyncCommittee `json:"current_sync_committee"`
|
||||||
CommitteeBranch merkle.Values `json:"current_sync_committee_branch"`
|
CommitteeBranch merkle.Values `json:"current_sync_committee_branch"`
|
||||||
|
|
@ -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)
|
return nil, fmt.Errorf("invalid checkpoint block header, have %v want %v", header.Hash(), checkpointHash)
|
||||||
}
|
}
|
||||||
checkpoint := &types.BootstrapData{
|
checkpoint := &types.BootstrapData{
|
||||||
|
Version: data.Version,
|
||||||
Header: header,
|
Header: header,
|
||||||
CommitteeBranch: data.Data.CommitteeBranch,
|
CommitteeBranch: data.Data.CommitteeBranch,
|
||||||
CommitteeRoot: data.Data.Committee.Root(),
|
CommitteeRoot: data.Data.Committee.Root(),
|
||||||
|
|
@ -395,7 +397,7 @@ func (api *BeaconLightApi) GetBeaconBlock(blockRoot common.Hash) (*types.BeaconB
|
||||||
}
|
}
|
||||||
|
|
||||||
var beaconBlockMessage struct {
|
var beaconBlockMessage struct {
|
||||||
Version string
|
Version string `json:"version"`
|
||||||
Data struct {
|
Data struct {
|
||||||
Message json.RawMessage `json:"message"`
|
Message json.RawMessage `json:"message"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,10 +39,10 @@ func GenerateTestUpdate(config *params.ChainConfig, period uint64, committee, ne
|
||||||
var attestedHeader types.Header
|
var attestedHeader types.Header
|
||||||
if finalizedHeader {
|
if finalizedHeader {
|
||||||
update.FinalizedHeader = new(types.Header)
|
update.FinalizedHeader = new(types.Header)
|
||||||
*update.FinalizedHeader, update.NextSyncCommitteeBranch = makeTestHeaderWithMerkleProof(types.SyncPeriodStart(period)+100, params.StateIndexNextSyncCommittee, merkle.Value(update.NextSyncCommitteeRoot))
|
*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()))
|
attestedHeader, update.FinalityBranch = makeTestHeaderWithMerkleProof(types.SyncPeriodStart(period)+200, params.StateIndexFinalBlock(""), merkle.Value(update.FinalizedHeader.Hash()))
|
||||||
} else {
|
} 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)
|
update.AttestedHeader = GenerateTestSignedHeader(attestedHeader, config, committee, attestedHeader.Slot+1, signerCount)
|
||||||
return update
|
return update
|
||||||
|
|
@ -63,7 +63,7 @@ func GenerateTestSignedHeader(header types.Header, config *params.ChainConfig, c
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateTestCheckpoint(period uint64, committee *types.SerializedSyncCommittee) *types.BootstrapData {
|
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{
|
return &types.BootstrapData{
|
||||||
Header: header,
|
Header: header,
|
||||||
Committee: committee,
|
Committee: committee,
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,8 @@ var (
|
||||||
AddFork("ALTAIR", 50, []byte{144, 0, 0, 112}).
|
AddFork("ALTAIR", 50, []byte{144, 0, 0, 112}).
|
||||||
AddFork("BELLATRIX", 100, []byte{144, 0, 0, 113}).
|
AddFork("BELLATRIX", 100, []byte{144, 0, 0, 113}).
|
||||||
AddFork("CAPELLA", 56832, []byte{144, 0, 0, 114}).
|
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{
|
HoleskyLightConfig = (&ChainConfig{
|
||||||
GenesisValidatorsRoot: common.HexToHash("0x9143aa7c615a7f7115e2b6aac319c03529df8242ae705fba9df39b79c59fa8b1"),
|
GenesisValidatorsRoot: common.HexToHash("0x9143aa7c615a7f7115e2b6aac319c03529df8242ae705fba9df39b79c59fa8b1"),
|
||||||
|
|
@ -52,5 +53,6 @@ var (
|
||||||
AddFork("ALTAIR", 0, []byte{2, 1, 112, 0}).
|
AddFork("ALTAIR", 0, []byte{2, 1, 112, 0}).
|
||||||
AddFork("BELLATRIX", 0, []byte{3, 1, 112, 0}).
|
AddFork("BELLATRIX", 0, []byte{3, 1, 112, 0}).
|
||||||
AddFork("CAPELLA", 256, []byte{4, 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})
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -29,18 +29,46 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
StateIndexGenesisTime = 32
|
StateIndexGenesisTime = 32
|
||||||
StateIndexGenesisValidators = 33
|
StateIndexGenesisValidators = 33
|
||||||
StateIndexForkVersion = 141
|
StateIndexForkVersion = 141
|
||||||
StateIndexLatestHeader = 36
|
StateIndexLatestHeader = 36
|
||||||
StateIndexBlockRoots = 37
|
StateIndexBlockRoots = 37
|
||||||
StateIndexStateRoots = 38
|
StateIndexStateRoots = 38
|
||||||
StateIndexHistoricRoots = 39
|
StateIndexHistoricRoots = 39
|
||||||
StateIndexFinalBlock = 105
|
StateIndexFinalBlockOld = 105
|
||||||
StateIndexSyncCommittee = 54
|
StateIndexFinalBlockElectra = 169
|
||||||
StateIndexNextSyncCommittee = 55
|
StateIndexSyncCommitteeOld = 54
|
||||||
StateIndexExecPayload = 56
|
StateIndexSyncCommitteeElectra = 86
|
||||||
StateIndexExecHead = 908
|
StateIndexNextSyncCommitteeOld = 55
|
||||||
|
StateIndexNextSyncCommitteeElectra = 87
|
||||||
|
StateIndexExecPayload = 56
|
||||||
|
StateIndexExecHead = 908
|
||||||
|
|
||||||
BodyIndexExecPayload = 25
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,16 +17,21 @@
|
||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/protolambda/zrnt/eth2/beacon/capella"
|
|
||||||
zrntcommon "github.com/protolambda/zrnt/eth2/beacon/common"
|
zrntcommon "github.com/protolambda/zrnt/eth2/beacon/common"
|
||||||
"github.com/protolambda/zrnt/eth2/beacon/deneb"
|
|
||||||
"github.com/protolambda/zrnt/eth2/configs"
|
"github.com/protolambda/zrnt/eth2/configs"
|
||||||
|
"github.com/protolambda/ztyp/codec"
|
||||||
"github.com/protolambda/ztyp/tree"
|
"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 {
|
type blockObject interface {
|
||||||
|
|
@ -43,10 +48,12 @@ type BeaconBlock struct {
|
||||||
func BlockFromJSON(forkName string, data []byte) (*BeaconBlock, error) {
|
func BlockFromJSON(forkName string, data []byte) (*BeaconBlock, error) {
|
||||||
var obj blockObject
|
var obj blockObject
|
||||||
switch forkName {
|
switch forkName {
|
||||||
case "deneb":
|
|
||||||
obj = new(deneb.BeaconBlock)
|
|
||||||
case "capella":
|
case "capella":
|
||||||
obj = new(capella.BeaconBlock)
|
obj = new(capella.BeaconBlock)
|
||||||
|
case "deneb":
|
||||||
|
obj = new(deneb.BeaconBlock)
|
||||||
|
case "electra":
|
||||||
|
obj = new(electra.BeaconBlock)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported fork: %s", forkName)
|
return nil, fmt.Errorf("unsupported fork: %s", forkName)
|
||||||
}
|
}
|
||||||
|
|
@ -63,6 +70,8 @@ func NewBeaconBlock(obj blockObject) *BeaconBlock {
|
||||||
return &BeaconBlock{obj}
|
return &BeaconBlock{obj}
|
||||||
case *deneb.BeaconBlock:
|
case *deneb.BeaconBlock:
|
||||||
return &BeaconBlock{obj}
|
return &BeaconBlock{obj}
|
||||||
|
case *electra.BeaconBlock:
|
||||||
|
return &BeaconBlock{obj}
|
||||||
default:
|
default:
|
||||||
panic(fmt.Errorf("unsupported block type %T", obj))
|
panic(fmt.Errorf("unsupported block type %T", obj))
|
||||||
}
|
}
|
||||||
|
|
@ -75,6 +84,8 @@ func (b *BeaconBlock) Slot() uint64 {
|
||||||
return uint64(obj.Slot)
|
return uint64(obj.Slot)
|
||||||
case *deneb.BeaconBlock:
|
case *deneb.BeaconBlock:
|
||||||
return uint64(obj.Slot)
|
return uint64(obj.Slot)
|
||||||
|
case *electra.BeaconBlock:
|
||||||
|
return uint64(obj.Slot)
|
||||||
default:
|
default:
|
||||||
panic(fmt.Errorf("unsupported block type %T", b.blockObj))
|
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) {
|
func (b *BeaconBlock) ExecutionPayload() (*types.Block, error) {
|
||||||
switch obj := b.blockObj.(type) {
|
switch obj := b.blockObj.(type) {
|
||||||
case *capella.BeaconBlock:
|
case *capella.BeaconBlock:
|
||||||
return convertPayload(&obj.Body.ExecutionPayload, &obj.ParentRoot)
|
return convertPayload(&obj.Body.ExecutionPayload, &obj.ParentRoot, nil)
|
||||||
case *deneb.BeaconBlock:
|
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:
|
default:
|
||||||
panic(fmt.Errorf("unsupported block type %T", b.blockObj))
|
panic(fmt.Errorf("unsupported block type %T", b.blockObj))
|
||||||
}
|
}
|
||||||
|
|
@ -99,6 +113,8 @@ func (b *BeaconBlock) Header() Header {
|
||||||
return headerFromZRNT(obj.Header(configs.Mainnet))
|
return headerFromZRNT(obj.Header(configs.Mainnet))
|
||||||
case *deneb.BeaconBlock:
|
case *deneb.BeaconBlock:
|
||||||
return headerFromZRNT(obj.Header(configs.Mainnet))
|
return headerFromZRNT(obj.Header(configs.Mainnet))
|
||||||
|
case *electra.BeaconBlock:
|
||||||
|
return headerFromZRNT(obj.Header(configs.Mainnet))
|
||||||
default:
|
default:
|
||||||
panic(fmt.Errorf("unsupported block type %T", b.blockObj))
|
panic(fmt.Errorf("unsupported block type %T", b.blockObj))
|
||||||
}
|
}
|
||||||
|
|
@ -108,3 +124,38 @@ func (b *BeaconBlock) Header() Header {
|
||||||
func (b *BeaconBlock) Root() common.Hash {
|
func (b *BeaconBlock) Root() common.Hash {
|
||||||
return common.Hash(b.blockObj.HashTreeRoot(configs.Mainnet, tree.GetHashFn()))
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,27 @@ func TestBlockFromJSON(t *testing.T) {
|
||||||
wantBlockHash common.Hash
|
wantBlockHash common.Hash
|
||||||
}
|
}
|
||||||
tests := []blocktest{
|
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",
|
file: "block_deneb.json",
|
||||||
version: "deneb",
|
version: "deneb",
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,12 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/beacon/merkle"
|
"github.com/ethereum/go-ethereum/beacon/merkle"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/protolambda/zrnt/eth2/beacon/capella"
|
|
||||||
zrntcommon "github.com/protolambda/zrnt/eth2/beacon/common"
|
zrntcommon "github.com/protolambda/zrnt/eth2/beacon/common"
|
||||||
"github.com/protolambda/zrnt/eth2/beacon/deneb"
|
|
||||||
"github.com/protolambda/ztyp/tree"
|
"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 {
|
type headerObject interface {
|
||||||
|
|
@ -43,7 +45,7 @@ func ExecutionHeaderFromJSON(forkName string, data []byte) (*ExecutionHeader, er
|
||||||
switch forkName {
|
switch forkName {
|
||||||
case "capella":
|
case "capella":
|
||||||
obj = new(capella.ExecutionPayloadHeader)
|
obj = new(capella.ExecutionPayloadHeader)
|
||||||
case "deneb":
|
case "deneb", "electra": // note: the payload type was not changed in electra
|
||||||
obj = new(deneb.ExecutionPayloadHeader)
|
obj = new(deneb.ExecutionPayloadHeader)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported fork: %s", forkName)
|
return nil, fmt.Errorf("unsupported fork: %s", forkName)
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ type payloadType interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// convertPayload converts a beacon chain execution payload to types.Block.
|
// 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 (
|
var (
|
||||||
header types.Header
|
header types.Header
|
||||||
transactions []*types.Transaction
|
transactions []*types.Transaction
|
||||||
|
|
@ -62,7 +62,10 @@ func convertPayload[T payloadType](payload T, parentRoot *zrntcommon.Root) (*typ
|
||||||
default:
|
default:
|
||||||
panic("unsupported block type")
|
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})
|
block := types.NewBlockWithHeader(&header).WithBody(types.Body{Transactions: transactions, Withdrawals: withdrawals})
|
||||||
if hash := block.Hash(); hash != expectedHash {
|
if hash := block.Hash(); hash != expectedHash {
|
||||||
return nil, fmt.Errorf("sanity check failed, payload hash does not match (expected %x, got %x)", expectedHash, hash)
|
return nil, fmt.Errorf("sanity check failed, payload hash does not match (expected %x, got %x)", expectedHash, hash)
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ type HeadInfo struct {
|
||||||
// together with a proof through a beacon header and corresponding state.
|
// together with a proof through a beacon header and corresponding state.
|
||||||
// Note: BootstrapData is fetched from a server based on a known checkpoint hash.
|
// Note: BootstrapData is fetched from a server based on a known checkpoint hash.
|
||||||
type BootstrapData struct {
|
type BootstrapData struct {
|
||||||
|
Version string
|
||||||
Header Header
|
Header Header
|
||||||
CommitteeRoot common.Hash
|
CommitteeRoot common.Hash
|
||||||
Committee *SerializedSyncCommittee `rlp:"-"`
|
Committee *SerializedSyncCommittee `rlp:"-"`
|
||||||
|
|
@ -47,7 +48,7 @@ func (c *BootstrapData) Validate() error {
|
||||||
if c.CommitteeRoot != c.Committee.Root() {
|
if c.CommitteeRoot != c.Committee.Root() {
|
||||||
return errors.New("wrong 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
|
// 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:
|
// See data structure definition here:
|
||||||
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientupdate
|
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientupdate
|
||||||
type LightClientUpdate struct {
|
type LightClientUpdate struct {
|
||||||
|
Version string
|
||||||
AttestedHeader SignedHeader // Arbitrary header out of the period signed by the sync committee
|
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
|
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
|
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 {
|
if update.FinalizedHeader.SyncPeriod() != period {
|
||||||
return errors.New("finalized header is from different 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)
|
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 fmt.Errorf("invalid next sync committee proof: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -194,6 +196,7 @@ func (u *OptimisticUpdate) Validate() error {
|
||||||
// See data structure definition here:
|
// See data structure definition here:
|
||||||
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientfinalityupdate
|
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientfinalityupdate
|
||||||
type FinalityUpdate struct {
|
type FinalityUpdate struct {
|
||||||
|
Version string
|
||||||
Attested, Finalized HeaderWithExecProof
|
Attested, Finalized HeaderWithExecProof
|
||||||
FinalityBranch merkle.Values
|
FinalityBranch merkle.Values
|
||||||
// Sync committee BLS signature aggregate
|
// Sync committee BLS signature aggregate
|
||||||
|
|
@ -223,14 +226,15 @@ func (u *FinalityUpdate) Validate() error {
|
||||||
if err := u.Finalized.Validate(); err != nil {
|
if err := u.Finalized.Validate(); err != nil {
|
||||||
return err
|
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
|
// ChainHeadEvent returns an authenticated execution payload associated with the
|
||||||
// latest accepted head of the beacon chain, along with the hash of the latest
|
// latest accepted head of the beacon chain, along with the hash of the latest
|
||||||
// finalized execution block.
|
// finalized execution block.
|
||||||
type ChainHeadEvent struct {
|
type ChainHeadEvent struct {
|
||||||
BeaconHead Header
|
BeaconHead Header
|
||||||
Block *ctypes.Block
|
Block *ctypes.Block
|
||||||
Finalized common.Hash
|
ExecRequests [][]byte // execution layer requests (added in Electra)
|
||||||
|
Finalized common.Hash // latest finalized block hash
|
||||||
}
|
}
|
||||||
|
|
|
||||||
194
beacon/types/testdata/block_electra_consolidations.json
vendored
Normal file
194
beacon/types/testdata/block_electra_consolidations.json
vendored
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
305
beacon/types/testdata/block_electra_deposits.json
vendored
Normal file
305
beacon/types/testdata/block_electra_deposits.json
vendored
Normal 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": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
232
beacon/types/testdata/block_electra_withdrawals.json
vendored
Normal file
232
beacon/types/testdata/block_electra_withdrawals.json
vendored
Normal 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": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,126 +2,130 @@
|
||||||
|
|
||||||
# version:spec-tests pectra-devnet-6@v1.0.0
|
# 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
|
||||||
# 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
|
b69211752a3029083c020dc635fe12156ca1a6725a08559da540a0337586a77e fixtures_pectra-devnet-6.tar.gz
|
||||||
|
|
||||||
# version:golang 1.24.0
|
# version:golang 1.24.1
|
||||||
# https://go.dev/dl/
|
# https://go.dev/dl/
|
||||||
d14120614acb29d12bcab72bd689f257eb4be9e0b6f88a8fb7e41ac65f8556e5 go1.24.0.src.tar.gz
|
8244ebf46c65607db10222b5806aeb31c1fcf8979c1b6b12f60c677e9a3c0656 go1.24.1.src.tar.gz
|
||||||
5d04588154d5923bd8e26b76111806340ec55c41af1b05623ea744fcb3d6bc22 go1.24.0.aix-ppc64.tar.gz
|
8d627dc163a4bffa2b1887112ad6194af175dce108d606ed1714a089fb806033 go1.24.1.aix-ppc64.tar.gz
|
||||||
7af054e5088b68c24b3d6e135e5ca8d91bbd5a05cb7f7f0187367b3e6e9e05ee go1.24.0.darwin-amd64.tar.gz
|
addbfce2056744962e2d7436313ab93486660cf7a2e066d171b9d6f2da7c7abe go1.24.1.darwin-amd64.tar.gz
|
||||||
dee0ea64411a00b47ded586d5a8e30cfe3acf51564aa1bb24e039a6dca807a29 go1.24.0.darwin-amd64.pkg
|
58d529334561cff11087cd4ab18fe0b46d8d5aad88f45c02b9645f847e014512 go1.24.1.darwin-amd64.pkg
|
||||||
fd9cfb5dd6c75a347cfc641a253f0db1cebaca16b0dd37965351c6184ba595e4 go1.24.0.darwin-arm64.tar.gz
|
295581b5619acc92f5106e5bcb05c51869337eb19742fdfa6c8346c18e78ff88 go1.24.1.darwin-arm64.tar.gz
|
||||||
b19eb6b7ae87f8371c3e7a84d129db67779a2883d2fffa6bb90412b0167df133 go1.24.0.darwin-arm64.pkg
|
78b0fc8ddc344eb499f1a952c687cb84cbd28ba2b739cfa0d4eb042f07e44e82 go1.24.1.darwin-arm64.pkg
|
||||||
d0dc34ad86aea746abe245994c68a9e1ad8f46ba8c4af901cd5861a4dd4c21df go1.24.0.dragonfly-amd64.tar.gz
|
e70053f56f7eb93806d80cbd5726f78509a0a467602f7bea0e2c4ee8ed7c3968 go1.24.1.dragonfly-amd64.tar.gz
|
||||||
4ee02b1f3812aff4da79c79464ee4038ca61ad74b3a9619850f30435f81c2536 go1.24.0.freebsd-386.tar.gz
|
3595e2674ed8fe72e604ca59c964d3e5277aafb08475c2b1aaca2d2fd69c24fc go1.24.1.freebsd-386.tar.gz
|
||||||
838191001f9324da904dece35a586a3156d548687db87ac9461aa3d38fc88b09 go1.24.0.freebsd-amd64.tar.gz
|
47d7de8bb64d5c3ee7b6723aa62d5ecb11e3568ef2249bbe1d4bbd432d37c00c go1.24.1.freebsd-amd64.tar.gz
|
||||||
ce6ad4e84a40a8a1d848b7e31b0cddfd1cee8f7959e7dc358a8fa8b5566ea718 go1.24.0.freebsd-arm.tar.gz
|
04eec3bcfaa14c1370cdf98e8307fac7e4853496c3045afb9c3124a29cbca205 go1.24.1.freebsd-arm.tar.gz
|
||||||
511f7b0cac4c4ed1066d324072ce223b906ad6b2a85f2e1c5d260eb7d08b5901 go1.24.0.freebsd-arm64.tar.gz
|
51aa70146e40cfdc20927424083dc86e6223f85dc08089913a1651973b55665b go1.24.1.freebsd-arm64.tar.gz
|
||||||
a1e4072630dc589a2975ef51317b52c7d8599bf6f389fc59033c01e0a0fa705a go1.24.0.freebsd-riscv64.tar.gz
|
3c131d8e3fc285a1340f87813153e24226d3ddbd6e54f3facbd6e4c46a84655e go1.24.1.freebsd-riscv64.tar.gz
|
||||||
7593e9dcee9f07c3df6d099f7d259f5734a6c0dccc5f28962f18e7f501c9bb21 go1.24.0.illumos-amd64.tar.gz
|
201d09da737ba39d5367f87d4e8b31edaeeb3dc9b9c407cb8cfb40f90c5a727a go1.24.1.illumos-amd64.tar.gz
|
||||||
90521453a59c6ce20364d2dc7c38532949b033b602ba12d782caeb90af1b0624 go1.24.0.linux-386.tar.gz
|
8c530ecedbc17e42ce10177bea07ccc96a3e77c792ea1ea72173a9675d16ffa5 go1.24.1.linux-386.tar.gz
|
||||||
dea9ca38a0b852a74e81c26134671af7c0fbe65d81b0dc1c5bfe22cf7d4c8858 go1.24.0.linux-amd64.tar.gz
|
cb2396bae64183cdccf81a9a6df0aea3bce9511fc21469fb89a0c00470088073 go1.24.1.linux-amd64.tar.gz
|
||||||
c3fa6d16ffa261091a5617145553c71d21435ce547e44cc6dfb7470865527cc7 go1.24.0.linux-arm64.tar.gz
|
8df5750ffc0281017fb6070fba450f5d22b600a02081dceef47966ffaf36a3af go1.24.1.linux-arm64.tar.gz
|
||||||
695dc54fa14cd3124fa6900d7b5ae39eeac23f7a4ecea81656070160fac2c54a go1.24.0.linux-armv6l.tar.gz
|
6d95f8d7884bfe2364644c837f080f2b585903d0b771eb5b06044e226a4f120a go1.24.1.linux-armv6l.tar.gz
|
||||||
a201e4c9b7e6d29ed64c43296ed88e81a66f82f2093ce45b766d2c526941396f go1.24.0.linux-loong64.tar.gz
|
19304a4a56e46d04604547d2d83235dc4f9b192c79832560ce337d26cc7b835a go1.24.1.linux-loong64.tar.gz
|
||||||
f3ac039aae78ad0bfb08106406c2e62eaf763dd82ebaf0ecd539adadd1d729a6 go1.24.0.linux-mips.tar.gz
|
6347be77fa5359c12a5308c8ab87147c1fc4717b0c216493d1706c3b9fcde22d go1.24.1.linux-mips.tar.gz
|
||||||
f2e6456d45e024831b1da8d88b1bb6392cca9500c1b00841f525d76c9e9553e0 go1.24.0.linux-mips64.tar.gz
|
1647df415f7030b82d4105670192aa7e8910e18563bb0d505192d72800cc2d21 go1.24.1.linux-mips64.tar.gz
|
||||||
b847893ff119389c939adc2b8516b6500204b7cb49d5e19b25e1c2091d2c74c6 go1.24.0.linux-mips64le.tar.gz
|
762da594e4ec0f9cf6defae6ef971f5f7901203ee6a2d979e317adec96657317 go1.24.1.linux-mips64le.tar.gz
|
||||||
bd4aed27d02746c237c3921e97029ac6b6fe687a67436b8f52ff1f698d330bd9 go1.24.0.linux-mipsle.tar.gz
|
9d8133c7b23a557399fab870b5cf464079c2b623a43b214a7567cf11c254a444 go1.24.1.linux-mipsle.tar.gz
|
||||||
007123c9b06c41729a4bb3f166f4df7196adf4e33c2d2ab0e7e990175f0ce1d4 go1.24.0.linux-ppc64.tar.gz
|
132f10999abbaccbada47fa85462db30c423955913b14d6c692de25f4636c766 go1.24.1.linux-ppc64.tar.gz
|
||||||
a871a43de7d26c91dd90cb6e0adacb214c9e35ee2188c617c91c08c017efe81a go1.24.0.linux-ppc64le.tar.gz
|
0fb522efcefabae6e37e69bdc444094e75bfe824ea6d4cc3cbc70c7ae1b16858 go1.24.1.linux-ppc64le.tar.gz
|
||||||
620dcf48c6297519aad6c81f8e344926dc0ab09a2a79f1e306964aece95a553d go1.24.0.linux-riscv64.tar.gz
|
eaef4323d5467ff97fb1979c8491764060dade19f02f3275a9313f9a0da3b9c0 go1.24.1.linux-riscv64.tar.gz
|
||||||
544d78b077c6b54bf78958c4a8285abec2d21f668fb007261c77418cd2edbb46 go1.24.0.linux-s390x.tar.gz
|
6c05e14d8f11094cb56a1c50f390b6b658bed8a7cbd8d1a57e926581b7eabfce go1.24.1.linux-s390x.tar.gz
|
||||||
8b143a7edefbaa2a0b0246c9df2df1bac9fbed909d8615a375c08da7744e697d go1.24.0.netbsd-386.tar.gz
|
5dbb287d343ea00d58a70b11629f32ee716dc50a6875c459ea2018df0f294cd8 go1.24.1.netbsd-386.tar.gz
|
||||||
67150a6dd7bdb9c4e88d77f46ee8c4dc99d5e71deca4912d8c2c85f7a16d0262 go1.24.0.netbsd-amd64.tar.gz
|
617aa3faee50ce84c343db0888e9a210c310a7203666b4ed620f31030c9fb32f go1.24.1.netbsd-amd64.tar.gz
|
||||||
446b2539f11218fd6f6f6e3dd90b20ae55a06afe129885eeb3df51eb344eb0f6 go1.24.0.netbsd-arm.tar.gz
|
59a928b7080c4a6ac985946274b7c65ce1cecc0b468ecd992d17b7c12fab9296 go1.24.1.netbsd-arm.tar.gz
|
||||||
370115b6ff7d30b29431223de348eb11ab65e3c92627532d97fd55f63f94e7a8 go1.24.0.netbsd-arm64.tar.gz
|
28daa8d0feb4aef2af60cefa3305bb9314de7e8a05cbca41ac548964cdfe89b7 go1.24.1.netbsd-arm64.tar.gz
|
||||||
cbda5f15f06ed9630f122a53542d9de13d149643633c74f1dcb45e79649b788a go1.24.0.openbsd-386.tar.gz
|
b7382b2f5d99813aeac14db482faa3bfbd47a68880b607fa2a7e669e26bab9cd go1.24.1.openbsd-386.tar.gz
|
||||||
926f601d0e655ab1e8d7f357fd82542e5cf206c38c4e2f9fccf0706987d38836 go1.24.0.openbsd-amd64.tar.gz
|
2513b6537c45deead5e641c7ce7502913e7d5e6f0b21c52542fb11f81578690f go1.24.1.openbsd-amd64.tar.gz
|
||||||
8a54892f8c933c541fff144a825d0fdc41bae14b0832aab703cb75eb4cb64f2c go1.24.0.openbsd-arm.tar.gz
|
853c1917d4fc7b144c55a02842aa48542d5cc798dde8db96dc0fdbc263200e04 go1.24.1.openbsd-arm.tar.gz
|
||||||
ef7fddcef0a22c7900c178b7687cf5aa25c2a9d46a3cc330b77a6de6e6c2396b go1.24.0.openbsd-arm64.tar.gz
|
6bc207b91e6f6ae3347fb54616a8fb2f5c11983713846a4cef111ff3f4f94d14 go1.24.1.openbsd-arm64.tar.gz
|
||||||
b3b5e2e2b53489ded2c2c21900ddcbbdb7991632bb5b42f05f125d71675e0b76 go1.24.0.openbsd-ppc64.tar.gz
|
4279260e2f2b94ee94e81470d13db7367f4393b061fee60985528fa0fa430df4 go1.24.1.openbsd-ppc64.tar.gz
|
||||||
fbcb1dbf1269b4079dc4fd0b15f3274b9d635f1a7e319c3fc1a907b03280348e go1.24.0.openbsd-riscv64.tar.gz
|
6fc4023a0a339ee0778522364a127d94c78e62122288d47d820dba703f81dc07 go1.24.1.openbsd-riscv64.tar.gz
|
||||||
33b4221e1c174a16e3f661deab6c60838ac4ae6cb869a4da1d1115773ceed88b go1.24.0.plan9-386.tar.gz
|
b5eb9fafd77146e7e1f748acfd95559580ecc8d2f15abf432a20f58c929c7cd2 go1.24.1.plan9-386.tar.gz
|
||||||
111a89014019cdbd69c2978de9b3e201f77e35183c8ab3606fba339d38f28549 go1.24.0.plan9-amd64.tar.gz
|
24dcad6361b141fc8cced15b092351e12a99d2e58d7013204a3013c50daf9fdd go1.24.1.plan9-amd64.tar.gz
|
||||||
8da3d3997049f40ebe0cd336a9bb9e4bfa4832df3c90a32f07383371d6d74849 go1.24.0.plan9-arm.tar.gz
|
a026ac3b55aa1e6fdc2aaab30207a117eafbe965ed81d3aa0676409f280ddc37 go1.24.1.plan9-arm.tar.gz
|
||||||
b6069da21dc95ccdbd047675b584e5480ffc3eba35f9e7c8b0e7b317aaf01e2c go1.24.0.solaris-amd64.tar.gz
|
8e4f6a77388dc6e5aa481efd5abdb3b9f5c9463bb82f4db074494e04e5c84992 go1.24.1.solaris-amd64.tar.gz
|
||||||
b53c28a4c2863ec50ab4a1dbebe818ef6177f86773b6f43475d40a5d9aa4ec9e go1.24.0.windows-386.zip
|
b799f4ab264eef12a014c759383ed934056608c483e0f73e34ea6caf9f1df5f9 go1.24.1.windows-386.zip
|
||||||
f07677013cd7861c5e16067b0a82144c23c4bf72c139c762e142440f4c926f61 go1.24.0.windows-386.msi
|
db128981033ac82a64688a123f631e61297b6b8f52ca913145e57caa8ce94cc3 go1.24.1.windows-386.msi
|
||||||
96b7280979205813759ee6947be7e3bb497da85c482711116c00522e3bb41ff1 go1.24.0.windows-amd64.zip
|
95666b551453209a2b8869d29d177285ff9573af10f085d961d7ae5440f645ce go1.24.1.windows-amd64.zip
|
||||||
4e78016d889431eb16aa0f87868cf52479b90059791c94a4ff45872d0573089e go1.24.0.windows-amd64.msi
|
5968e7adcf26e68a54f1cd41ad561275a670a8e2ca5263bc375b524638557dfb go1.24.1.windows-amd64.msi
|
||||||
53f73450fb66075d16be9f206e9177bd972b528168271918c4747903b5596c3d go1.24.0.windows-arm64.zip
|
e28c4e6d0b913955765b46157ab88ae59bb636acaa12d7bec959aa6900f1cebd go1.24.1.windows-arm64.zip
|
||||||
4f9780158fb7996dcbcbc7c7ef208f880b5e8c1f2792ba3ede0c75050c1bc23a go1.24.0.windows-arm64.msi
|
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/
|
||||||
# https://github.com/golangci/golangci-lint/releases/download/v1.64.4/
|
# https://github.com/golangci/golangci-lint/releases/download/v1.64.6/
|
||||||
f31ef6e20c62acc31eb7b851d611b4bd10256d1b1d8be38f0cceec300b5b60db golangci-lint-1.64.4-darwin-amd64.tar.gz
|
08f9459e7125fed2612abd71596e04d172695921aff82120d1c1e5e9b6fff2a3 golangci-lint-1.64.6-darwin-amd64.tar.gz
|
||||||
bb4a9ece7ef66ef49bd6de873f5f5ec801cf93dacf58efb360cc939b4fd37c47 golangci-lint-1.64.4-darwin-arm64.tar.gz
|
8c10d0c7c3935b8c2269d628b6a06a8f48d8fb4cc31af02fe4ce0cd18b0704c1 golangci-lint-1.64.6-darwin-arm64.tar.gz
|
||||||
fd8f9046150356e36ad8af31f99219515d5e0ff6e8e9f4204182312447018fcf golangci-lint-1.64.4-freebsd-386.tar.gz
|
c07fcabb55deb8d2c4d390025568e76162d7f91b1d14bd2311be45d8d440a624 golangci-lint-1.64.6-freebsd-386.tar.gz
|
||||||
bbae940caa31cb1d2d8d26abd867666cd92746d2ddc63d6917d128c456d715ef golangci-lint-1.64.4-freebsd-amd64.tar.gz
|
8ed1ef1102e1a42983ffcaae8e06de6a540334c3a69e205c610b8a7c92d469cd golangci-lint-1.64.6-freebsd-amd64.tar.gz
|
||||||
86eb00cbdc5e277857a380d556d78a42b3edf8aae5991de8767dce7da337d73b golangci-lint-1.64.4-freebsd-armv6.tar.gz
|
8f8dda66d1b4a85cc8a1daf1b69af6559c3eb0a41dd8033148a9ad85cfc0e1d9 golangci-lint-1.64.6-freebsd-armv6.tar.gz
|
||||||
c0f7d50f4fcd39ae8bda181c127b078a79d87c885ac472c4cce5b01134dde5bc golangci-lint-1.64.4-freebsd-armv7.tar.gz
|
59e8ca1fa254661b2a55e96515b14a10cd02221d443054deac5b28c3c3e12d6b golangci-lint-1.64.6-freebsd-armv7.tar.gz
|
||||||
31d264fa26a73a583425bc9b39c18de70a330e346fe800e80c277ddb5f8477b6 golangci-lint-1.64.4-illumos-amd64.tar.gz
|
e3d323d5f132e9b7593141bfe1d19c8460e65a55cea1008ec96945fed563f981 golangci-lint-1.64.6-illumos-amd64.tar.gz
|
||||||
3dccc18a6b1a0f2518196190ed56277b1480b381231605936eca6b862426b83c golangci-lint-1.64.4-linux-386.deb
|
5ce910f2a864c5fbeb32a30cbd506e1b2ef215f7a0f422cd5be6370b13db6f03 golangci-lint-1.64.6-linux-386.deb
|
||||||
b95f3e757c59ada75144bd086478ab63bcda83c2b2a935e60d8f39aff20fbcd2 golangci-lint-1.64.4-linux-386.rpm
|
2caab682a26b9a5fb94ba24e3a7e1404fa9eab2c12e36ae1c5548d80a1be190c golangci-lint-1.64.6-linux-386.rpm
|
||||||
8fa58af0022ae0487162c02d08075e2d869c9b5f8613a64b85fd769a2a2d2b93 golangci-lint-1.64.4-linux-386.tar.gz
|
2d82d0a4716e6d9b70c95103054855cb4b5f20f7bbdee42297f0189955bd14b6 golangci-lint-1.64.6-linux-386.tar.gz
|
||||||
fa80d2ad920fc74b85a0b93ad1671fd27c9c4d6f22e167cc4e1da758d72dd893 golangci-lint-1.64.4-linux-amd64.deb
|
9cd82503e9841abcaa57663fc899587fe90363c26d86a793a98d3080fd25e907 golangci-lint-1.64.6-linux-amd64.deb
|
||||||
8bf2e3c82504a893a12e84ecbc8071f787075d2fdc1ef946deda3d29aae1afad golangci-lint-1.64.4-linux-amd64.rpm
|
981aaca5e5202d4fbb162ec7080490eb67ef5d32add5fb62fb02210597acc9da golangci-lint-1.64.6-linux-amd64.rpm
|
||||||
7de8f80a12cd3052610e6be21f08f2220805b1f3e43b3b6ec843f14a12140de2 golangci-lint-1.64.4-linux-amd64.tar.gz
|
71e290acbacb7b3ba4f68f0540fb74dc180c4336ac8a6f3bdcd7fcc48b15148d golangci-lint-1.64.6-linux-amd64.tar.gz
|
||||||
ade222b9ae997a2875e44ed53ca5a129f24944b232132d0a5898f101381d3bfa golangci-lint-1.64.4-linux-arm64.deb
|
718016bb06c1f598a8d23c7964e2643de621dbe5808688cb38857eb0bb773c84 golangci-lint-1.64.6-linux-arm64.deb
|
||||||
8f9f8617ed0d8b8f62c8dfe69877932c0d85f0d83a9ec3250b04777c7133ada1 golangci-lint-1.64.4-linux-arm64.rpm
|
ddc0e7b4b10b47cf894aea157e89e3674bbb60f8f5c480110c21c49cc2c1634d golangci-lint-1.64.6-linux-arm64.rpm
|
||||||
debf1f73e71f1dd288957574eab4e7515b1b04c4ac2f127192181da8a42f0e8e golangci-lint-1.64.4-linux-arm64.tar.gz
|
99a7ff13dec7a8781a68408b6ecb8a1c5e82786cba3189eaa91d5cdcc24362ce golangci-lint-1.64.6-linux-arm64.tar.gz
|
||||||
3f616caa4a16738114ad29076a333c62bb0650fe4846b438c341d235f78ee5aa golangci-lint-1.64.4-linux-armv6.deb
|
90e360f89c244394912b8709fb83a900b6d56cf19141df2afaf9e902ef3057b1 golangci-lint-1.64.6-linux-armv6.deb
|
||||||
6a7c9d7b7c57d7d440e1e3f133e18e4b5b703604d4ae7f7862fb9ea461887a8b golangci-lint-1.64.4-linux-armv6.rpm
|
46546ff7c98d873ffcdbee06b39dc1024fc08db4fbf1f6309360a44cf976b5c2 golangci-lint-1.64.6-linux-armv6.rpm
|
||||||
d7b9e3c89af93d98c6b5a555ef48e25bee24f84d7825a29e343e9966404d5b4d golangci-lint-1.64.4-linux-armv6.tar.gz
|
e45c1a42868aca0b0ee54d14fb89da216f3b4409367cd7bce3b5f36788b4fc92 golangci-lint-1.64.6-linux-armv6.tar.gz
|
||||||
18179ecf04ace3051450852424b31a4d26644a3912e759cd595e624eee0174bd golangci-lint-1.64.4-linux-armv7.deb
|
3cf6ddbbbf358db3de4b64a24f9683bbe2da3f003cfdee86cb610124b57abafb golangci-lint-1.64.6-linux-armv7.deb
|
||||||
f669fce85d3edc3d557d1e64e2241d895f1ee676f79d139874bf76693cc447a2 golangci-lint-1.64.4-linux-armv7.rpm
|
508b6712710da10f11aab9ea5e63af415c932dfe7fff718e649d3100b838f797 golangci-lint-1.64.6-linux-armv7.rpm
|
||||||
9bbaa13b474ecb1560fb05aee0a8cab3e9cc327d41632bee8b553b766f2bf756 golangci-lint-1.64.4-linux-armv7.tar.gz
|
da9a8bbee86b4dfee73fbc17e0856ec84c5b04919eb09bf3dd5904c39ce41753 golangci-lint-1.64.6-linux-armv7.tar.gz
|
||||||
b5a2725adba2a1b04a5160ffa0730ba30b88c1ee99ecbc65dd7075a8d70f3730 golangci-lint-1.64.4-linux-loong64.deb
|
ad496a58284e1e9c8ac6f993fec429dcd96c85a8c4715dbb6530a5af0dae7fbd golangci-lint-1.64.6-linux-loong64.deb
|
||||||
28ac00afb3f6407808fe7c15a6002e5fe6b3b461df3fe0223603542e6f9b974a golangci-lint-1.64.4-linux-loong64.rpm
|
3bd70fa737b224750254dce616d9a188570e49b894b0cdb2fd04155e2c061350 golangci-lint-1.64.6-linux-loong64.rpm
|
||||||
121170309fed82cb64d6fae5020ef2b194e591a53156370c221a80121cc4aa08 golangci-lint-1.64.4-linux-loong64.tar.gz
|
a535af973499729f2215a84303eb0de61399057aad6901ddea1b4f73f68d5f2c golangci-lint-1.64.6-linux-loong64.tar.gz
|
||||||
aadfaab41380f53ada8f7ad4fa9af784bb882eaaa1e3939e048b3a5ea8f2c13f golangci-lint-1.64.4-linux-mips64.deb
|
6ad2a1cd37ca30303a488abfca2de52aff57519901c6d8d2608656fe9fb05292 golangci-lint-1.64.6-linux-mips64.deb
|
||||||
6fecbff69086e316dd028050d3ebf82fc466c7f4dc0f6084307054621101c202 golangci-lint-1.64.4-linux-mips64.rpm
|
5f18369f0ca010a02c267352ebe6e3e0514c6debff49899c9e5520906c0da287 golangci-lint-1.64.6-linux-mips64.rpm
|
||||||
430c48034527af028dc608c115166f47b6b67c8cb83a317d9d44876b9753292a golangci-lint-1.64.4-linux-mips64.tar.gz
|
3449d6c13307b91b0e8817f8911c07c3412cdb6931b8d101e42db3e9762e91ad golangci-lint-1.64.6-linux-mips64.tar.gz
|
||||||
432126af2bbbd639dbbcc2c265337f104cc613a7b4c5a8fb5e49f182b428f077 golangci-lint-1.64.4-linux-mips64le.deb
|
d4712a348f65dcaf9f6c58f1cfd6d0984d54a902873dca5e76f0d686f5c59499 golangci-lint-1.64.6-linux-mips64le.deb
|
||||||
cfa663eee0fae721510d6dc31e61db4be51b4e4244eb7de2b6a9bc6d475551e5 golangci-lint-1.64.4-linux-mips64le.rpm
|
57cea4538894558cb8c956d6b69c5509e4304546abe4a467478fc9ada0c736d6 golangci-lint-1.64.6-linux-mips64le.rpm
|
||||||
2bacfac65182893c5ceb67b61d5b9d2b24da47726d84cb87d734a6e32f040148 golangci-lint-1.64.4-linux-mips64le.tar.gz
|
bc030977d44535b2152fddb2732f056d193c043992fe638ddecea21a40ef09fe golangci-lint-1.64.6-linux-mips64le.tar.gz
|
||||||
66d86e47d68420c03045eff0698e9acf9f82d97cd1f07d7e52837a6e544eceed golangci-lint-1.64.4-linux-ppc64le.deb
|
1ceb4e492efa527d246c61798c581f49113756fab8c39bb3eefdb1fa97041f92 golangci-lint-1.64.6-linux-ppc64le.deb
|
||||||
05466bd2d8e82f7245b838e29d0c90cdbaf67375b2b5bc980d8510d5edb206ad golangci-lint-1.64.4-linux-ppc64le.rpm
|
454e1c2b3583a8644e0c33a970fb4ce35b8f11848e1a770d9095d99d159ad0ab golangci-lint-1.64.6-linux-ppc64le.rpm
|
||||||
e013b6980f7140712af0aedc9a661265e1d3c6adc7e9d045734e3686a3601acc golangci-lint-1.64.4-linux-ppc64le.tar.gz
|
fddf30d35923eb6c7bb57520d8645768f802bf86c4cbf5a3a13049efb9e71b82 golangci-lint-1.64.6-linux-ppc64le.tar.gz
|
||||||
753980d2fb421d35292b71b51f9bb0552f18de2eb181ccf692ef79e086ad2304 golangci-lint-1.64.4-linux-riscv64.deb
|
bd75f0dd6f65bee5821c433803b28f3c427ef3582764c3d0e7f7fae1c9d468b6 golangci-lint-1.64.6-linux-riscv64.deb
|
||||||
73a33a883020298f8c652a4ace4f6888103d80d411fe3e9b77ce0a3b36ff7d3a golangci-lint-1.64.4-linux-riscv64.rpm
|
58457207c225cbd5340c8dcb75d9a44aa890d604e28464115a3a9762febaed04 golangci-lint-1.64.6-linux-riscv64.rpm
|
||||||
020efd09b894dc75012f43b1d4c58e69e5446ca3f79845b5e10a02588def3e1b golangci-lint-1.64.4-linux-riscv64.tar.gz
|
82639518a613a6705b7e2de5b28c278e875d782a5c97e9c1b2ac10b4ecd7852f golangci-lint-1.64.6-linux-riscv64.tar.gz
|
||||||
4d99a16bc9ff5cea5e4ae3568c6254390fbdad8f1d370c287744409112bceff9 golangci-lint-1.64.4-linux-s390x.deb
|
131d69204f8ced200b1437731e987cc886edac30dc87e6e1dcbd4f833d351713 golangci-lint-1.64.6-linux-s390x.deb
|
||||||
6e63374a9f95fc09f2bb9501059eb18b8107e08416722ba7b561ae385d80b33b golangci-lint-1.64.4-linux-s390x.rpm
|
ca6caf28ca7a1df7cff720f8fd6ac4b8f2eac10c8cbfe7d2eade70876aded570 golangci-lint-1.64.6-linux-s390x.rpm
|
||||||
62a6febb892b26eee84a32cc99d04afe90bf62d0970774b1bab75e36b66f53b4 golangci-lint-1.64.4-linux-s390x.tar.gz
|
ed966d28a6512bc2b1120029a9f78ed77f2015e330b589a731d67d59be30b0b1 golangci-lint-1.64.6-linux-s390x.tar.gz
|
||||||
5ba4d235f9abd60d2d280f9fe4228c6ddab11ae9fe48d253b7e477046675f1f6 golangci-lint-1.64.4-netbsd-386.tar.gz
|
b181132b41943fc77ace7f9f5523085d12d3613f27774a0e35e17dd5e65ba589 golangci-lint-1.64.6-netbsd-386.tar.gz
|
||||||
061643bb4370a52010e5e76f88f5b5220e51058df50af86b45286263af92b321 golangci-lint-1.64.4-netbsd-amd64.tar.gz
|
f7b81c426006e3c699dc8665797a462aecba8c2cd23ac4971472e55184d95bc9 golangci-lint-1.64.6-netbsd-amd64.tar.gz
|
||||||
12b2be6c35bcf7a36374e952c7efec7905a17fa3ebe6214924ba8721b766c4af golangci-lint-1.64.4-netbsd-arm64.tar.gz
|
663d6fb4c3bef57b8aacdb1b1a4634075e55d294ed170724b443374860897ca6 golangci-lint-1.64.6-netbsd-arm64.tar.gz
|
||||||
553a71a677751dccee082ed8afe24cf58bef725f72aa02aedcc6758dd8f44125 golangci-lint-1.64.4-netbsd-armv6.tar.gz
|
8c7a76ee822568cc19352bbb9b2b0863dc5e185eb07782adbbaf648afd02ebcd golangci-lint-1.64.6-netbsd-armv6.tar.gz
|
||||||
74a5a5a7b8c8cd11dccdec303ee6592d904b09836782e6d658be359737f300ef golangci-lint-1.64.4-netbsd-armv7.tar.gz
|
0ce26d56ce78e516529e087118ba3f1bcd71d311b4c5b2bde6ec24a6e8966d85 golangci-lint-1.64.6-netbsd-armv7.tar.gz
|
||||||
b3ceea2187843f9ba6510303141a72465621e3f190a262c0b48707b9e983ea9a golangci-lint-1.64.4-source.tar.gz
|
135d5d998168683f46e4fab308cef5aa3c282025b7de6b258f976aadfb820db7 golangci-lint-1.64.6-source.tar.gz
|
||||||
fda4de312fcb0b4853b03bcdfb31ac631a68fa0b966f6aa010eb30d14427180b golangci-lint-1.64.4-windows-386.zip
|
ccdb0cc249531a205304a76308cfa7cda830083d083d557884416a2461148263 golangci-lint-1.64.6-windows-386.zip
|
||||||
8fc60bd809f86c6431ed8f3d1f25917538c0ebc93e670cfb199f25bbaa56e602 golangci-lint-1.64.4-windows-amd64.zip
|
2d88f282e08e1853c70bc7c914b5f58beaa6509903d56098aeb9bc3df30ea9d5 golangci-lint-1.64.6-windows-amd64.zip
|
||||||
b9058c9d2ae8d75048c6ad8885d11e5ac0a6c40be2af85bfa6605e48d69d3eb6 golangci-lint-1.64.4-windows-arm64.zip
|
6a3c6e7afc6916392679d7d6b95ac239827644e3e50ec4e8ca6ab1e4e6db65fe golangci-lint-1.64.6-windows-arm64.zip
|
||||||
809b0131adb1aa8b359becd13222df367b505bc2955bb3b1d2d0dd6f07347b13 golangci-lint-1.64.4-windows-armv6.zip
|
9c9c1ef9687651566987f93e76252f7526c386901d7d8aeee044ca88115da4b1 golangci-lint-1.64.6-windows-armv6.zip
|
||||||
fc43f8c95d6bec8ba9a3557a0bb63cf9f504137ce6d24cfdc4c330d46f7779d2 golangci-lint-1.64.4-windows-armv7.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 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
|
# 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.
|
# 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/
|
# https://go.dev/dl/
|
||||||
d7f0013f82e6d7f862cc6cb5c8cdb48eef5f2e239b35baa97e2f1a7466043767 go1.19.6.src.tar.gz
|
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/
|
# https://go.dev/dl/
|
||||||
58f0c5ced45a0012bce2ff7a9df03e128abcc8818ebabe5027bb92bafe20e421 go1.21.9.src.tar.gz
|
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
|
# version:protoc 27.1
|
||||||
# https://github.com/protocolbuffers/protobuf/releases/
|
# https://github.com/protocolbuffers/protobuf/releases/
|
||||||
# https://github.com/protocolbuffers/protobuf/releases/download/v27.1/
|
# https://github.com/protocolbuffers/protobuf/releases/download/v27.1/
|
||||||
|
|
|
||||||
34
build/ci.go
34
build/ci.go
|
|
@ -25,8 +25,7 @@ Usage: go run build/ci.go <command> <command flags/arguments>
|
||||||
Available commands are:
|
Available commands are:
|
||||||
|
|
||||||
lint -- runs certain pre-selected linters
|
lint -- runs certain pre-selected linters
|
||||||
check_tidy -- verifies that everything is 'go mod tidy'-ed
|
check_generate -- verifies that 'go generate' and 'go mod tidy' do not produce changes
|
||||||
check_generate -- verifies that everything is 'go generate'-ed
|
|
||||||
check_baddeps -- verifies that certain dependencies are avoided
|
check_baddeps -- verifies that certain dependencies are avoided
|
||||||
|
|
||||||
install [ -arch architecture ] [ -cc compiler ] [ packages... ] -- builds packages and executables
|
install [ -arch architecture ] [ -cc compiler ] [ packages... ] -- builds packages and executables
|
||||||
|
|
@ -155,8 +154,6 @@ func main() {
|
||||||
doTest(os.Args[2:])
|
doTest(os.Args[2:])
|
||||||
case "lint":
|
case "lint":
|
||||||
doLint(os.Args[2:])
|
doLint(os.Args[2:])
|
||||||
case "check_tidy":
|
|
||||||
doCheckTidy()
|
|
||||||
case "check_generate":
|
case "check_generate":
|
||||||
doCheckGenerate()
|
doCheckGenerate()
|
||||||
case "check_baddeps":
|
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.
|
// doCheckTidy assets that the Go modules files are tidied already.
|
||||||
func doCheckTidy() {
|
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
|
// doCheckGenerate ensures that re-generating generated files does not cause
|
||||||
|
|
@ -375,12 +356,13 @@ func doCheckTidy() {
|
||||||
func doCheckGenerate() {
|
func doCheckGenerate() {
|
||||||
var (
|
var (
|
||||||
cachedir = flag.String("cachedir", "./build/cache", "directory for caching binaries.")
|
cachedir = flag.String("cachedir", "./build/cache", "directory for caching binaries.")
|
||||||
|
tc = new(build.GoToolchain)
|
||||||
)
|
)
|
||||||
// Compute the origin hashes of all the files
|
// Compute the origin hashes of all the files
|
||||||
var hashes map[string][32]byte
|
var hashes map[string][32]byte
|
||||||
|
|
||||||
var err error
|
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 {
|
if err != nil {
|
||||||
log.Fatal("Error computing hashes", "err", err)
|
log.Fatal("Error computing hashes", "err", err)
|
||||||
}
|
}
|
||||||
|
|
@ -389,13 +371,13 @@ func doCheckGenerate() {
|
||||||
protocPath = downloadProtoc(*cachedir)
|
protocPath = downloadProtoc(*cachedir)
|
||||||
protocGenGoPath = downloadProtocGenGo(*cachedir)
|
protocGenGoPath = downloadProtocGenGo(*cachedir)
|
||||||
)
|
)
|
||||||
c := new(build.GoToolchain).Go("generate", "./...")
|
c := tc.Go("generate", "./...")
|
||||||
pathList := []string{filepath.Join(protocPath, "bin"), protocGenGoPath, os.Getenv("PATH")}
|
pathList := []string{filepath.Join(protocPath, "bin"), protocGenGoPath, os.Getenv("PATH")}
|
||||||
c.Env = append(c.Env, "PATH="+strings.Join(pathList, string(os.PathListSeparator)))
|
c.Env = append(c.Env, "PATH="+strings.Join(pathList, string(os.PathListSeparator)))
|
||||||
build.MustRun(c)
|
build.MustRun(c)
|
||||||
|
|
||||||
// Check if generate file hashes have changed
|
// 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 {
|
if err != nil {
|
||||||
log.Fatalf("Error re-computing hashes: %v", err)
|
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 ./...'")
|
log.Fatal("One or more generated files were updated by running 'go generate ./...'")
|
||||||
}
|
}
|
||||||
fmt.Println("No stale files detected.")
|
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
|
// doCheckBadDeps verifies whether certain unintended dependencies between some
|
||||||
|
|
@ -843,7 +829,7 @@ func downloadGoBootstrapSources(cachedir string) []string {
|
||||||
csdb := build.MustLoadChecksums("build/checksums.txt")
|
csdb := build.MustLoadChecksums("build/checksums.txt")
|
||||||
|
|
||||||
var bundles []string
|
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)
|
gobootVersion, err := build.Version(csdb, booter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,8 @@ override_dh_auto_build:
|
||||||
# requirements opposed to older versions of Go.
|
# requirements opposed to older versions of Go.
|
||||||
(mv .goboot-1 ../ && cd ../.goboot-1/src && ./make.bash)
|
(mv .goboot-1 ../ && cd ../.goboot-1/src && ./make.bash)
|
||||||
(mv .goboot-2 ../ && cd ../.goboot-2/src && GOROOT_BOOTSTRAP=`pwd`/../../.goboot-1 ./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
|
# We can't download external go modules within Launchpad, so we're shipping the
|
||||||
# entire dependency source cache with go-ethereum.
|
# entire dependency source cache with go-ethereum.
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func abigen(c *cli.Context) error {
|
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) == "" {
|
if c.String(pkgFlag.Name) == "" {
|
||||||
utils.Fatalf("No destination package specified (--pkg)")
|
utils.Fatalf("No destination package specified (--pkg)")
|
||||||
|
|
|
||||||
|
|
@ -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"
|
> 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
|
### 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
|
### 3.0.0
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,13 +57,7 @@ func TestMain(m *testing.M) {
|
||||||
// This method creates a temporary keystore folder which will be removed after
|
// This method creates a temporary keystore folder which will be removed after
|
||||||
// the test exits.
|
// the test exits.
|
||||||
func runClef(t *testing.T, args ...string) *testproc {
|
func runClef(t *testing.T, args ...string) *testproc {
|
||||||
ddir, err := os.MkdirTemp("", "cleftest-*")
|
ddir := t.TempDir()
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
t.Cleanup(func() {
|
|
||||||
os.RemoveAll(ddir)
|
|
||||||
})
|
|
||||||
return runWithKeystore(t, ddir, args...)
|
return runWithKeystore(t, ddir, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -163,7 +163,7 @@ func discv4Ping(ctx *cli.Context) error {
|
||||||
defer disc.Close()
|
defer disc.Close()
|
||||||
|
|
||||||
start := time.Now()
|
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)
|
return fmt.Errorf("node didn't respond: %v", err)
|
||||||
}
|
}
|
||||||
fmt.Printf("node responded to ping (RTT %v).\n", time.Since(start))
|
fmt.Printf("node responded to ping (RTT %v).\n", time.Since(start))
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,8 @@ func discv5Ping(ctx *cli.Context) error {
|
||||||
disc, _ := startV5(ctx)
|
disc, _ := startV5(ctx)
|
||||||
defer disc.Close()
|
defer disc.Close()
|
||||||
|
|
||||||
fmt.Println(disc.Ping(n))
|
_, err := disc.Ping(n)
|
||||||
|
fmt.Println(err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"maps"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
@ -40,7 +41,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"golang.org/x/exp/maps"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Chain is a lightweight blockchain-like store which can read a hivechain
|
// 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
|
// GetSender returns the address associated with account at the index in the
|
||||||
// pre-funded accounts list.
|
// pre-funded accounts list.
|
||||||
func (c *Chain) GetSender(idx int) (common.Address, uint64) {
|
func (c *Chain) GetSender(idx int) (common.Address, uint64) {
|
||||||
accounts := maps.Keys(c.senders)
|
accounts := slices.SortedFunc(maps.Keys(c.senders), common.Address.Cmp)
|
||||||
slices.SortFunc(accounts, common.Address.Cmp)
|
|
||||||
addr := accounts[idx]
|
addr := accounts[idx]
|
||||||
return addr, c.senders[addr].Nonce
|
return addr, c.senders[addr].Nonce
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -130,11 +130,16 @@ func (c *Conn) Write(proto Proto, code uint64, msg any) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errDisc error = fmt.Errorf("disconnect")
|
||||||
|
|
||||||
// ReadEth reads an Eth sub-protocol wire message.
|
// ReadEth reads an Eth sub-protocol wire message.
|
||||||
func (c *Conn) ReadEth() (any, error) {
|
func (c *Conn) ReadEth() (any, error) {
|
||||||
c.SetReadDeadline(time.Now().Add(timeout))
|
c.SetReadDeadline(time.Now().Add(timeout))
|
||||||
for {
|
for {
|
||||||
code, data, _, err := c.Conn.Read()
|
code, data, _, err := c.Conn.Read()
|
||||||
|
if code == discMsg {
|
||||||
|
return nil, errDisc
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,12 @@
|
||||||
package ethtest
|
package ethtest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
||||||
|
|
@ -79,6 +83,8 @@ func (s *Suite) EthTests() []utesting.Test {
|
||||||
{Name: "InvalidTxs", Fn: s.TestInvalidTxs},
|
{Name: "InvalidTxs", Fn: s.TestInvalidTxs},
|
||||||
{Name: "NewPooledTxs", Fn: s.TestNewPooledTxs},
|
{Name: "NewPooledTxs", Fn: s.TestNewPooledTxs},
|
||||||
{Name: "BlobViolations", Fn: s.TestBlobViolations},
|
{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()
|
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(©)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"maps"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
@ -28,7 +29,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/tests"
|
"github.com/ethereum/go-ethereum/tests"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"golang.org/x/exp/maps"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var blockTestCommand = &cli.Command{
|
var blockTestCommand = &cli.Command{
|
||||||
|
|
@ -80,8 +80,7 @@ func runBlockTest(ctx *cli.Context, fname string) ([]testResult, error) {
|
||||||
tracer := tracerFromFlags(ctx)
|
tracer := tracerFromFlags(ctx)
|
||||||
|
|
||||||
// Pull out keys to sort and ensure tests are run in order.
|
// Pull out keys to sort and ensure tests are run in order.
|
||||||
keys := maps.Keys(tests)
|
keys := slices.Sorted(maps.Keys(tests))
|
||||||
slices.Sort(keys)
|
|
||||||
|
|
||||||
// Run all the tests.
|
// Run all the tests.
|
||||||
var results []testResult
|
var results []testResult
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ var (
|
||||||
ArgsUsage: "<genesisPath>",
|
ArgsUsage: "<genesisPath>",
|
||||||
Flags: slices.Concat([]cli.Flag{
|
Flags: slices.Concat([]cli.Flag{
|
||||||
utils.CachePreimagesFlag,
|
utils.CachePreimagesFlag,
|
||||||
utils.OverrideCancun,
|
utils.OverridePrague,
|
||||||
utils.OverrideVerkle,
|
utils.OverrideVerkle,
|
||||||
}, utils.DatabaseFlags),
|
}, utils.DatabaseFlags),
|
||||||
Description: `
|
Description: `
|
||||||
|
|
@ -103,11 +103,12 @@ if one is set. Otherwise it prints the genesis from the datadir.`,
|
||||||
utils.StateHistoryFlag,
|
utils.StateHistoryFlag,
|
||||||
}, utils.DatabaseFlags),
|
}, utils.DatabaseFlags),
|
||||||
Description: `
|
Description: `
|
||||||
The import command imports blocks from an RLP-encoded form. The form can be one file
|
The import command allows the import of blocks from an RLP-encoded format. This format can be a single file
|
||||||
with several RLP-encoded blocks, or several files can be used.
|
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,
|
If only one file is used, an import error will result in the entire import process failing. If
|
||||||
processing will proceed even if an individual RLP-file import failure occurs.`,
|
multiple files are processed, the import process will continue even if an individual RLP file fails
|
||||||
|
to import successfully.`,
|
||||||
}
|
}
|
||||||
exportCommand = &cli.Command{
|
exportCommand = &cli.Command{
|
||||||
Action: exportChain,
|
Action: exportChain,
|
||||||
|
|
@ -212,9 +213,9 @@ func initGenesis(ctx *cli.Context) error {
|
||||||
defer stack.Close()
|
defer stack.Close()
|
||||||
|
|
||||||
var overrides core.ChainOverrides
|
var overrides core.ChainOverrides
|
||||||
if ctx.IsSet(utils.OverrideCancun.Name) {
|
if ctx.IsSet(utils.OverridePrague.Name) {
|
||||||
v := ctx.Uint64(utils.OverrideCancun.Name)
|
v := ctx.Uint64(utils.OverridePrague.Name)
|
||||||
overrides.OverrideCancun = &v
|
overrides.OverridePrague = &v
|
||||||
}
|
}
|
||||||
if ctx.IsSet(utils.OverrideVerkle.Name) {
|
if ctx.IsSet(utils.OverrideVerkle.Name) {
|
||||||
v := ctx.Uint64(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 {
|
if err := utils.ImportChain(chain, arg); err != nil {
|
||||||
importErr = err
|
importErr = err
|
||||||
log.Error("Import error", "file", arg, "err", err)
|
log.Error("Import error", "file", arg, "err", err)
|
||||||
|
if err == utils.ErrImportInterrupted {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -183,9 +183,9 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
|
||||||
// makeFullNode loads geth configuration and creates the Ethereum backend.
|
// makeFullNode loads geth configuration and creates the Ethereum backend.
|
||||||
func makeFullNode(ctx *cli.Context) *node.Node {
|
func makeFullNode(ctx *cli.Context) *node.Node {
|
||||||
stack, cfg := makeConfigNode(ctx)
|
stack, cfg := makeConfigNode(ctx)
|
||||||
if ctx.IsSet(utils.OverrideCancun.Name) {
|
if ctx.IsSet(utils.OverridePrague.Name) {
|
||||||
v := ctx.Uint64(utils.OverrideCancun.Name)
|
v := ctx.Uint64(utils.OverridePrague.Name)
|
||||||
cfg.Eth.OverrideCancun = &v
|
cfg.Eth.OverridePrague = &v
|
||||||
}
|
}
|
||||||
if ctx.IsSet(utils.OverrideVerkle.Name) {
|
if ctx.IsSet(utils.OverrideVerkle.Name) {
|
||||||
v := ctx.Uint64(utils.OverrideVerkle.Name)
|
v := ctx.Uint64(utils.OverrideVerkle.Name)
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ var (
|
||||||
utils.NoUSBFlag, // deprecated
|
utils.NoUSBFlag, // deprecated
|
||||||
utils.USBFlag,
|
utils.USBFlag,
|
||||||
utils.SmartCardDaemonPathFlag,
|
utils.SmartCardDaemonPathFlag,
|
||||||
utils.OverrideCancun,
|
utils.OverridePrague,
|
||||||
utils.OverrideVerkle,
|
utils.OverrideVerkle,
|
||||||
utils.EnablePersonal, // deprecated
|
utils.EnablePersonal, // deprecated
|
||||||
utils.TxPoolLocalsFlag,
|
utils.TxPoolLocalsFlag,
|
||||||
|
|
@ -86,6 +86,7 @@ var (
|
||||||
utils.SnapshotFlag,
|
utils.SnapshotFlag,
|
||||||
utils.TxLookupLimitFlag, // deprecated
|
utils.TxLookupLimitFlag, // deprecated
|
||||||
utils.TransactionHistoryFlag,
|
utils.TransactionHistoryFlag,
|
||||||
|
utils.ChainHistoryFlag,
|
||||||
utils.StateHistoryFlag,
|
utils.StateHistoryFlag,
|
||||||
utils.LightServeFlag, // deprecated
|
utils.LightServeFlag, // deprecated
|
||||||
utils.LightIngressFlag, // deprecated
|
utils.LightIngressFlag, // deprecated
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,9 @@ const (
|
||||||
importBatchSize = 2500
|
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.
|
// Fatalf formats a message to standard error and exits the program.
|
||||||
// The message is also printed to standard output if standard error
|
// The message is also printed to standard output if standard error
|
||||||
// is redirected to a different file.
|
// is redirected to a different file.
|
||||||
|
|
@ -191,7 +194,7 @@ func ImportChain(chain *core.BlockChain, fn string) error {
|
||||||
for batch := 0; ; batch++ {
|
for batch := 0; ; batch++ {
|
||||||
// Load a batch of RLP blocks.
|
// Load a batch of RLP blocks.
|
||||||
if checkInterrupt() {
|
if checkInterrupt() {
|
||||||
return errors.New("interrupted")
|
return ErrImportInterrupted
|
||||||
}
|
}
|
||||||
i := 0
|
i := 0
|
||||||
for ; i < importBatchSize; i++ {
|
for ; i < importBatchSize; i++ {
|
||||||
|
|
|
||||||
|
|
@ -233,9 +233,9 @@ var (
|
||||||
Value: 2048,
|
Value: 2048,
|
||||||
Category: flags.EthCategory,
|
Category: flags.EthCategory,
|
||||||
}
|
}
|
||||||
OverrideCancun = &cli.Uint64Flag{
|
OverridePrague = &cli.Uint64Flag{
|
||||||
Name: "override.cancun",
|
Name: "override.prague",
|
||||||
Usage: "Manually specify the Cancun fork timestamp, overriding the bundled setting",
|
Usage: "Manually specify the Prague fork timestamp, overriding the bundled setting",
|
||||||
Category: flags.EthCategory,
|
Category: flags.EthCategory,
|
||||||
}
|
}
|
||||||
OverrideVerkle = &cli.Uint64Flag{
|
OverrideVerkle = &cli.Uint64Flag{
|
||||||
|
|
@ -272,6 +272,12 @@ var (
|
||||||
Value: ethconfig.Defaults.TransactionHistory,
|
Value: ethconfig.Defaults.TransactionHistory,
|
||||||
Category: flags.StateCategory,
|
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
|
// Beacon client light sync settings
|
||||||
BeaconApiFlag = &cli.StringSliceFlag{
|
BeaconApiFlag = &cli.StringSliceFlag{
|
||||||
Name: "beacon.api",
|
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,
|
// 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.
|
// returning an empty string if IPC was explicitly disabled, or the set path.
|
||||||
func setIPC(ctx *cli.Context, cfg *node.Config) {
|
func setIPC(ctx *cli.Context, cfg *node.Config) {
|
||||||
CheckExclusive(ctx, IPCDisabledFlag, IPCPathFlag)
|
flags.CheckExclusive(ctx, IPCDisabledFlag, IPCPathFlag)
|
||||||
switch {
|
switch {
|
||||||
case ctx.Bool(IPCDisabledFlag.Name):
|
case ctx.Bool(IPCDisabledFlag.Name):
|
||||||
cfg.IPCPath = ""
|
cfg.IPCPath = ""
|
||||||
|
|
@ -1295,8 +1301,8 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) {
|
||||||
cfg.NoDiscovery = true
|
cfg.NoDiscovery = true
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckExclusive(ctx, DiscoveryV4Flag, NoDiscoverFlag)
|
flags.CheckExclusive(ctx, DiscoveryV4Flag, NoDiscoverFlag)
|
||||||
CheckExclusive(ctx, DiscoveryV5Flag, NoDiscoverFlag)
|
flags.CheckExclusive(ctx, DiscoveryV5Flag, NoDiscoverFlag)
|
||||||
cfg.DiscoveryV4 = ctx.Bool(DiscoveryV4Flag.Name)
|
cfg.DiscoveryV4 = ctx.Bool(DiscoveryV4Flag.Name)
|
||||||
cfg.DiscoveryV5 = ctx.Bool(DiscoveryV5Flag.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.
|
// SetEthConfig applies eth-related command line flags to the config.
|
||||||
func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
||||||
// Avoid conflicting network flags
|
// Avoid conflicting network flags
|
||||||
CheckExclusive(ctx, MainnetFlag, DeveloperFlag, SepoliaFlag, HoleskyFlag)
|
flags.CheckExclusive(ctx, MainnetFlag, DeveloperFlag, SepoliaFlag, HoleskyFlag)
|
||||||
CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer
|
flags.CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer
|
||||||
|
|
||||||
// Set configurations from CLI flags
|
// Set configurations from CLI flags
|
||||||
setEtherbase(ctx, cfg)
|
setEtherbase(ctx, cfg)
|
||||||
|
|
@ -1607,10 +1572,19 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
||||||
if ctx.IsSet(SyncTargetFlag.Name) {
|
if ctx.IsSet(SyncTargetFlag.Name) {
|
||||||
cfg.SyncMode = ethconfig.FullSync // dev sync target forces full sync
|
cfg.SyncMode = ethconfig.FullSync // dev sync target forces full sync
|
||||||
} else if ctx.IsSet(SyncModeFlag.Name) {
|
} else if ctx.IsSet(SyncModeFlag.Name) {
|
||||||
if err = cfg.SyncMode.UnmarshalText([]byte(ctx.String(SyncModeFlag.Name))); err != nil {
|
value := ctx.String(SyncModeFlag.Name)
|
||||||
Fatalf("invalid --syncmode flag: %v", err)
|
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) {
|
if ctx.IsSet(NetworkIdFlag.Name) {
|
||||||
cfg.NetworkId = ctx.Uint64(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 {
|
func MakeBeaconLightConfig(ctx *cli.Context) bparams.ClientConfig {
|
||||||
var config bparams.ClientConfig
|
var config bparams.ClientConfig
|
||||||
customConfig := ctx.IsSet(BeaconConfigFlag.Name)
|
customConfig := ctx.IsSet(BeaconConfigFlag.Name)
|
||||||
CheckExclusive(ctx, MainnetFlag, SepoliaFlag, HoleskyFlag, BeaconConfigFlag)
|
flags.CheckExclusive(ctx, MainnetFlag, SepoliaFlag, HoleskyFlag, BeaconConfigFlag)
|
||||||
switch {
|
switch {
|
||||||
case ctx.Bool(MainnetFlag.Name):
|
case ctx.Bool(MainnetFlag.Name):
|
||||||
config.ChainConfig = *bparams.MainnetLightConfig
|
config.ChainConfig = *bparams.MainnetLightConfig
|
||||||
|
|
@ -2127,7 +2101,7 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
|
||||||
gspec = MakeGenesis(ctx)
|
gspec = MakeGenesis(ctx)
|
||||||
chainDb = MakeChainDatabase(ctx, stack, readonly)
|
chainDb = MakeChainDatabase(ctx, stack, readonly)
|
||||||
)
|
)
|
||||||
config, err := core.LoadChainConfig(chainDb, gspec)
|
config, _, err := core.LoadChainConfig(chainDb, gspec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Fatalf("%v", err)
|
Fatalf("%v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,11 +87,7 @@ func TestHistoryImportAndExport(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make temp directory for era files.
|
// Make temp directory for era files.
|
||||||
dir, err := os.MkdirTemp("", "history-export-test")
|
dir := t.TempDir()
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error creating temp test directory: %v", err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
|
|
||||||
// Export history to temp directory.
|
// Export history to temp directory.
|
||||||
if err := ExportHistory(chain, dir, 0, count, step); err != nil {
|
if err := ExportHistory(chain, dir, 0, count, step); err != nil {
|
||||||
|
|
|
||||||
29
cmd/workload/README.md
Normal file
29
cmd/workload/README.md
Normal 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
219
cmd/workload/filtertest.go
Normal 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
|
||||||
|
}
|
||||||
382
cmd/workload/filtertestgen.go
Normal file
382
cmd/workload/filtertestgen.go
Normal 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()
|
||||||
|
}
|
||||||
137
cmd/workload/filtertestperf.go
Normal file
137
cmd/workload/filtertestperf.go
Normal 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
333
cmd/workload/historytest.go
Normal 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
|
||||||
|
}
|
||||||
147
cmd/workload/historytestgen.go
Normal file
147
cmd/workload/historytestgen.go
Normal 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
86
cmd/workload/main.go
Normal 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)
|
||||||
|
}
|
||||||
1
cmd/workload/queries/filter_queries_mainnet.json
Normal file
1
cmd/workload/queries/filter_queries_mainnet.json
Normal file
File diff suppressed because one or more lines are too long
1
cmd/workload/queries/filter_queries_sepolia.json
Normal file
1
cmd/workload/queries/filter_queries_sepolia.json
Normal file
File diff suppressed because one or more lines are too long
1
cmd/workload/queries/history_mainnet.json
Normal file
1
cmd/workload/queries/history_mainnet.json
Normal file
File diff suppressed because one or more lines are too long
1
cmd/workload/queries/history_sepolia.json
Normal file
1
cmd/workload/queries/history_sepolia.json
Normal file
File diff suppressed because one or more lines are too long
174
cmd/workload/testsuite.go
Normal file
174
cmd/workload/testsuite.go
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -154,6 +154,10 @@ type CacheConfig struct {
|
||||||
|
|
||||||
SnapshotNoBuild bool // Whether the background generation is allowed
|
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
|
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.
|
// triedbConfig derives the configures for trie database.
|
||||||
|
|
@ -231,14 +235,15 @@ type BlockChain struct {
|
||||||
statedb *state.CachingDB // State database to reuse between imports (contains state cache)
|
statedb *state.CachingDB // State database to reuse between imports (contains state cache)
|
||||||
txIndexer *txIndexer // Transaction indexer, might be nil if not enabled
|
txIndexer *txIndexer // Transaction indexer, might be nil if not enabled
|
||||||
|
|
||||||
hc *HeaderChain
|
hc *HeaderChain
|
||||||
rmLogsFeed event.Feed
|
rmLogsFeed event.Feed
|
||||||
chainFeed event.Feed
|
chainFeed event.Feed
|
||||||
chainHeadFeed event.Feed
|
chainHeadFeed event.Feed
|
||||||
logsFeed event.Feed
|
logsFeed event.Feed
|
||||||
blockProcFeed event.Feed
|
blockProcFeed event.Feed
|
||||||
scope event.SubscriptionScope
|
blockProcCounter int32
|
||||||
genesisBlock *types.Block
|
scope event.SubscriptionScope
|
||||||
|
genesisBlock *types.Block
|
||||||
|
|
||||||
// This mutex synchronizes chain write operations.
|
// This mutex synchronizes chain write operations.
|
||||||
// Readers don't need to take it, they can just read the database.
|
// Readers don't need to take it, they can just read the database.
|
||||||
|
|
@ -1570,8 +1575,6 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) {
|
||||||
if len(chain) == 0 {
|
if len(chain) == 0 {
|
||||||
return 0, nil
|
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.
|
// Do a sanity check that the provided chain is actually ordered and linked.
|
||||||
for i := 1; i < len(chain); i++ {
|
for i := 1; i < len(chain); i++ {
|
||||||
|
|
@ -1611,6 +1614,16 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
|
||||||
if bc.insertStopped() {
|
if bc.insertStopped() {
|
||||||
return nil, 0, nil
|
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)
|
// 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)
|
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 {
|
func (bc *BlockChain) GetTrieFlushInterval() time.Duration {
|
||||||
return time.Duration(bc.flushInterval.Load())
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
156
core/filtermaps/chain_view.go
Normal file
156
core/filtermaps/chain_view.go
Normal 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]
|
||||||
|
}
|
||||||
63
core/filtermaps/checkpoints.go
Normal file
63
core/filtermaps/checkpoints.go
Normal 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
|
||||||
|
}
|
||||||
20
core/filtermaps/checkpoints_holesky.json
Normal file
20
core/filtermaps/checkpoints_holesky.json
Normal 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}
|
||||||
|
]
|
||||||
|
|
||||||
256
core/filtermaps/checkpoints_mainnet.json
Normal file
256
core/filtermaps/checkpoints_mainnet.json
Normal 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}
|
||||||
|
]
|
||||||
|
|
||||||
63
core/filtermaps/checkpoints_sepolia.json
Normal file
63
core/filtermaps/checkpoints_sepolia.json
Normal 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}
|
||||||
|
]
|
||||||
|
|
||||||
726
core/filtermaps/filtermaps.go
Normal file
726
core/filtermaps/filtermaps.go
Normal 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
395
core/filtermaps/indexer.go
Normal 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
|
||||||
|
}
|
||||||
444
core/filtermaps/indexer_test.go
Normal file
444
core/filtermaps/indexer_test.go
Normal 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()
|
||||||
|
}
|
||||||
768
core/filtermaps/map_renderer.go
Normal file
768
core/filtermaps/map_renderer.go
Normal 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
934
core/filtermaps/matcher.go
Normal 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])
|
||||||
|
}
|
||||||
|
}
|
||||||
206
core/filtermaps/matcher_backend.go
Normal file
206
core/filtermaps/matcher_backend.go
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
87
core/filtermaps/matcher_test.go
Normal file
87
core/filtermaps/matcher_test.go
Normal 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
212
core/filtermaps/math.go
Normal 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] }
|
||||||
149
core/filtermaps/math_test.go
Normal file
149
core/filtermaps/math_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -256,7 +256,7 @@ func (e *GenesisMismatchError) Error() string {
|
||||||
|
|
||||||
// ChainOverrides contains the changes to chain config.
|
// ChainOverrides contains the changes to chain config.
|
||||||
type ChainOverrides struct {
|
type ChainOverrides struct {
|
||||||
OverrideCancun *uint64
|
OverridePrague *uint64
|
||||||
OverrideVerkle *uint64
|
OverrideVerkle *uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -265,8 +265,8 @@ func (o *ChainOverrides) apply(cfg *params.ChainConfig) error {
|
||||||
if o == nil || cfg == nil {
|
if o == nil || cfg == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if o.OverrideCancun != nil {
|
if o.OverridePrague != nil {
|
||||||
cfg.CancunTime = o.OverrideCancun
|
cfg.PragueTime = o.OverridePrague
|
||||||
}
|
}
|
||||||
if o.OverrideVerkle != nil {
|
if o.OverrideVerkle != nil {
|
||||||
cfg.VerkleTime = o.OverrideVerkle
|
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
|
// LoadChainConfig loads the stored chain config if it is already present in
|
||||||
// database, otherwise, return the config in the provided genesis specification.
|
// 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
|
// Load the stored chain config from the database. It can be nil
|
||||||
// in case the database is empty. Notably, we only care about the
|
// in case the database is empty. Notably, we only care about the
|
||||||
// chain config corresponds to the canonical chain.
|
// chain config corresponds to the canonical chain.
|
||||||
|
|
@ -394,27 +394,28 @@ func LoadChainConfig(db ethdb.Database, genesis *Genesis) (*params.ChainConfig,
|
||||||
if stored != (common.Hash{}) {
|
if stored != (common.Hash{}) {
|
||||||
storedcfg := rawdb.ReadChainConfig(db, stored)
|
storedcfg := rawdb.ReadChainConfig(db, stored)
|
||||||
if storedcfg != nil {
|
if storedcfg != nil {
|
||||||
return storedcfg, nil
|
return storedcfg, stored, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Load the config from the provided genesis specification
|
// Load the config from the provided genesis specification
|
||||||
if genesis != nil {
|
if genesis != nil {
|
||||||
// Reject invalid genesis spec without valid chain config
|
// Reject invalid genesis spec without valid chain config
|
||||||
if genesis.Config == nil {
|
if genesis.Config == nil {
|
||||||
return nil, errGenesisNoConfig
|
return nil, common.Hash{}, errGenesisNoConfig
|
||||||
}
|
}
|
||||||
// If the canonical genesis header is present, but the chain
|
// If the canonical genesis header is present, but the chain
|
||||||
// config is missing(initialize the empty leveldb with an
|
// config is missing(initialize the empty leveldb with an
|
||||||
// external ancient chain segment), ensure the provided genesis
|
// external ancient chain segment), ensure the provided genesis
|
||||||
// is matched.
|
// is matched.
|
||||||
if stored != (common.Hash{}) && genesis.ToBlock().Hash() != stored {
|
ghash := genesis.ToBlock().Hash()
|
||||||
return nil, &GenesisMismatchError{stored, 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,
|
// There is no stored chain config and no new config provided,
|
||||||
// In this case the default chain config(mainnet) will be used
|
// 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
|
// 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),
|
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||||
Difficulty: big.NewInt(0),
|
Difficulty: big.NewInt(0),
|
||||||
Alloc: map[common.Address]types.Account{
|
Alloc: map[common.Address]types.Account{
|
||||||
common.BytesToAddress([]byte{1}): {Balance: big.NewInt(1)}, // ECRecover
|
common.BytesToAddress([]byte{0x01}): {Balance: big.NewInt(1)}, // ECRecover
|
||||||
common.BytesToAddress([]byte{2}): {Balance: big.NewInt(1)}, // SHA256
|
common.BytesToAddress([]byte{0x02}): {Balance: big.NewInt(1)}, // SHA256
|
||||||
common.BytesToAddress([]byte{3}): {Balance: big.NewInt(1)}, // RIPEMD
|
common.BytesToAddress([]byte{0x03}): {Balance: big.NewInt(1)}, // RIPEMD
|
||||||
common.BytesToAddress([]byte{4}): {Balance: big.NewInt(1)}, // Identity
|
common.BytesToAddress([]byte{0x04}): {Balance: big.NewInt(1)}, // Identity
|
||||||
common.BytesToAddress([]byte{5}): {Balance: big.NewInt(1)}, // ModExp
|
common.BytesToAddress([]byte{0x05}): {Balance: big.NewInt(1)}, // ModExp
|
||||||
common.BytesToAddress([]byte{6}): {Balance: big.NewInt(1)}, // ECAdd
|
common.BytesToAddress([]byte{0x06}): {Balance: big.NewInt(1)}, // ECAdd
|
||||||
common.BytesToAddress([]byte{7}): {Balance: big.NewInt(1)}, // ECScalarMul
|
common.BytesToAddress([]byte{0x07}): {Balance: big.NewInt(1)}, // ECScalarMul
|
||||||
common.BytesToAddress([]byte{8}): {Balance: big.NewInt(1)}, // ECPairing
|
common.BytesToAddress([]byte{0x08}): {Balance: big.NewInt(1)}, // ECPairing
|
||||||
common.BytesToAddress([]byte{9}): {Balance: big.NewInt(1)}, // BLAKE2b
|
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
|
// Pre-deploy system contracts
|
||||||
params.BeaconRootsAddress: {Nonce: 1, Code: params.BeaconRootsCode, Balance: common.Big0},
|
params.BeaconRootsAddress: {Nonce: 1, Code: params.BeaconRootsCode, Balance: common.Big0},
|
||||||
params.HistoryStorageAddress: {Nonce: 1, Code: params.HistoryStorageCode, Balance: common.Big0},
|
params.HistoryStorageAddress: {Nonce: 1, Code: params.HistoryStorageCode, Balance: common.Big0},
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ package rawdb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"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())
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,11 @@ import (
|
||||||
// ReadPreimage retrieves a single preimage of the provided hash.
|
// ReadPreimage retrieves a single preimage of the provided hash.
|
||||||
func ReadPreimage(db ethdb.KeyValueReader, hash common.Hash) []byte {
|
func ReadPreimage(db ethdb.KeyValueReader, hash common.Hash) []byte {
|
||||||
data, _ := db.Get(preimageKey(hash))
|
data, _ := db.Get(preimageKey(hash))
|
||||||
|
if len(data) == 0 {
|
||||||
|
preimageMissCounter.Inc(1)
|
||||||
|
} else {
|
||||||
|
preimageHitsCounter.Inc(1)
|
||||||
|
}
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,7 +43,6 @@ func WritePreimages(db ethdb.KeyValueWriter, preimages map[common.Hash][]byte) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
preimageCounter.Inc(int64(len(preimages)))
|
preimageCounter.Inc(int64(len(preimages)))
|
||||||
preimageHitCounter.Inc(int64(len(preimages)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadCode retrieves the contract code of the provided code hash.
|
// ReadCode retrieves the contract code of the provided code hash.
|
||||||
|
|
|
||||||
|
|
@ -376,6 +376,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||||
storageSnaps stat
|
storageSnaps stat
|
||||||
preimages stat
|
preimages stat
|
||||||
bloomBits stat
|
bloomBits stat
|
||||||
|
filterMaps stat
|
||||||
beaconHeaders stat
|
beaconHeaders stat
|
||||||
cliqueSnaps stat
|
cliqueSnaps stat
|
||||||
|
|
||||||
|
|
@ -440,6 +441,8 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||||
bloomBits.Add(size)
|
bloomBits.Add(size)
|
||||||
case bytes.HasPrefix(key, BloomBitsIndexPrefix):
|
case bytes.HasPrefix(key, BloomBitsIndexPrefix):
|
||||||
bloomBits.Add(size)
|
bloomBits.Add(size)
|
||||||
|
case bytes.HasPrefix(key, []byte(FilterMapsPrefix)):
|
||||||
|
filterMaps.Add(size)
|
||||||
case bytes.HasPrefix(key, skeletonHeaderPrefix) && len(key) == (len(skeletonHeaderPrefix)+8):
|
case bytes.HasPrefix(key, skeletonHeaderPrefix) && len(key) == (len(skeletonHeaderPrefix)+8):
|
||||||
beaconHeaders.Add(size)
|
beaconHeaders.Add(size)
|
||||||
case bytes.HasPrefix(key, CliqueSnapshotPrefix) && len(key) == 7+common.HashLength:
|
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", "Block hash->number", hashNumPairings.Size(), hashNumPairings.Count()},
|
||||||
{"Key-Value store", "Transaction index", txLookups.Size(), txLookups.Count()},
|
{"Key-Value store", "Transaction index", txLookups.Size(), txLookups.Count()},
|
||||||
{"Key-Value store", "Bloombit index", bloomBits.Size(), bloomBits.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", "Contract codes", codes.Size(), codes.Count()},
|
||||||
{"Key-Value store", "Hash trie nodes", legacyTries.Size(), legacyTries.Count()},
|
{"Key-Value store", "Hash trie nodes", legacyTries.Size(), legacyTries.Count()},
|
||||||
{"Key-Value store", "Path trie state lookups", stateLookups.Size(), stateLookups.Count()},
|
{"Key-Value store", "Path trie state lookups", stateLookups.Size(), stateLookups.Count()},
|
||||||
|
|
|
||||||
|
|
@ -145,8 +145,15 @@ var (
|
||||||
FixedCommitteeRootKey = []byte("fixedRoot-") // bigEndian64(syncPeriod) -> committee root hash
|
FixedCommitteeRootKey = []byte("fixedRoot-") // bigEndian64(syncPeriod) -> committee root hash
|
||||||
SyncCommitteeKey = []byte("committee-") // bigEndian64(syncPeriod) -> serialized committee
|
SyncCommitteeKey = []byte("committee-") // bigEndian64(syncPeriod) -> serialized committee
|
||||||
|
|
||||||
preimageCounter = metrics.NewRegisteredCounter("db/preimage/total", nil)
|
FilterMapsPrefix = "fm-"
|
||||||
preimageHitCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil)
|
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)
|
||||||
|
preimageHitsCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil)
|
||||||
|
preimageMissCounter = metrics.NewRegisteredCounter("db/preimage/miss", nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
// LegacyTxLookupEntry is the legacy TxLookupEntry definition with some unnecessary
|
// LegacyTxLookupEntry is the legacy TxLookupEntry definition with some unnecessary
|
||||||
|
|
@ -341,3 +348,34 @@ func IsStorageTrieNode(key []byte) bool {
|
||||||
ok, _, _ := ResolveStorageTrieNode(key)
|
ok, _, _ := ResolveStorageTrieNode(key)
|
||||||
return ok
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ package state
|
||||||
import "github.com/ethereum/go-ethereum/metrics"
|
import "github.com/ethereum/go-ethereum/metrics"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
accountReadMeters = metrics.NewRegisteredMeter("state/read/accounts", nil)
|
accountReadMeters = metrics.NewRegisteredMeter("state/read/account", nil)
|
||||||
storageReadMeters = metrics.NewRegisteredMeter("state/read/storage", nil)
|
storageReadMeters = metrics.NewRegisteredMeter("state/read/storage", nil)
|
||||||
accountUpdatedMeter = metrics.NewRegisteredMeter("state/update/account", nil)
|
accountUpdatedMeter = metrics.NewRegisteredMeter("state/update/account", nil)
|
||||||
storageUpdatedMeter = metrics.NewRegisteredMeter("state/update/storage", nil)
|
storageUpdatedMeter = metrics.NewRegisteredMeter("state/update/storage", nil)
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package snapshot
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"maps"
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
@ -30,7 +31,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
bloomfilter "github.com/holiman/bloomfilter/v2"
|
bloomfilter "github.com/holiman/bloomfilter/v2"
|
||||||
"golang.org/x/exp/maps"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -431,8 +431,7 @@ func (dl *diffLayer) AccountList() []common.Hash {
|
||||||
dl.lock.Lock()
|
dl.lock.Lock()
|
||||||
defer dl.lock.Unlock()
|
defer dl.lock.Unlock()
|
||||||
|
|
||||||
dl.accountList = maps.Keys(dl.accountData)
|
dl.accountList = slices.SortedFunc(maps.Keys(dl.accountData), common.Hash.Cmp)
|
||||||
slices.SortFunc(dl.accountList, common.Hash.Cmp)
|
|
||||||
dl.memory += uint64(len(dl.accountList) * common.HashLength)
|
dl.memory += uint64(len(dl.accountList) * common.HashLength)
|
||||||
return dl.accountList
|
return dl.accountList
|
||||||
}
|
}
|
||||||
|
|
@ -464,8 +463,7 @@ func (dl *diffLayer) StorageList(accountHash common.Hash) []common.Hash {
|
||||||
dl.lock.Lock()
|
dl.lock.Lock()
|
||||||
defer dl.lock.Unlock()
|
defer dl.lock.Unlock()
|
||||||
|
|
||||||
storageList := maps.Keys(dl.storageData[accountHash])
|
storageList := slices.SortedFunc(maps.Keys(dl.storageData[accountHash]), common.Hash.Cmp)
|
||||||
slices.SortFunc(storageList, common.Hash.Cmp)
|
|
||||||
dl.storageList[accountHash] = storageList
|
dl.storageList[accountHash] = storageList
|
||||||
dl.memory += uint64(len(dl.storageList)*common.HashLength + common.HashLength)
|
dl.memory += uint64(len(dl.storageList)*common.HashLength + common.HashLength)
|
||||||
return storageList
|
return storageList
|
||||||
|
|
|
||||||
|
|
@ -83,8 +83,8 @@ func TestHooks(t *testing.T) {
|
||||||
inner.SetTxContext(common.Hash{0x11}, 100) // For the log
|
inner.SetTxContext(common.Hash{0x11}, 100) // For the log
|
||||||
var result []string
|
var result []string
|
||||||
var wants = []string{
|
var wants = []string{
|
||||||
"0xaa00000000000000000000000000000000000000.balance: 0->100 (BalanceChangeUnspecified)",
|
"0xaa00000000000000000000000000000000000000.balance: 0->100 (Unspecified)",
|
||||||
"0xaa00000000000000000000000000000000000000.balance: 100->50 (BalanceChangeTransfer)",
|
"0xaa00000000000000000000000000000000000000.balance: 100->50 (Transfer)",
|
||||||
"0xaa00000000000000000000000000000000000000.nonce: 0->1337",
|
"0xaa00000000000000000000000000000000000000.nonce: 0->1337",
|
||||||
"0xaa00000000000000000000000000000000000000.code: (0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470) ->0x1325 (0xa12ae05590de0c93a00bc7ac773c2fdb621e44f814985e72194f921c0050f728)",
|
"0xaa00000000000000000000000000000000000000.code: (0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470) ->0x1325 (0xa12ae05590de0c93a00bc7ac773c2fdb621e44f814985e72194f921c0050f728)",
|
||||||
"0xaa00000000000000000000000000000000000000.storage slot 0x0000000000000000000000000000000000000000000000000000000000000001: 0x0000000000000000000000000000000000000000000000000000000000000000 ->0x0000000000000000000000000000000000000000000000000000000000000011",
|
"0xaa00000000000000000000000000000000000000.storage slot 0x0000000000000000000000000000000000000000000000000000000000000001: 0x0000000000000000000000000000000000000000000000000000000000000000 ->0x0000000000000000000000000000000000000000000000000000000000000011",
|
||||||
|
|
|
||||||
|
|
@ -305,12 +305,14 @@ func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte
|
||||||
*requests = append(*requests, requestsData)
|
*requests = append(*requests, requestsData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var depositTopic = common.HexToHash("0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5")
|
||||||
|
|
||||||
// ParseDepositLogs extracts the EIP-6110 deposit values from logs emitted by
|
// ParseDepositLogs extracts the EIP-6110 deposit values from logs emitted by
|
||||||
// BeaconDepositContract.
|
// BeaconDepositContract.
|
||||||
func ParseDepositLogs(requests *[][]byte, logs []*types.Log, config *params.ChainConfig) error {
|
func ParseDepositLogs(requests *[][]byte, logs []*types.Log, config *params.ChainConfig) error {
|
||||||
deposits := make([]byte, 1) // note: first byte is 0x00 (== deposit request type)
|
deposits := make([]byte, 1) // note: first byte is 0x00 (== deposit request type)
|
||||||
for _, log := range logs {
|
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)
|
request, err := types.DepositLogToRequest(log.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to parse deposit data: %v", err)
|
return fmt.Errorf("unable to parse deposit data: %v", err)
|
||||||
|
|
|
||||||
|
|
@ -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
|
package tracing
|
||||||
|
|
||||||
|
|
@ -26,9 +26,9 @@ func _() {
|
||||||
_ = x[BalanceChangeRevert-15]
|
_ = 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 {
|
func (i BalanceChangeReason) String() string {
|
||||||
if i >= BalanceChangeReason(len(_BalanceChangeReason_index)-1) {
|
if i >= BalanceChangeReason(len(_BalanceChangeReason_index)-1) {
|
||||||
|
|
|
||||||
52
core/tracing/gen_gas_change_reason_stringer.go
Normal file
52
core/tracing/gen_gas_change_reason_stringer.go
Normal 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) + ")"
|
||||||
|
}
|
||||||
|
}
|
||||||
29
core/tracing/gen_nonce_change_reason_stringer.go
Normal file
29
core/tracing/gen_nonce_change_reason_stringer.go
Normal 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]]
|
||||||
|
}
|
||||||
|
|
@ -223,7 +223,7 @@ type Hooks struct {
|
||||||
// for tracing and reporting.
|
// for tracing and reporting.
|
||||||
type BalanceChangeReason byte
|
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 (
|
const (
|
||||||
BalanceChangeUnspecified BalanceChangeReason = 0
|
BalanceChangeUnspecified BalanceChangeReason = 0
|
||||||
|
|
@ -284,6 +284,8 @@ const (
|
||||||
// once per transaction, while those that start with `GasChangeCall` are emitted on a call basis.
|
// once per transaction, while those that start with `GasChangeCall` are emitted on a call basis.
|
||||||
type GasChangeReason byte
|
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 (
|
const (
|
||||||
GasChangeUnspecified GasChangeReason = 0
|
GasChangeUnspecified GasChangeReason = 0
|
||||||
|
|
||||||
|
|
@ -348,6 +350,8 @@ const (
|
||||||
// NonceChangeReason is used to indicate the reason for a nonce change.
|
// NonceChangeReason is used to indicate the reason for a nonce change.
|
||||||
type NonceChangeReason byte
|
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 (
|
const (
|
||||||
NonceChangeUnspecified NonceChangeReason = 0
|
NonceChangeUnspecified NonceChangeReason = 0
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1085,19 +1085,24 @@ func (p *BlobPool) SetGasTip(tip *big.Int) {
|
||||||
p.updateStorageMetrics()
|
p.updateStorageMetrics()
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateTx checks whether a transaction is valid according to the consensus
|
// ValidateTxBasics checks whether a transaction is valid according to the consensus
|
||||||
// rules and adheres to some heuristic limits of the local node (price and size).
|
// rules, but does not check state-dependent validation such as sufficient balance.
|
||||||
func (p *BlobPool) validateTx(tx *types.Transaction) error {
|
// This check is meant as an early check which only needs to be performed once,
|
||||||
// Ensure the transaction adheres to basic pool filters (type, size, tip) and
|
// and does not require the pool mutex to be held.
|
||||||
// consensus rules
|
func (p *BlobPool) ValidateTxBasics(tx *types.Transaction) error {
|
||||||
baseOpts := &txpool.ValidationOptions{
|
opts := &txpool.ValidationOptions{
|
||||||
Config: p.chain.Config(),
|
Config: p.chain.Config(),
|
||||||
Accept: 1 << types.BlobTxType,
|
Accept: 1 << types.BlobTxType,
|
||||||
MaxSize: txMaxSize,
|
MaxSize: txMaxSize,
|
||||||
MinTip: p.gasTip.ToBig(),
|
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
|
return err
|
||||||
}
|
}
|
||||||
// Ensure the transaction adheres to the stateful pool filters (nonce, balance)
|
// Ensure the transaction adheres to the stateful pool filters (nonce, balance)
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -452,8 +453,7 @@ func TestOpenDrops(t *testing.T) {
|
||||||
//log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
|
//log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
|
||||||
|
|
||||||
// Create a temporary folder for the persistent backend
|
// Create a temporary folder for the persistent backend
|
||||||
storage, _ := os.MkdirTemp("", "blobpool-")
|
storage := t.TempDir()
|
||||||
defer os.RemoveAll(storage)
|
|
||||||
|
|
||||||
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
|
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
|
||||||
store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(testMaxBlobsPerBlock), nil)
|
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)))
|
//log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
|
||||||
|
|
||||||
// Create a temporary folder for the persistent backend
|
// Create a temporary folder for the persistent backend
|
||||||
storage, _ := os.MkdirTemp("", "blobpool-")
|
storage := t.TempDir()
|
||||||
defer os.RemoveAll(storage)
|
|
||||||
|
|
||||||
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
|
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
|
||||||
store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(testMaxBlobsPerBlock), nil)
|
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)))
|
//log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
|
||||||
|
|
||||||
// Create a temporary folder for the persistent backend
|
// Create a temporary folder for the persistent backend
|
||||||
storage, _ := os.MkdirTemp("", "blobpool-")
|
storage := t.TempDir()
|
||||||
defer os.RemoveAll(storage)
|
|
||||||
|
|
||||||
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
|
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
|
||||||
store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(testMaxBlobsPerBlock), nil)
|
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)))
|
//log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
|
||||||
|
|
||||||
// Create a temporary folder for the persistent backend
|
// Create a temporary folder for the persistent backend
|
||||||
storage, _ := os.MkdirTemp("", "blobpool-")
|
storage := t.TempDir()
|
||||||
defer os.RemoveAll(storage)
|
|
||||||
|
|
||||||
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
|
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
|
||||||
store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(testMaxBlobsPerBlock), nil)
|
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)))
|
//log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
|
||||||
|
|
||||||
// Create a temporary folder for the persistent backend
|
// Create a temporary folder for the persistent backend
|
||||||
storage, _ := os.MkdirTemp("", "blobpool-")
|
storage := t.TempDir()
|
||||||
defer os.RemoveAll(storage)
|
|
||||||
|
|
||||||
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
|
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
|
||||||
store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(6), nil)
|
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 {
|
for i, tt := range tests {
|
||||||
// Create a temporary folder for the persistent backend
|
// Create a temporary folder for the persistent backend
|
||||||
storage, _ := os.MkdirTemp("", "blobpool-")
|
storage := filepath.Join(t.TempDir(), fmt.Sprintf("test-%d", i))
|
||||||
defer os.RemoveAll(storage) // late defer, still ok
|
|
||||||
|
|
||||||
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
|
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
|
||||||
store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(testMaxBlobsPerBlock), nil)
|
store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(testMaxBlobsPerBlock), nil)
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,12 @@ package blobpool
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"container/heap"
|
"container/heap"
|
||||||
|
"maps"
|
||||||
"math"
|
"math"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
"golang.org/x/exp/maps"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// evictHeap is a helper data structure to keep track of the cheapest bottleneck
|
// 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,
|
// Populate the heap in account sort order. Not really needed in practice,
|
||||||
// but it makes the heap initialization deterministic and less annoying to
|
// but it makes the heap initialization deterministic and less annoying to
|
||||||
// test in unit tests.
|
// test in unit tests.
|
||||||
heap.addrs = maps.Keys(index)
|
heap.addrs = slices.SortedFunc(maps.Keys(index), common.Address.Cmp)
|
||||||
slices.SortFunc(heap.addrs, common.Address.Cmp)
|
|
||||||
for i, addr := range heap.addrs {
|
for i, addr := range heap.addrs {
|
||||||
heap.index[addr] = i
|
heap.index[addr] = i
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,22 +51,9 @@ var (
|
||||||
// making the transaction invalid, rather a DOS protection.
|
// making the transaction invalid, rather a DOS protection.
|
||||||
ErrOversizedData = errors.New("oversized data")
|
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
|
// 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
|
// 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
|
// input transaction of non-blob type when a blob transaction from this sender
|
||||||
// remains pending (and vice-versa).
|
// remains pending (and vice-versa).
|
||||||
ErrAlreadyReserved = errors.New("address already reserved")
|
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")
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package legacypool
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"maps"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
@ -40,7 +41,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
"golang.org/x/exp/maps"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -61,6 +61,19 @@ var (
|
||||||
// ErrTxPoolOverflow is returned if the transaction pool is full and can't accept
|
// ErrTxPoolOverflow is returned if the transaction pool is full and can't accept
|
||||||
// another remote transaction.
|
// another remote transaction.
|
||||||
ErrTxPoolOverflow = errors.New("txpool is full")
|
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 (
|
var (
|
||||||
|
|
@ -545,11 +558,11 @@ func (pool *LegacyPool) Pending(filter txpool.PendingFilter) map[common.Address]
|
||||||
return pending
|
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.
|
// 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,
|
// 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.
|
// 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{
|
opts := &txpool.ValidationOptions{
|
||||||
Config: pool.chainconfig,
|
Config: pool.chainconfig,
|
||||||
Accept: 0 |
|
Accept: 0 |
|
||||||
|
|
@ -560,10 +573,7 @@ func (pool *LegacyPool) validateTxBasics(tx *types.Transaction) error {
|
||||||
MaxSize: txMaxSize,
|
MaxSize: txMaxSize,
|
||||||
MinTip: pool.gasTip.Load().ToBig(),
|
MinTip: pool.gasTip.Load().ToBig(),
|
||||||
}
|
}
|
||||||
if err := txpool.ValidateTransaction(tx, pool.currentHead.Load(), pool.signer, opts); err != nil {
|
return txpool.ValidateTransaction(tx, pool.currentHead.Load(), pool.signer, opts)
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateTx checks whether a transaction is valid according to the consensus
|
// validateTx checks whether a transaction is valid according to the consensus
|
||||||
|
|
@ -572,22 +582,8 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction) error {
|
||||||
opts := &txpool.ValidationOptionsWithState{
|
opts := &txpool.ValidationOptionsWithState{
|
||||||
State: pool.currentState,
|
State: pool.currentState,
|
||||||
|
|
||||||
FirstNonceGap: nil, // Pool allows arbitrary arrival order, don't invalidate nonce gaps
|
FirstNonceGap: nil, // Pool allows arbitrary arrival order, don't invalidate nonce gaps
|
||||||
UsedAndLeftSlots: func(addr common.Address) (int, int) {
|
UsedAndLeftSlots: nil, // Pool has own mechanism to limit the number of transactions
|
||||||
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
|
|
||||||
},
|
|
||||||
ExistingExpenditure: func(addr common.Address) *big.Int {
|
ExistingExpenditure: func(addr common.Address) *big.Int {
|
||||||
if list := pool.pending[addr]; list != nil {
|
if list := pool.pending[addr]; list != nil {
|
||||||
return list.totalcost.ToBig()
|
return list.totalcost.ToBig()
|
||||||
|
|
@ -602,22 +598,49 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction) error {
|
||||||
}
|
}
|
||||||
return nil
|
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 {
|
if err := txpool.ValidateTransactionWithState(tx, pool.signer, opts); err != nil {
|
||||||
return err
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -709,7 +732,7 @@ func (pool *LegacyPool) add(tx *types.Transaction) (replaced bool, err error) {
|
||||||
pool.priced.Put(dropTx)
|
pool.priced.Put(dropTx)
|
||||||
}
|
}
|
||||||
log.Trace("Discarding future transaction replacing pending tx", "hash", hash)
|
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
|
// Exclude transactions with basic errors, e.g invalid signatures and
|
||||||
// insufficient intrinsic gas as soon as possible and cache senders
|
// insufficient intrinsic gas as soon as possible and cache senders
|
||||||
// in transactions before obtaining lock
|
// in transactions before obtaining lock
|
||||||
if err := pool.validateTxBasics(tx); err != nil {
|
if err := pool.ValidateTxBasics(tx); err != nil {
|
||||||
errs[i] = err
|
errs[i] = err
|
||||||
log.Trace("Discarding invalid transaction", "hash", tx.Hash(), "err", err)
|
log.Trace("Discarding invalid transaction", "hash", tx.Hash(), "err", err)
|
||||||
invalidTxMeter.Mark(1)
|
invalidTxMeter.Mark(1)
|
||||||
|
|
@ -1581,7 +1604,7 @@ func (pool *LegacyPool) demoteUnexecutables() {
|
||||||
gapped := list.Cap(0)
|
gapped := list.Cap(0)
|
||||||
for _, tx := range gapped {
|
for _, tx := range gapped {
|
||||||
hash := tx.Hash()
|
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.
|
// Internal shuffle shouldn't touch the lookup set.
|
||||||
pool.enqueueTx(hash, tx, false)
|
pool.enqueueTx(hash, tx, false)
|
||||||
|
|
@ -1648,7 +1671,7 @@ func (as *accountSet) addTx(tx *types.Transaction) {
|
||||||
// reuse. The returned slice should not be changed!
|
// reuse. The returned slice should not be changed!
|
||||||
func (as *accountSet) flatten() []common.Address {
|
func (as *accountSet) flatten() []common.Address {
|
||||||
if as.cache == nil {
|
if as.cache == nil {
|
||||||
as.cache = maps.Keys(as.accounts)
|
as.cache = slices.Collect(maps.Keys(as.accounts))
|
||||||
}
|
}
|
||||||
return as.cache
|
return as.cache
|
||||||
}
|
}
|
||||||
|
|
@ -1739,12 +1762,12 @@ func (t *lookup) Remove(hash common.Hash) {
|
||||||
t.lock.Lock()
|
t.lock.Lock()
|
||||||
defer t.lock.Unlock()
|
defer t.lock.Unlock()
|
||||||
|
|
||||||
t.removeAuthorities(hash)
|
|
||||||
tx, ok := t.txs[hash]
|
tx, ok := t.txs[hash]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Error("No transaction found to be deleted", "hash", hash)
|
log.Error("No transaction found to be deleted", "hash", hash)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
t.removeAuthorities(tx)
|
||||||
t.slots -= numSlots(tx)
|
t.slots -= numSlots(tx)
|
||||||
slotsGauge.Update(int64(t.slots))
|
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
|
// removeAuthorities stops tracking the supplied tx in relation to its
|
||||||
// authorities.
|
// authorities.
|
||||||
func (t *lookup) removeAuthorities(hash common.Hash) {
|
func (t *lookup) removeAuthorities(tx *types.Transaction) {
|
||||||
for addr := range t.auths {
|
hash := tx.Hash()
|
||||||
|
for _, addr := range tx.SetCodeAuthorities() {
|
||||||
list := t.auths[addr]
|
list := t.auths[addr]
|
||||||
// Remove tx from tracker.
|
// Remove tx from tracker.
|
||||||
if i := slices.Index(list, hash); i >= 0 {
|
if i := slices.Index(list, hash); i >= 0 {
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -238,6 +239,23 @@ func validatePoolInternals(pool *LegacyPool) error {
|
||||||
return fmt.Errorf("pending nonce mismatch: have %v, want %v", nonce, last+1)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1645,8 +1663,8 @@ func TestUnderpricing(t *testing.T) {
|
||||||
t.Fatalf("failed to add well priced transaction: %v", err)
|
t.Fatalf("failed to add well priced transaction: %v", err)
|
||||||
}
|
}
|
||||||
// Ensure that replacing a pending transaction with a future transaction fails
|
// 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 {
|
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, txpool.ErrFutureReplacePending)
|
t.Fatalf("adding future replace transaction error mismatch: have %v, want %v", err, ErrFutureReplacePending)
|
||||||
}
|
}
|
||||||
pending, queued = pool.Stats()
|
pending, queued = pool.Stats()
|
||||||
if pending != 4 {
|
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 {
|
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), keyA)); err != nil {
|
||||||
t.Fatalf("%s: failed to add remote transaction: %v", name, err)
|
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) {
|
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, txpool.ErrAccountLimitExceeded, err)
|
t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err)
|
||||||
}
|
}
|
||||||
// Also check gapped transaction.
|
// Also check gapped transaction.
|
||||||
if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1), keyA)); !errors.Is(err, txpool.ErrAccountLimitExceeded) {
|
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, txpool.ErrAccountLimitExceeded, err)
|
t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err)
|
||||||
}
|
}
|
||||||
// Replace by fee.
|
// Replace by fee.
|
||||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(10), keyA)); err != nil {
|
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)
|
t.Fatalf("%s: failed to add with pending delegatio: %v", name, err)
|
||||||
}
|
}
|
||||||
// Also check gapped transaction is rejected.
|
// Also check gapped transaction is rejected.
|
||||||
if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyC)); !errors.Is(err, txpool.ErrAccountLimitExceeded) {
|
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, txpool.ErrAccountLimitExceeded, err)
|
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 {
|
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)
|
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)
|
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 {
|
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)
|
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)
|
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)
|
tt.run(tt.name)
|
||||||
pending, queued := pool.Stats()
|
pending, queued := pool.Stats()
|
||||||
|
|
@ -2440,8 +2484,8 @@ func TestSetCodeTransactionsReorg(t *testing.T) {
|
||||||
t.Fatalf("failed to add with remote setcode transaction: %v", err)
|
t.Fatalf("failed to add with remote setcode transaction: %v", err)
|
||||||
}
|
}
|
||||||
// Try to add a transactions in
|
// Try to add a transactions in
|
||||||
if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1000), keyA)); !errors.Is(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, txpool.ErrAccountLimitExceeded)
|
t.Fatalf("unexpected error %v, expecting %v", err, ErrInflightTxLimitReached)
|
||||||
}
|
}
|
||||||
// Simulate the chain moving
|
// Simulate the chain moving
|
||||||
blockchain.statedb.SetNonce(addrA, 2, tracing.NonceChangeAuthorization)
|
blockchain.statedb.SetNonce(addrA, 2, tracing.NonceChangeAuthorization)
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
package locals
|
package locals
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -28,7 +29,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -74,28 +74,45 @@ func New(journalPath string, journalTime time.Duration, chainConfig *params.Chai
|
||||||
|
|
||||||
// Track adds a transaction to the tracked set.
|
// Track adds a transaction to the tracked set.
|
||||||
// Note: blob-type transactions are ignored.
|
// Note: blob-type transactions are ignored.
|
||||||
func (tracker *TxTracker) Track(tx *types.Transaction) {
|
func (tracker *TxTracker) Track(tx *types.Transaction) error {
|
||||||
tracker.TrackAll([]*types.Transaction{tx})
|
return tracker.TrackAll([]*types.Transaction{tx})[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrackAll adds a list of transactions to the tracked set.
|
// TrackAll adds a list of transactions to the tracked set.
|
||||||
// Note: blob-type transactions are ignored.
|
// Note: blob-type transactions are ignored.
|
||||||
func (tracker *TxTracker) TrackAll(txs []*types.Transaction) {
|
func (tracker *TxTracker) TrackAll(txs []*types.Transaction) []error {
|
||||||
tracker.mu.Lock()
|
tracker.mu.Lock()
|
||||||
defer tracker.mu.Unlock()
|
defer tracker.mu.Unlock()
|
||||||
|
|
||||||
|
var errors []error
|
||||||
for _, tx := range txs {
|
for _, tx := range txs {
|
||||||
if tx.Type() == types.BlobTxType {
|
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
|
continue
|
||||||
}
|
}
|
||||||
// If we're already tracking it, it's a no-op
|
// If we're already tracking it, it's a no-op
|
||||||
if _, ok := tracker.all[tx.Hash()]; ok {
|
if _, ok := tracker.all[tx.Hash()]; ok {
|
||||||
|
errors = append(errors, nil)
|
||||||
continue
|
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)
|
addr, err := types.Sender(tracker.signer, tx)
|
||||||
if err != nil { // Ignore this tx
|
if err != nil {
|
||||||
|
errors = append(errors, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
errors = append(errors, nil)
|
||||||
|
|
||||||
tracker.all[tx.Hash()] = tx
|
tracker.all[tx.Hash()] = tx
|
||||||
if tracker.byAddr[addr] == nil {
|
if tracker.byAddr[addr] == nil {
|
||||||
tracker.byAddr[addr] = legacypool.NewSortedMap()
|
tracker.byAddr[addr] = legacypool.NewSortedMap()
|
||||||
|
|
@ -107,6 +124,7 @@ func (tracker *TxTracker) TrackAll(txs []*types.Transaction) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
localGauge.Update(int64(len(tracker.all)))
|
localGauge.Update(int64(len(tracker.all)))
|
||||||
|
return errors
|
||||||
}
|
}
|
||||||
|
|
||||||
// recheck checks and returns any transactions that needs to be resubmitted.
|
// recheck checks and returns any transactions that needs to be resubmitted.
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,12 @@ type SubPool interface {
|
||||||
// retrieve blobs from the pools directly instead of the network.
|
// retrieve blobs from the pools directly instead of the network.
|
||||||
GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Proof)
|
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
|
// 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 the large transaction churn, add may postpone fully integrating the tx
|
||||||
// to a later point to batch multiple ones together.
|
// to a later point to batch multiple ones together.
|
||||||
|
|
|
||||||
|
|
@ -325,6 +325,17 @@ func (p *TxPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Pr
|
||||||
return nil, nil
|
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
|
// 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 the large transaction churn, add may postpone fully integrating the tx
|
||||||
// to a later point to batch multiple ones together.
|
// to a later point to batch multiple ones together.
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
package txpool
|
package txpool
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
@ -170,20 +169,11 @@ func validateBlobSidecar(hashes []common.Hash, sidecar *types.BlobTxSidecar) err
|
||||||
if len(sidecar.Blobs) != len(hashes) {
|
if len(sidecar.Blobs) != len(hashes) {
|
||||||
return fmt.Errorf("invalid number of %d blobs compared to %d blob hashes", 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) {
|
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))
|
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
|
if err := sidecar.ValidateBlobCommitmentHashes(hashes); err != nil {
|
||||||
// transaction hash before getting to the cryptography
|
return err
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Blob commitments match with the hashes in the transaction, verify the
|
// Blob commitments match with the hashes in the transaction, verify the
|
||||||
// blobs themselves via KZG
|
// blobs themselves via KZG
|
||||||
|
|
@ -206,7 +196,7 @@ type ValidationOptionsWithState struct {
|
||||||
// nonce gaps will be ignored and permitted.
|
// nonce gaps will be ignored and permitted.
|
||||||
FirstNonceGap func(addr common.Address) uint64
|
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
|
// used and the number still permitted for an account. New transactions will
|
||||||
// be rejected once the number of remaining slots reaches zero.
|
// be rejected once the number of remaining slots reaches zero.
|
||||||
UsedAndLeftSlots func(addr common.Address) (int, int)
|
UsedAndLeftSlots func(addr common.Address) (int, int)
|
||||||
|
|
@ -218,11 +208,6 @@ type ValidationOptionsWithState struct {
|
||||||
// ExistingCost is a mandatory callback to retrieve an already pooled
|
// ExistingCost is a mandatory callback to retrieve an already pooled
|
||||||
// transaction's cost with the given nonce to check for overdrafts.
|
// transaction's cost with the given nonce to check for overdrafts.
|
||||||
ExistingCost func(addr common.Address, nonce uint64) *big.Int
|
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
|
// ValidateTransactionWithState is a helper method to check whether a transaction
|
||||||
|
|
@ -273,15 +258,9 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op
|
||||||
// Transaction takes a new nonce value out of the pool. Ensure it doesn't
|
// Transaction takes a new nonce value out of the pool. Ensure it doesn't
|
||||||
// overflow the number of permitted transactions from a single account
|
// overflow the number of permitted transactions from a single account
|
||||||
// (i.e. max cancellable via out-of-bound transaction).
|
// (i.e. max cancellable via out-of-bound transaction).
|
||||||
if used, left := opts.UsedAndLeftSlots(from); left <= 0 {
|
if opts.UsedAndLeftSlots != nil {
|
||||||
return fmt.Errorf("%w: pooled %d txs", ErrAccountLimitExceeded, used)
|
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -483,15 +483,23 @@ func (tx *Transaction) SetCodeAuthorizations() []SetCodeAuthorization {
|
||||||
return setcodetx.AuthList
|
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 {
|
func (tx *Transaction) SetCodeAuthorities() []common.Address {
|
||||||
setcodetx, ok := tx.inner.(*SetCodeTx)
|
setcodetx, ok := tx.inner.(*SetCodeTx)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
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 {
|
for _, auth := range setcodetx.AuthList {
|
||||||
if addr, err := auth.Authority(); err == nil {
|
if addr, err := auth.Authority(); err == nil {
|
||||||
|
if marks[addr] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
marks[addr] = true
|
||||||
auths = append(auths, addr)
|
auths = append(auths, addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -514,6 +514,5 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
|
||||||
// Now set the inner transaction.
|
// Now set the inner transaction.
|
||||||
tx.setDecoded(inner, 0)
|
tx.setDecoded(inner, 0)
|
||||||
|
|
||||||
// TODO: check hash here?
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package types
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"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)
|
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.
|
// blobTxWithBlobs is used for encoding of transactions when blobs are present.
|
||||||
type blobTxWithBlobs struct {
|
type blobTxWithBlobs struct {
|
||||||
BlobTx *BlobTx
|
BlobTx *BlobTx
|
||||||
|
|
|
||||||
|
|
@ -184,6 +184,9 @@ func UnmarshalPubkey(pub []byte) (*ecdsa.PublicKey, error) {
|
||||||
return &ecdsa.PublicKey{Curve: S256(), X: x, Y: y}, nil
|
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 {
|
func FromECDSAPub(pub *ecdsa.PublicKey) []byte {
|
||||||
if pub == nil || pub.X == nil || pub.Y == nil {
|
if pub == nil || pub.X == nil || pub.Y == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,7 @@ func TestLoadECDSA(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
f, err := os.CreateTemp("", "loadecdsa_test.*.txt")
|
f, err := os.CreateTemp(t.TempDir(), "loadecdsa_test.*.txt")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -202,7 +202,7 @@ func TestLoadECDSA(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSaveECDSA(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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -109,8 +109,8 @@ int secp256k1_ext_scalar_mul(const secp256k1_context* ctx, unsigned char *point,
|
||||||
ARG_CHECK(scalar != NULL);
|
ARG_CHECK(scalar != NULL);
|
||||||
(void)ctx;
|
(void)ctx;
|
||||||
|
|
||||||
secp256k1_fe_set_b32(&feX, point);
|
secp256k1_fe_set_b32_limit(&feX, point);
|
||||||
secp256k1_fe_set_b32(&feY, point+32);
|
secp256k1_fe_set_b32_limit(&feY, point+32);
|
||||||
secp256k1_ge_set_xy(&ge, &feX, &feY);
|
secp256k1_ge_set_xy(&ge, &feX, &feY);
|
||||||
secp256k1_scalar_set_b32(&s, scalar, &overflow);
|
secp256k1_scalar_set_b32(&s, scalar, &overflow);
|
||||||
if (overflow || secp256k1_scalar_is_zero(&s)) {
|
if (overflow || secp256k1_scalar_is_zero(&s)) {
|
||||||
|
|
|
||||||
101
crypto/secp256k1/libsecp256k1/.cirrus.yml
Normal file
101
crypto/secp256k1/libsecp256k1/.cirrus.yml
Normal 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
|
||||||
2
crypto/secp256k1/libsecp256k1/.gitattributes
vendored
Normal file
2
crypto/secp256k1/libsecp256k1/.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
src/precomputed_ecmult.c linguist-generated
|
||||||
|
src/precomputed_ecmult_gen.c linguist-generated
|
||||||
33
crypto/secp256k1/libsecp256k1/.github/actions/install-homebrew-valgrind/action.yml
vendored
Normal file
33
crypto/secp256k1/libsecp256k1/.github/actions/install-homebrew-valgrind/action.yml
vendored
Normal 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
|
||||||
54
crypto/secp256k1/libsecp256k1/.github/actions/run-in-docker-action/action.yml
vendored
Normal file
54
crypto/secp256k1/libsecp256k1/.github/actions/run-in-docker-action/action.yml
vendored
Normal 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
|
||||||
890
crypto/secp256k1/libsecp256k1/.github/workflows/ci.yml
vendored
Normal file
890
crypto/secp256k1/libsecp256k1/.github/workflows/ci.yml
vendored
Normal 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
|
||||||
42
crypto/secp256k1/libsecp256k1/.gitignore
vendored
42
crypto/secp256k1/libsecp256k1/.gitignore
vendored
|
|
@ -1,17 +1,24 @@
|
||||||
bench_inv
|
bench
|
||||||
bench_ecdh
|
bench_ecmult
|
||||||
bench_sign
|
|
||||||
bench_verify
|
|
||||||
bench_schnorr_verify
|
|
||||||
bench_recover
|
|
||||||
bench_internal
|
bench_internal
|
||||||
|
noverify_tests
|
||||||
tests
|
tests
|
||||||
exhaustive_tests
|
exhaustive_tests
|
||||||
gen_context
|
precompute_ecmult_gen
|
||||||
|
precompute_ecmult
|
||||||
|
ctime_tests
|
||||||
|
ecdh_example
|
||||||
|
ecdsa_example
|
||||||
|
schnorr_example
|
||||||
|
ellswift_example
|
||||||
|
musig_example
|
||||||
*.exe
|
*.exe
|
||||||
*.so
|
*.so
|
||||||
*.a
|
*.a
|
||||||
!.gitignore
|
*.csv
|
||||||
|
*.log
|
||||||
|
*.trs
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
Makefile
|
Makefile
|
||||||
configure
|
configure
|
||||||
|
|
@ -21,6 +28,7 @@ aclocal.m4
|
||||||
autom4te.cache/
|
autom4te.cache/
|
||||||
config.log
|
config.log
|
||||||
config.status
|
config.status
|
||||||
|
conftest*
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
*.la
|
*.la
|
||||||
libtool
|
libtool
|
||||||
|
|
@ -29,9 +37,15 @@ libtool
|
||||||
*.lo
|
*.lo
|
||||||
*.o
|
*.o
|
||||||
*~
|
*~
|
||||||
src/libsecp256k1-config.h
|
|
||||||
src/libsecp256k1-config.h.in
|
coverage/
|
||||||
src/ecmult_static_context.h
|
coverage.html
|
||||||
|
coverage.*.html
|
||||||
|
*.gcda
|
||||||
|
*.gcno
|
||||||
|
*.gcov
|
||||||
|
|
||||||
|
build-aux/ar-lib
|
||||||
build-aux/config.guess
|
build-aux/config.guess
|
||||||
build-aux/config.sub
|
build-aux/config.sub
|
||||||
build-aux/depcomp
|
build-aux/depcomp
|
||||||
|
|
@ -45,5 +59,9 @@ build-aux/m4/ltversion.m4
|
||||||
build-aux/missing
|
build-aux/missing
|
||||||
build-aux/compile
|
build-aux/compile
|
||||||
build-aux/test-driver
|
build-aux/test-driver
|
||||||
src/stamp-h1
|
|
||||||
libsecp256k1.pc
|
libsecp256k1.pc
|
||||||
|
|
||||||
|
### CMake
|
||||||
|
/CMakeUserPresets.json
|
||||||
|
# Default CMake build directory.
|
||||||
|
/build
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
177
crypto/secp256k1/libsecp256k1/CHANGELOG.md
Normal file
177
crypto/secp256k1/libsecp256k1/CHANGELOG.md
Normal 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
Loading…
Reference in a new issue