Merge pull request #86 from berachain/update-main

Update main
This commit is contained in:
Cal Bera 2025-09-09 14:04:25 -07:00 committed by GitHub
commit b8ea5f66d0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
198 changed files with 10249 additions and 2213 deletions

23
.github/workflows/validate_pr.yml vendored Normal file
View file

@ -0,0 +1,23 @@
name: PR Format Validation
on:
pull_request:
types: [opened, edited, synchronize]
jobs:
validate-pr:
runs-on: ubuntu-latest
steps:
- name: Check PR Title Format
uses: actions/github-script@v7
with:
script: |
const prTitle = context.payload.pull_request.title;
const titleRegex = /^(\.?[\w\s,{}/]+): .+/;
if (!titleRegex.test(prTitle)) {
core.setFailed(`PR title "${prTitle}" does not match required format: directory, ...: description`);
return;
}
console.log('✅ PR title format is valid');

View file

@ -8,6 +8,7 @@ https://pkg.go.dev/badge/github.com/ethereum/go-ethereum
[![Go Report Card](https://goreportcard.com/badge/github.com/ethereum/go-ethereum)](https://goreportcard.com/report/github.com/ethereum/go-ethereum) [![Go Report Card](https://goreportcard.com/badge/github.com/ethereum/go-ethereum)](https://goreportcard.com/report/github.com/ethereum/go-ethereum)
[![Travis](https://app.travis-ci.com/ethereum/go-ethereum.svg?branch=master)](https://app.travis-ci.com/github/ethereum/go-ethereum) [![Travis](https://app.travis-ci.com/ethereum/go-ethereum.svg?branch=master)](https://app.travis-ci.com/github/ethereum/go-ethereum)
[![Discord](https://img.shields.io/badge/discord-join%20chat-blue.svg)](https://discord.gg/nthXNEv) [![Discord](https://img.shields.io/badge/discord-join%20chat-blue.svg)](https://discord.gg/nthXNEv)
[![Twitter](https://img.shields.io/twitter/follow/go_ethereum)](https://x.com/go_ethereum)
Automated builds are available for stable releases and the unstable master branch. Binary Automated builds are available for stable releases and the unstable master branch. Binary
archives are published at https://geth.ethereum.org/downloads/. archives are published at https://geth.ethereum.org/downloads/.

View file

@ -183,7 +183,7 @@ var (
// Solidity: {{.Original.String}} // Solidity: {{.Original.String}}
func ({{ decapitalise $contract.Type}} *{{$contract.Type}}) Unpack{{.Normalized.Name}}Event(log *types.Log) (*{{$contract.Type}}{{.Normalized.Name}}, error) { func ({{ decapitalise $contract.Type}} *{{$contract.Type}}) Unpack{{.Normalized.Name}}Event(log *types.Log) (*{{$contract.Type}}{{.Normalized.Name}}, error) {
event := "{{.Original.Name}}" event := "{{.Original.Name}}"
if log.Topics[0] != {{ decapitalise $contract.Type}}.abi.Events[event].ID { if len(log.Topics) == 0 || log.Topics[0] != {{ decapitalise $contract.Type}}.abi.Events[event].ID {
return nil, errors.New("event signature mismatch") return nil, errors.New("event signature mismatch")
} }
out := new({{$contract.Type}}{{.Normalized.Name}}) out := new({{$contract.Type}}{{.Normalized.Name}})

View file

@ -360,7 +360,7 @@ func (CrowdsaleFundTransfer) ContractEventName() string {
// Solidity: event FundTransfer(address backer, uint256 amount, bool isContribution) // Solidity: event FundTransfer(address backer, uint256 amount, bool isContribution)
func (crowdsale *Crowdsale) UnpackFundTransferEvent(log *types.Log) (*CrowdsaleFundTransfer, error) { func (crowdsale *Crowdsale) UnpackFundTransferEvent(log *types.Log) (*CrowdsaleFundTransfer, error) {
event := "FundTransfer" event := "FundTransfer"
if log.Topics[0] != crowdsale.abi.Events[event].ID { if len(log.Topics) == 0 || log.Topics[0] != crowdsale.abi.Events[event].ID {
return nil, errors.New("event signature mismatch") return nil, errors.New("event signature mismatch")
} }
out := new(CrowdsaleFundTransfer) out := new(CrowdsaleFundTransfer)

View file

@ -606,7 +606,7 @@ func (DAOChangeOfRules) ContractEventName() string {
// Solidity: event ChangeOfRules(uint256 minimumQuorum, uint256 debatingPeriodInMinutes, int256 majorityMargin) // Solidity: event ChangeOfRules(uint256 minimumQuorum, uint256 debatingPeriodInMinutes, int256 majorityMargin)
func (dAO *DAO) UnpackChangeOfRulesEvent(log *types.Log) (*DAOChangeOfRules, error) { func (dAO *DAO) UnpackChangeOfRulesEvent(log *types.Log) (*DAOChangeOfRules, error) {
event := "ChangeOfRules" event := "ChangeOfRules"
if log.Topics[0] != dAO.abi.Events[event].ID { if len(log.Topics) == 0 || log.Topics[0] != dAO.abi.Events[event].ID {
return nil, errors.New("event signature mismatch") return nil, errors.New("event signature mismatch")
} }
out := new(DAOChangeOfRules) out := new(DAOChangeOfRules)
@ -648,7 +648,7 @@ func (DAOMembershipChanged) ContractEventName() string {
// Solidity: event MembershipChanged(address member, bool isMember) // Solidity: event MembershipChanged(address member, bool isMember)
func (dAO *DAO) UnpackMembershipChangedEvent(log *types.Log) (*DAOMembershipChanged, error) { func (dAO *DAO) UnpackMembershipChangedEvent(log *types.Log) (*DAOMembershipChanged, error) {
event := "MembershipChanged" event := "MembershipChanged"
if log.Topics[0] != dAO.abi.Events[event].ID { if len(log.Topics) == 0 || log.Topics[0] != dAO.abi.Events[event].ID {
return nil, errors.New("event signature mismatch") return nil, errors.New("event signature mismatch")
} }
out := new(DAOMembershipChanged) out := new(DAOMembershipChanged)
@ -692,7 +692,7 @@ func (DAOProposalAdded) ContractEventName() string {
// Solidity: event ProposalAdded(uint256 proposalID, address recipient, uint256 amount, string description) // Solidity: event ProposalAdded(uint256 proposalID, address recipient, uint256 amount, string description)
func (dAO *DAO) UnpackProposalAddedEvent(log *types.Log) (*DAOProposalAdded, error) { func (dAO *DAO) UnpackProposalAddedEvent(log *types.Log) (*DAOProposalAdded, error) {
event := "ProposalAdded" event := "ProposalAdded"
if log.Topics[0] != dAO.abi.Events[event].ID { if len(log.Topics) == 0 || log.Topics[0] != dAO.abi.Events[event].ID {
return nil, errors.New("event signature mismatch") return nil, errors.New("event signature mismatch")
} }
out := new(DAOProposalAdded) out := new(DAOProposalAdded)
@ -736,7 +736,7 @@ func (DAOProposalTallied) ContractEventName() string {
// Solidity: event ProposalTallied(uint256 proposalID, int256 result, uint256 quorum, bool active) // Solidity: event ProposalTallied(uint256 proposalID, int256 result, uint256 quorum, bool active)
func (dAO *DAO) UnpackProposalTalliedEvent(log *types.Log) (*DAOProposalTallied, error) { func (dAO *DAO) UnpackProposalTalliedEvent(log *types.Log) (*DAOProposalTallied, error) {
event := "ProposalTallied" event := "ProposalTallied"
if log.Topics[0] != dAO.abi.Events[event].ID { if len(log.Topics) == 0 || log.Topics[0] != dAO.abi.Events[event].ID {
return nil, errors.New("event signature mismatch") return nil, errors.New("event signature mismatch")
} }
out := new(DAOProposalTallied) out := new(DAOProposalTallied)
@ -780,7 +780,7 @@ func (DAOVoted) ContractEventName() string {
// Solidity: event Voted(uint256 proposalID, bool position, address voter, string justification) // Solidity: event Voted(uint256 proposalID, bool position, address voter, string justification)
func (dAO *DAO) UnpackVotedEvent(log *types.Log) (*DAOVoted, error) { func (dAO *DAO) UnpackVotedEvent(log *types.Log) (*DAOVoted, error) {
event := "Voted" event := "Voted"
if log.Topics[0] != dAO.abi.Events[event].ID { if len(log.Topics) == 0 || log.Topics[0] != dAO.abi.Events[event].ID {
return nil, errors.New("event signature mismatch") return nil, errors.New("event signature mismatch")
} }
out := new(DAOVoted) out := new(DAOVoted)

View file

@ -72,7 +72,7 @@ func (EventCheckerDynamic) ContractEventName() string {
// Solidity: event dynamic(string indexed idxStr, bytes indexed idxDat, string str, bytes dat) // Solidity: event dynamic(string indexed idxStr, bytes indexed idxDat, string str, bytes dat)
func (eventChecker *EventChecker) UnpackDynamicEvent(log *types.Log) (*EventCheckerDynamic, error) { func (eventChecker *EventChecker) UnpackDynamicEvent(log *types.Log) (*EventCheckerDynamic, error) {
event := "dynamic" event := "dynamic"
if log.Topics[0] != eventChecker.abi.Events[event].ID { if len(log.Topics) == 0 || log.Topics[0] != eventChecker.abi.Events[event].ID {
return nil, errors.New("event signature mismatch") return nil, errors.New("event signature mismatch")
} }
out := new(EventCheckerDynamic) out := new(EventCheckerDynamic)
@ -112,7 +112,7 @@ func (EventCheckerEmpty) ContractEventName() string {
// Solidity: event empty() // Solidity: event empty()
func (eventChecker *EventChecker) UnpackEmptyEvent(log *types.Log) (*EventCheckerEmpty, error) { func (eventChecker *EventChecker) UnpackEmptyEvent(log *types.Log) (*EventCheckerEmpty, error) {
event := "empty" event := "empty"
if log.Topics[0] != eventChecker.abi.Events[event].ID { if len(log.Topics) == 0 || log.Topics[0] != eventChecker.abi.Events[event].ID {
return nil, errors.New("event signature mismatch") return nil, errors.New("event signature mismatch")
} }
out := new(EventCheckerEmpty) out := new(EventCheckerEmpty)
@ -154,7 +154,7 @@ func (EventCheckerIndexed) ContractEventName() string {
// Solidity: event indexed(address indexed addr, int256 indexed num) // Solidity: event indexed(address indexed addr, int256 indexed num)
func (eventChecker *EventChecker) UnpackIndexedEvent(log *types.Log) (*EventCheckerIndexed, error) { func (eventChecker *EventChecker) UnpackIndexedEvent(log *types.Log) (*EventCheckerIndexed, error) {
event := "indexed" event := "indexed"
if log.Topics[0] != eventChecker.abi.Events[event].ID { if len(log.Topics) == 0 || log.Topics[0] != eventChecker.abi.Events[event].ID {
return nil, errors.New("event signature mismatch") return nil, errors.New("event signature mismatch")
} }
out := new(EventCheckerIndexed) out := new(EventCheckerIndexed)
@ -196,7 +196,7 @@ func (EventCheckerMixed) ContractEventName() string {
// Solidity: event mixed(address indexed addr, int256 num) // Solidity: event mixed(address indexed addr, int256 num)
func (eventChecker *EventChecker) UnpackMixedEvent(log *types.Log) (*EventCheckerMixed, error) { func (eventChecker *EventChecker) UnpackMixedEvent(log *types.Log) (*EventCheckerMixed, error) {
event := "mixed" event := "mixed"
if log.Topics[0] != eventChecker.abi.Events[event].ID { if len(log.Topics) == 0 || log.Topics[0] != eventChecker.abi.Events[event].ID {
return nil, errors.New("event signature mismatch") return nil, errors.New("event signature mismatch")
} }
out := new(EventCheckerMixed) out := new(EventCheckerMixed)
@ -238,7 +238,7 @@ func (EventCheckerUnnamed) ContractEventName() string {
// Solidity: event unnamed(uint256 indexed arg0, uint256 indexed arg1) // Solidity: event unnamed(uint256 indexed arg0, uint256 indexed arg1)
func (eventChecker *EventChecker) UnpackUnnamedEvent(log *types.Log) (*EventCheckerUnnamed, error) { func (eventChecker *EventChecker) UnpackUnnamedEvent(log *types.Log) (*EventCheckerUnnamed, error) {
event := "unnamed" event := "unnamed"
if log.Topics[0] != eventChecker.abi.Events[event].ID { if len(log.Topics) == 0 || log.Topics[0] != eventChecker.abi.Events[event].ID {
return nil, errors.New("event signature mismatch") return nil, errors.New("event signature mismatch")
} }
out := new(EventCheckerUnnamed) out := new(EventCheckerUnnamed)

View file

@ -134,7 +134,7 @@ func (NameConflictLog) ContractEventName() string {
// Solidity: event log(int256 msg, int256 _msg) // Solidity: event log(int256 msg, int256 _msg)
func (nameConflict *NameConflict) UnpackLogEvent(log *types.Log) (*NameConflictLog, error) { func (nameConflict *NameConflict) UnpackLogEvent(log *types.Log) (*NameConflictLog, error) {
event := "log" event := "log"
if log.Topics[0] != nameConflict.abi.Events[event].ID { if len(log.Topics) == 0 || log.Topics[0] != nameConflict.abi.Events[event].ID {
return nil, errors.New("event signature mismatch") return nil, errors.New("event signature mismatch")
} }
out := new(NameConflictLog) out := new(NameConflictLog)

View file

@ -136,7 +136,7 @@ func (NumericMethodNameE1TestEvent) ContractEventName() string {
// Solidity: event _1TestEvent(address _param) // Solidity: event _1TestEvent(address _param)
func (numericMethodName *NumericMethodName) UnpackE1TestEventEvent(log *types.Log) (*NumericMethodNameE1TestEvent, error) { func (numericMethodName *NumericMethodName) UnpackE1TestEventEvent(log *types.Log) (*NumericMethodNameE1TestEvent, error) {
event := "_1TestEvent" event := "_1TestEvent"
if log.Topics[0] != numericMethodName.abi.Events[event].ID { if len(log.Topics) == 0 || log.Topics[0] != numericMethodName.abi.Events[event].ID {
return nil, errors.New("event signature mismatch") return nil, errors.New("event signature mismatch")
} }
out := new(NumericMethodNameE1TestEvent) out := new(NumericMethodNameE1TestEvent)

View file

@ -114,7 +114,7 @@ func (OverloadBar) ContractEventName() string {
// Solidity: event bar(uint256 i) // Solidity: event bar(uint256 i)
func (overload *Overload) UnpackBarEvent(log *types.Log) (*OverloadBar, error) { func (overload *Overload) UnpackBarEvent(log *types.Log) (*OverloadBar, error) {
event := "bar" event := "bar"
if log.Topics[0] != overload.abi.Events[event].ID { if len(log.Topics) == 0 || log.Topics[0] != overload.abi.Events[event].ID {
return nil, errors.New("event signature mismatch") return nil, errors.New("event signature mismatch")
} }
out := new(OverloadBar) out := new(OverloadBar)
@ -156,7 +156,7 @@ func (OverloadBar0) ContractEventName() string {
// Solidity: event bar(uint256 i, uint256 j) // Solidity: event bar(uint256 i, uint256 j)
func (overload *Overload) UnpackBar0Event(log *types.Log) (*OverloadBar0, error) { func (overload *Overload) UnpackBar0Event(log *types.Log) (*OverloadBar0, error) {
event := "bar0" event := "bar0"
if log.Topics[0] != overload.abi.Events[event].ID { if len(log.Topics) == 0 || log.Topics[0] != overload.abi.Events[event].ID {
return nil, errors.New("event signature mismatch") return nil, errors.New("event signature mismatch")
} }
out := new(OverloadBar0) out := new(OverloadBar0)

View file

@ -386,7 +386,7 @@ func (TokenTransfer) ContractEventName() string {
// Solidity: event Transfer(address indexed from, address indexed to, uint256 value) // Solidity: event Transfer(address indexed from, address indexed to, uint256 value)
func (token *Token) UnpackTransferEvent(log *types.Log) (*TokenTransfer, error) { func (token *Token) UnpackTransferEvent(log *types.Log) (*TokenTransfer, error) {
event := "Transfer" event := "Transfer"
if log.Topics[0] != token.abi.Events[event].ID { if len(log.Topics) == 0 || log.Topics[0] != token.abi.Events[event].ID {
return nil, errors.New("event signature mismatch") return nil, errors.New("event signature mismatch")
} }
out := new(TokenTransfer) out := new(TokenTransfer)

View file

@ -193,7 +193,7 @@ func (TupleTupleEvent) ContractEventName() string {
// Solidity: event TupleEvent((uint256,uint256[],(uint256,uint256)[]) a, (uint256,uint256)[2][] b, (uint256,uint256)[][2] c, (uint256,uint256[],(uint256,uint256)[])[] d, uint256[] e) // Solidity: event TupleEvent((uint256,uint256[],(uint256,uint256)[]) a, (uint256,uint256)[2][] b, (uint256,uint256)[][2] c, (uint256,uint256[],(uint256,uint256)[])[] d, uint256[] e)
func (tuple *Tuple) UnpackTupleEventEvent(log *types.Log) (*TupleTupleEvent, error) { func (tuple *Tuple) UnpackTupleEventEvent(log *types.Log) (*TupleTupleEvent, error) {
event := "TupleEvent" event := "TupleEvent"
if log.Topics[0] != tuple.abi.Events[event].ID { if len(log.Topics) == 0 || log.Topics[0] != tuple.abi.Events[event].ID {
return nil, errors.New("event signature mismatch") return nil, errors.New("event signature mismatch")
} }
out := new(TupleTupleEvent) out := new(TupleTupleEvent)
@ -234,7 +234,7 @@ func (TupleTupleEvent2) ContractEventName() string {
// Solidity: event TupleEvent2((uint8,uint8)[] arg0) // Solidity: event TupleEvent2((uint8,uint8)[] arg0)
func (tuple *Tuple) UnpackTupleEvent2Event(log *types.Log) (*TupleTupleEvent2, error) { func (tuple *Tuple) UnpackTupleEvent2Event(log *types.Log) (*TupleTupleEvent2, error) {
event := "TupleEvent2" event := "TupleEvent2"
if log.Topics[0] != tuple.abi.Events[event].ID { if len(log.Topics) == 0 || log.Topics[0] != tuple.abi.Events[event].ID {
return nil, errors.New("event signature mismatch") return nil, errors.New("event signature mismatch")
} }
out := new(TupleTupleEvent2) out := new(TupleTupleEvent2)

View file

@ -276,7 +276,7 @@ func (DBInsert) ContractEventName() string {
// Solidity: event Insert(uint256 key, uint256 value, uint256 length) // Solidity: event Insert(uint256 key, uint256 value, uint256 length)
func (dB *DB) UnpackInsertEvent(log *types.Log) (*DBInsert, error) { func (dB *DB) UnpackInsertEvent(log *types.Log) (*DBInsert, error) {
event := "Insert" event := "Insert"
if log.Topics[0] != dB.abi.Events[event].ID { if len(log.Topics) == 0 || log.Topics[0] != dB.abi.Events[event].ID {
return nil, errors.New("event signature mismatch") return nil, errors.New("event signature mismatch")
} }
out := new(DBInsert) out := new(DBInsert)
@ -318,7 +318,7 @@ func (DBKeyedInsert) ContractEventName() string {
// Solidity: event KeyedInsert(uint256 indexed key, uint256 value) // Solidity: event KeyedInsert(uint256 indexed key, uint256 value)
func (dB *DB) UnpackKeyedInsertEvent(log *types.Log) (*DBKeyedInsert, error) { func (dB *DB) UnpackKeyedInsertEvent(log *types.Log) (*DBKeyedInsert, error) {
event := "KeyedInsert" event := "KeyedInsert"
if log.Topics[0] != dB.abi.Events[event].ID { if len(log.Topics) == 0 || log.Topics[0] != dB.abi.Events[event].ID {
return nil, errors.New("event signature mismatch") return nil, errors.New("event signature mismatch")
} }
out := new(DBKeyedInsert) out := new(DBKeyedInsert)

View file

@ -115,7 +115,7 @@ func (CBasic1) ContractEventName() string {
// Solidity: event basic1(uint256 indexed id, uint256 data) // Solidity: event basic1(uint256 indexed id, uint256 data)
func (c *C) UnpackBasic1Event(log *types.Log) (*CBasic1, error) { func (c *C) UnpackBasic1Event(log *types.Log) (*CBasic1, error) {
event := "basic1" event := "basic1"
if log.Topics[0] != c.abi.Events[event].ID { if len(log.Topics) == 0 || log.Topics[0] != c.abi.Events[event].ID {
return nil, errors.New("event signature mismatch") return nil, errors.New("event signature mismatch")
} }
out := new(CBasic1) out := new(CBasic1)
@ -157,7 +157,7 @@ func (CBasic2) ContractEventName() string {
// Solidity: event basic2(bool indexed flag, uint256 data) // Solidity: event basic2(bool indexed flag, uint256 data)
func (c *C) UnpackBasic2Event(log *types.Log) (*CBasic2, error) { func (c *C) UnpackBasic2Event(log *types.Log) (*CBasic2, error) {
event := "basic2" event := "basic2"
if log.Topics[0] != c.abi.Events[event].ID { if len(log.Topics) == 0 || log.Topics[0] != c.abi.Events[event].ID {
return nil, errors.New("event signature mismatch") return nil, errors.New("event signature mismatch")
} }
out := new(CBasic2) out := new(CBasic2)

View file

@ -367,3 +367,28 @@ func TestErrors(t *testing.T) {
t.Fatalf("bad unpacked error result: expected Arg4 to be false. got true") t.Fatalf("bad unpacked error result: expected Arg4 to be false. got true")
} }
} }
func TestEventUnpackEmptyTopics(t *testing.T) {
c := events.NewC()
for _, log := range []*types.Log{
{Topics: []common.Hash{}},
{Topics: nil},
} {
_, err := c.UnpackBasic1Event(log)
if err == nil {
t.Fatal("expected error when unpacking event with empty topics, got nil")
}
if err.Error() != "event signature mismatch" {
t.Fatalf("expected 'event signature mismatch' error, got: %v", err)
}
_, err = c.UnpackBasic2Event(log)
if err == nil {
t.Fatal("expected error when unpacking event with empty topics, got nil")
}
if err.Error() != "event signature mismatch" {
t.Fatalf("expected 'event signature mismatch' error, got: %v", err)
}
}
}

View file

@ -472,6 +472,11 @@ func (w *Wallet) selfDerive() {
continue continue
} }
pairing := w.Hub.pairing(w) pairing := w.Hub.pairing(w)
if pairing == nil {
w.lock.Unlock()
reqc <- struct{}{}
continue
}
// Device lock obtained, derive the next batch of accounts // Device lock obtained, derive the next batch of accounts
var ( var (
@ -631,13 +636,13 @@ func (w *Wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Accoun
} }
if pin { if pin {
pairing := w.Hub.pairing(w) if pairing := w.Hub.pairing(w); pairing != nil {
pairing.Accounts[account.Address] = path pairing.Accounts[account.Address] = path
if err := w.Hub.setPairing(w, pairing); err != nil { if err := w.Hub.setPairing(w, pairing); err != nil {
return accounts.Account{}, err return accounts.Account{}, err
} }
} }
}
return account, nil return account, nil
} }
@ -774,11 +779,11 @@ func (w *Wallet) SignTxWithPassphrase(account accounts.Account, passphrase strin
// It first checks for the address in the list of pinned accounts, and if it is // It first checks for the address in the list of pinned accounts, and if it is
// not found, attempts to parse the derivation path from the account's URL. // not found, attempts to parse the derivation path from the account's URL.
func (w *Wallet) findAccountPath(account accounts.Account) (accounts.DerivationPath, error) { func (w *Wallet) findAccountPath(account accounts.Account) (accounts.DerivationPath, error) {
pairing := w.Hub.pairing(w) if pairing := w.Hub.pairing(w); pairing != nil {
if path, ok := pairing.Accounts[account.Address]; ok { if path, ok := pairing.Accounts[account.Address]; ok {
return path, nil return path, nil
} }
}
// Look for the path in the URL // Look for the path in the URL
if account.URL.Scheme != w.Hub.scheme { if account.URL.Scheme != w.Hub.scheme {
return nil, fmt.Errorf("scheme %s does not match wallet scheme %s", account.URL.Scheme, w.Hub.scheme) return nil, fmt.Errorf("scheme %s does not match wallet scheme %s", account.URL.Scheme, w.Hub.scheme)

View file

@ -166,7 +166,7 @@ func (w *ledgerDriver) SignTx(path accounts.DerivationPath, tx *types.Transactio
return common.Address{}, nil, accounts.ErrWalletClosed return common.Address{}, nil, accounts.ErrWalletClosed
} }
// Ensure the wallet is capable of signing the given transaction // Ensure the wallet is capable of signing the given transaction
if chainID != nil && w.version[0] <= 1 && w.version[1] <= 0 && w.version[2] <= 2 { if chainID != nil && (w.version[0] < 1 || (w.version[0] == 1 && w.version[1] == 0 && w.version[2] < 3)) {
//lint:ignore ST1005 brand name displayed on the console //lint:ignore ST1005 brand name displayed on the console
return common.Address{}, nil, fmt.Errorf("Ledger v%d.%d.%d doesn't support signing this transaction, please update to v1.0.3 at least", w.version[0], w.version[1], w.version[2]) return common.Address{}, nil, fmt.Errorf("Ledger v%d.%d.%d doesn't support signing this transaction, please update to v1.0.3 at least", w.version[0], w.version[1], w.version[2])
} }

View file

@ -269,7 +269,7 @@ func (s *Scheduler) addEvent(event Event) {
s.Trigger() s.Trigger()
} }
// filterEvent sorts each Event either as a request event or a server event, // filterEvents sorts each Event either as a request event or a server event,
// depending on its type. Request events are also sorted in a map based on the // depending on its type. Request events are also sorted in a map based on the
// module that originally initiated the request. It also ensures that no events // module that originally initiated the request. It also ensures that no events
// related to a server are returned before EvRegistered or after EvUnregistered. // related to a server are returned before EvRegistered or after EvUnregistered.

View file

@ -124,6 +124,7 @@ var (
"jammy", // 22.04, EOL: 04/2032 "jammy", // 22.04, EOL: 04/2032
"noble", // 24.04, EOL: 04/2034 "noble", // 24.04, EOL: 04/2034
"oracular", // 24.10, EOL: 07/2025 "oracular", // 24.10, EOL: 07/2025
"plucky", // 25.04, EOL: 01/2026
} }
// This is where the tests should be unpacked. // This is where the tests should be unpacked.

View file

@ -109,6 +109,7 @@ if one is set. Otherwise it prints the genesis from the datadir.`,
utils.MetricsInfluxDBTokenFlag, utils.MetricsInfluxDBTokenFlag,
utils.MetricsInfluxDBBucketFlag, utils.MetricsInfluxDBBucketFlag,
utils.MetricsInfluxDBOrganizationFlag, utils.MetricsInfluxDBOrganizationFlag,
utils.StateSizeTrackingFlag,
utils.TxLookupLimitFlag, utils.TxLookupLimitFlag,
utils.VMTraceFlag, utils.VMTraceFlag,
utils.VMTraceJsonConfigFlag, utils.VMTraceJsonConfigFlag,
@ -292,7 +293,7 @@ func initGenesis(ctx *cli.Context) error {
// Only create triedb if we're not using PBSS or genesis is empty on disk. Refer to // Only create triedb if we're not using PBSS or genesis is empty on disk. Refer to
// https://github.com/ethereum/go-ethereum/pull/25523 for why the triedb cannot be // https://github.com/ethereum/go-ethereum/pull/25523 for why the triedb cannot be
// re-created when using PBSS. // re-created when using PBSS.
triedb = utils.MakeTrieDatabase(ctx, chaindb, ctx.Bool(utils.CachePreimagesFlag.Name), false, genesis.IsVerkle()) triedb = utils.MakeTrieDatabase(ctx, stack, chaindb, ctx.Bool(utils.CachePreimagesFlag.Name), false, genesis.IsVerkle())
defer triedb.Close() defer triedb.Close()
} }
@ -651,7 +652,7 @@ func dump(ctx *cli.Context) error {
if err != nil { if err != nil {
return err return err
} }
triedb := utils.MakeTrieDatabase(ctx, db, true, true, false) // always enable preimage lookup triedb := utils.MakeTrieDatabase(ctx, stack, db, true, true, false) // always enable preimage lookup
defer triedb.Close() defer triedb.Close()
state, err := state.New(root, state.NewDatabase(triedb, nil)) state, err := state.New(root, state.NewDatabase(triedb, nil))

View file

@ -524,7 +524,7 @@ func dbDumpTrie(ctx *cli.Context) error {
db := utils.MakeChainDatabase(ctx, stack, true) db := utils.MakeChainDatabase(ctx, stack, true)
defer db.Close() defer db.Close()
triedb := utils.MakeTrieDatabase(ctx, db, false, true, false) triedb := utils.MakeTrieDatabase(ctx, stack, db, false, true, false)
defer triedb.Close() defer triedb.Close()
var ( var (
@ -859,7 +859,7 @@ func inspectHistory(ctx *cli.Context) error {
db := utils.MakeChainDatabase(ctx, stack, true) db := utils.MakeChainDatabase(ctx, stack, true)
defer db.Close() defer db.Close()
triedb := utils.MakeTrieDatabase(ctx, db, false, false, false) triedb := utils.MakeTrieDatabase(ctx, stack, db, false, false, false)
defer triedb.Close() defer triedb.Close()
var ( var (

View file

@ -200,6 +200,7 @@ var (
utils.MetricsInfluxDBTokenFlag, utils.MetricsInfluxDBTokenFlag,
utils.MetricsInfluxDBBucketFlag, utils.MetricsInfluxDBBucketFlag,
utils.MetricsInfluxDBOrganizationFlag, utils.MetricsInfluxDBOrganizationFlag,
utils.StateSizeTrackingFlag,
} }
) )

View file

@ -217,7 +217,7 @@ func verifyState(ctx *cli.Context) error {
log.Error("Failed to load head block") log.Error("Failed to load head block")
return errors.New("no head block") return errors.New("no head block")
} }
triedb := utils.MakeTrieDatabase(ctx, chaindb, false, true, false) triedb := utils.MakeTrieDatabase(ctx, stack, chaindb, false, true, false)
defer triedb.Close() defer triedb.Close()
var ( var (
@ -282,7 +282,7 @@ func traverseState(ctx *cli.Context) error {
chaindb := utils.MakeChainDatabase(ctx, stack, true) chaindb := utils.MakeChainDatabase(ctx, stack, true)
defer chaindb.Close() defer chaindb.Close()
triedb := utils.MakeTrieDatabase(ctx, chaindb, false, true, false) triedb := utils.MakeTrieDatabase(ctx, stack, chaindb, false, true, false)
defer triedb.Close() defer triedb.Close()
headBlock := rawdb.ReadHeadBlock(chaindb) headBlock := rawdb.ReadHeadBlock(chaindb)
@ -391,7 +391,7 @@ func traverseRawState(ctx *cli.Context) error {
chaindb := utils.MakeChainDatabase(ctx, stack, true) chaindb := utils.MakeChainDatabase(ctx, stack, true)
defer chaindb.Close() defer chaindb.Close()
triedb := utils.MakeTrieDatabase(ctx, chaindb, false, true, false) triedb := utils.MakeTrieDatabase(ctx, stack, chaindb, false, true, false)
defer triedb.Close() defer triedb.Close()
headBlock := rawdb.ReadHeadBlock(chaindb) headBlock := rawdb.ReadHeadBlock(chaindb)
@ -558,7 +558,7 @@ func dumpState(ctx *cli.Context) error {
if err != nil { if err != nil {
return err return err
} }
triedb := utils.MakeTrieDatabase(ctx, db, false, true, false) triedb := utils.MakeTrieDatabase(ctx, stack, db, false, true, false)
defer triedb.Close() defer triedb.Close()
snapConfig := snapshot.Config{ snapConfig := snapshot.Config{
@ -640,7 +640,7 @@ func snapshotExportPreimages(ctx *cli.Context) error {
chaindb := utils.MakeChainDatabase(ctx, stack, true) chaindb := utils.MakeChainDatabase(ctx, stack, true)
defer chaindb.Close() defer chaindb.Close()
triedb := utils.MakeTrieDatabase(ctx, chaindb, false, true, false) triedb := utils.MakeTrieDatabase(ctx, stack, chaindb, false, true, false)
defer triedb.Close() defer triedb.Close()
var root common.Hash var root common.Hash

View file

@ -1133,7 +1133,10 @@ func (s *Suite) testBadBlobTx(t *utesting.T, tx *types.Transaction, badTx *types
// transmit the same tx but with correct sidecar from the good peer. // transmit the same tx but with correct sidecar from the good peer.
var req *eth.GetPooledTransactionsPacket var req *eth.GetPooledTransactionsPacket
req, err = readUntil[eth.GetPooledTransactionsPacket](context.Background(), conn) ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second)
defer cancel()
req, err = readUntil[eth.GetPooledTransactionsPacket](ctx, conn)
if err != nil { if err != nil {
errc <- fmt.Errorf("reading pooled tx request failed: %v", err) errc <- fmt.Errorf("reading pooled tx request failed: %v", err)
return return

View file

@ -425,7 +425,7 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc) *state.StateDB
sdb := state.NewDatabase(tdb, nil) sdb := state.NewDatabase(tdb, nil)
statedb, _ := state.New(types.EmptyRootHash, sdb) statedb, _ := state.New(types.EmptyRootHash, sdb)
for addr, a := range accounts { for addr, a := range accounts {
statedb.SetCode(addr, a.Code) statedb.SetCode(addr, a.Code, tracing.CodeChangeUnspecified)
statedb.SetNonce(addr, a.Nonce, tracing.NonceChangeGenesis) statedb.SetNonce(addr, a.Nonce, tracing.NonceChangeGenesis)
statedb.SetBalance(addr, uint256.MustFromBig(a.Balance), tracing.BalanceIncreaseGenesisBalance) statedb.SetBalance(addr, uint256.MustFromBig(a.Balance), tracing.BalanceIncreaseGenesisBalance)
for k, v := range a.Storage { for k, v := range a.Storage {

View file

@ -322,7 +322,7 @@ func runCmd(ctx *cli.Context) error {
} }
} else { } else {
if len(code) > 0 { if len(code) > 0 {
prestate.SetCode(receiver, code) prestate.SetCode(receiver, code, tracing.CodeChangeUnspecified)
} }
execFunc = func() ([]byte, uint64, error) { execFunc = func() ([]byte, uint64, error) {
// don't mutate the state! // don't mutate the state!

View file

@ -281,6 +281,12 @@ var (
Usage: "Scheme to use for storing ethereum state ('hash' or 'path')", Usage: "Scheme to use for storing ethereum state ('hash' or 'path')",
Category: flags.StateCategory, Category: flags.StateCategory,
} }
StateSizeTrackingFlag = &cli.BoolFlag{
Name: "state.size-tracking",
Usage: "Enable state size tracking, retrieve state size with debug_stateSize.",
Value: ethconfig.Defaults.EnableStateSizeTracking,
Category: flags.StateCategory,
}
StateHistoryFlag = &cli.Uint64Flag{ StateHistoryFlag = &cli.Uint64Flag{
Name: "history.state", Name: "history.state",
Usage: "Number of recent blocks to retain state history for, only relevant in state.scheme=path (default = 90,000 blocks, 0 = entire chain)", Usage: "Number of recent blocks to retain state history for, only relevant in state.scheme=path (default = 90,000 blocks, 0 = entire chain)",
@ -1741,6 +1747,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
cfg.EthDiscoveryURLs = SplitAndTrim(urls) cfg.EthDiscoveryURLs = SplitAndTrim(urls)
} }
} }
if ctx.Bool(StateSizeTrackingFlag.Name) {
cfg.EnableStateSizeTracking = true
}
// Override any default configs for hard coded networks. // Override any default configs for hard coded networks.
switch { switch {
case ctx.Bool(MainnetFlag.Name): case ctx.Bool(MainnetFlag.Name):
@ -2223,6 +2232,9 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
// - DATADIR/triedb/merkle.journal // - DATADIR/triedb/merkle.journal
// - DATADIR/triedb/verkle.journal // - DATADIR/triedb/verkle.journal
TrieJournalDirectory: stack.ResolvePath("triedb"), TrieJournalDirectory: stack.ResolvePath("triedb"),
// Enable state size tracking if enabled
StateSizeTracking: ctx.Bool(StateSizeTrackingFlag.Name),
} }
if options.ArchiveMode && !options.Preimages { if options.ArchiveMode && !options.Preimages {
options.Preimages = true options.Preimages = true
@ -2284,7 +2296,7 @@ func MakeConsolePreloads(ctx *cli.Context) []string {
} }
// MakeTrieDatabase constructs a trie database based on the configured scheme. // MakeTrieDatabase constructs a trie database based on the configured scheme.
func MakeTrieDatabase(ctx *cli.Context, disk ethdb.Database, preimage bool, readOnly bool, isVerkle bool) *triedb.Database { func MakeTrieDatabase(ctx *cli.Context, stack *node.Node, disk ethdb.Database, preimage bool, readOnly bool, isVerkle bool) *triedb.Database {
config := &triedb.Config{ config := &triedb.Config{
Preimages: preimage, Preimages: preimage,
IsVerkle: isVerkle, IsVerkle: isVerkle,
@ -2300,10 +2312,13 @@ func MakeTrieDatabase(ctx *cli.Context, disk ethdb.Database, preimage bool, read
config.HashDB = hashdb.Defaults config.HashDB = hashdb.Defaults
return triedb.NewDatabase(disk, config) return triedb.NewDatabase(disk, config)
} }
var pathConfig pathdb.Config
if readOnly { if readOnly {
config.PathDB = pathdb.ReadOnly pathConfig = *pathdb.ReadOnly
} else { } else {
config.PathDB = pathdb.Defaults pathConfig = *pathdb.Defaults
} }
pathConfig.JournalDirectory = stack.ResolvePath("triedb")
config.PathDB = &pathConfig
return triedb.NewDatabase(disk, config) return triedb.NewDatabase(disk, config)
} }

View file

@ -154,7 +154,7 @@ func (st *bucketStats) print(name string) {
name, st.count, float64(st.blocks)/float64(st.count), float64(st.logs)/float64(st.count), st.runtime/time.Duration(st.count)) name, st.count, float64(st.blocks)/float64(st.count), float64(st.logs)/float64(st.count), st.runtime/time.Duration(st.count))
} }
// writeQueries serializes the generated errors to the error file. // writeErrors serializes the generated errors to the error file.
func writeErrors(errorFile string, errors []*filterQuery) { func writeErrors(errorFile string, errors []*filterQuery) {
file, err := os.Create(errorFile) file, err := os.Create(errorFile)
if err != nil { if err != nil {

30
common/eta.go Normal file
View file

@ -0,0 +1,30 @@
// 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 common
import "time"
// CalculateETA calculates the estimated remaining time based on the
// number of finished task, remaining task, and the time cost for finished task.
func CalculateETA(done, left uint64, elapsed time.Duration) time.Duration {
if done == 0 || elapsed.Milliseconds() == 0 {
return 0
}
speed := float64(done) / float64(elapsed.Milliseconds())
return time.Duration(float64(left)/speed) * time.Millisecond
}

60
common/eta_test.go Normal file
View file

@ -0,0 +1,60 @@
// 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 common
import (
"testing"
"time"
)
func TestCalculateETA(t *testing.T) {
type args struct {
done uint64
left uint64
elapsed time.Duration
}
tests := []struct {
name string
args args
want time.Duration
}{
{
name: "zero done",
args: args{done: 0, left: 100, elapsed: time.Second},
want: 0,
},
{
name: "zero elapsed",
args: args{done: 1, left: 100, elapsed: 0},
want: 0,
},
{
name: "@Jolly23 's case",
args: args{done: 16858580, left: 41802252, elapsed: 66179848 * time.Millisecond},
want: 164098440 * time.Millisecond,
// wrong msg: msg="Indexing state history" processed=16858580 left=41802252 elapsed=18h22m59.848s eta=11h36m42.252s
// should be around 45.58 hours
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := CalculateETA(tt.args.done, tt.args.left, tt.args.elapsed); got != tt.want {
t.Errorf("CalculateETA() = %v ms, want %v ms", got.Milliseconds(), tt.want)
}
})
}
}

View file

@ -169,10 +169,13 @@ type BlockChainConfig struct {
TrieJournalDirectory string // Directory path to the journal used for persisting trie data across node restarts TrieJournalDirectory string // Directory path to the journal used for persisting trie data across node restarts
Preimages bool // Whether to store preimage of trie key to the disk Preimages bool // Whether to store preimage of trie key to the disk
StateHistory uint64 // Number of blocks from head whose state histories are reserved.
StateScheme string // Scheme used to store ethereum states and merkle tree nodes on top StateScheme string // Scheme used to store ethereum states and merkle tree nodes on top
ArchiveMode bool // Whether to enable the archive mode ArchiveMode bool // Whether to enable the archive mode
// Number of blocks from the chain head for which state histories are retained.
// If set to 0, all state histories across the entire chain will be retained;
StateHistory uint64
// State snapshot related options // State snapshot related options
SnapshotLimit int // Memory allowance (MB) to use for caching snapshot entries in memory SnapshotLimit int // Memory allowance (MB) to use for caching snapshot entries in memory
SnapshotNoBuild bool // Whether the background generation is allowed SnapshotNoBuild bool // Whether the background generation is allowed
@ -193,6 +196,9 @@ type BlockChainConfig struct {
// If the value is zero, all transactions of the entire chain will be indexed. // If the value is zero, all transactions of the entire chain will be indexed.
// If the value is -1, indexing is disabled. // If the value is -1, indexing is disabled.
TxLookupLimit int64 TxLookupLimit int64
// StateSizeTracking indicates whether the state size tracking is enabled.
StateSizeTracking bool
} }
// DefaultConfig returns the default config. // DefaultConfig returns the default config.
@ -330,6 +336,7 @@ type BlockChain struct {
prefetcher Prefetcher prefetcher Prefetcher
processor Processor // Block transaction processor interface processor Processor // Block transaction processor interface
logger *tracing.Hooks logger *tracing.Hooks
stateSizer *state.SizeTracker // State size tracking
lastForkReadyAlert time.Time // Last time there was a fork readiness print out lastForkReadyAlert time.Time // Last time there was a fork readiness print out
} }
@ -523,6 +530,17 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine,
if bc.cfg.TxLookupLimit >= 0 { if bc.cfg.TxLookupLimit >= 0 {
bc.txIndexer = newTxIndexer(uint64(bc.cfg.TxLookupLimit), bc) bc.txIndexer = newTxIndexer(uint64(bc.cfg.TxLookupLimit), bc)
} }
// Start state size tracker
if bc.cfg.StateSizeTracking {
stateSizer, err := state.NewSizeTracker(bc.db, bc.triedb)
if err == nil {
bc.stateSizer = stateSizer
log.Info("Enabled state size metrics")
} else {
log.Info("Failed to setup size tracker", "err", err)
}
}
return bc, nil return bc, nil
} }
@ -1249,6 +1267,10 @@ func (bc *BlockChain) stopWithoutSaving() {
// Signal shutdown to all goroutines. // Signal shutdown to all goroutines.
bc.InterruptInsert(true) bc.InterruptInsert(true)
// Stop state size tracker
if bc.stateSizer != nil {
bc.stateSizer.Stop()
}
// Now wait for all chain modifications to end and persistent goroutines to exit. // Now wait for all chain modifications to end and persistent goroutines to exit.
// //
// Note: Close waits for the mutex to become available, i.e. any running chain // Note: Close waits for the mutex to become available, i.e. any running chain
@ -1583,10 +1605,14 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
log.Crit("Failed to write block into disk", "err", err) log.Crit("Failed to write block into disk", "err", err)
} }
// Commit all cached state changes into underlying memory database. // Commit all cached state changes into underlying memory database.
root, err := statedb.Commit(block.NumberU64(), bc.chainConfig.IsEIP158(block.Number()), bc.chainConfig.IsCancun(block.Number(), block.Time())) root, stateUpdate, err := statedb.CommitWithUpdate(block.NumberU64(), bc.chainConfig.IsEIP158(block.Number()), bc.chainConfig.IsCancun(block.Number(), block.Time()))
if err != nil { if err != nil {
return err return err
} }
// Emit the state update to the state sizestats if it's active
if bc.stateSizer != nil {
bc.stateSizer.Notify(stateUpdate)
}
// If node is running in path mode, skip explicit gc operation // If node is running in path mode, skip explicit gc operation
// which is unnecessary in this mode. // which is unnecessary in this mode.
if bc.triedb.Scheme() == rawdb.PathScheme { if bc.triedb.Scheme() == rawdb.PathScheme {
@ -2011,7 +2037,10 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
// If we are past Byzantium, enable prefetching to pull in trie node paths // If we are past Byzantium, enable prefetching to pull in trie node paths
// while processing transactions. Before Byzantium the prefetcher is mostly // while processing transactions. Before Byzantium the prefetcher is mostly
// useless due to the intermediate root hashing after each transaction. // useless due to the intermediate root hashing after each transaction.
var witness *stateless.Witness var (
witness *stateless.Witness
witnessStats *stateless.WitnessStats
)
if bc.chainConfig.IsByzantium(block.Number()) { if bc.chainConfig.IsByzantium(block.Number()) {
// Generate witnesses either if we're self-testing, or if it's the // Generate witnesses either if we're self-testing, or if it's the
// only block being inserted. A bit crude, but witnesses are huge, // only block being inserted. A bit crude, but witnesses are huge,
@ -2021,8 +2050,11 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
if err != nil { if err != nil {
return nil, err return nil, err
} }
if bc.cfg.VmConfig.EnableWitnessStats {
witnessStats = stateless.NewWitnessStats()
} }
statedb.StartPrefetcher("chain", witness) }
statedb.StartPrefetcher("chain", witness, witnessStats)
defer statedb.StopPrefetcher() defer statedb.StopPrefetcher()
} }
@ -2083,6 +2115,7 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
return nil, fmt.Errorf("stateless self-validation receipt root mismatch (cross: %x local: %x)", crossReceiptRoot, block.ReceiptHash()) return nil, fmt.Errorf("stateless self-validation receipt root mismatch (cross: %x local: %x)", crossReceiptRoot, block.ReceiptHash())
} }
} }
xvtime := time.Since(xvstart) xvtime := time.Since(xvstart)
proctime := time.Since(startTime) // processing + validation + cross validation proctime := time.Since(startTime) // processing + validation + cross validation
@ -2118,6 +2151,11 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Report the collected witness statistics
if witnessStats != nil {
witnessStats.ReportMetrics()
}
// Update the metrics touched during block commit // Update the metrics touched during block commit
accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them
storageCommitTimer.Update(statedb.StorageCommits) // Storage commits are complete, we can mark them storageCommitTimer.Update(statedb.StorageCommits) // Storage commits are complete, we can mark them
@ -2776,3 +2814,8 @@ 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())
} }
// StateSizer returns the state size tracker, or nil if it's not initialized
func (bc *BlockChain) StateSizer() *state.SizeTracker {
return bc.stateSizer
}

View file

@ -30,7 +30,7 @@ const (
headLogDelay = time.Second // head indexing log info delay (do not log if finished faster) 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 // indexerLoop initializes and updates the log index structure according to the
// current targetView. // current targetView.
func (f *FilterMaps) indexerLoop() { func (f *FilterMaps) indexerLoop() {
defer f.closeWg.Done() defer f.closeWg.Done()
@ -221,7 +221,7 @@ func (f *FilterMaps) processSingleEvent(blocking bool) bool {
return true return true
} }
// setTargetView updates the target chain view of the iterator. // setTarget updates the target chain view of the iterator.
func (f *FilterMaps) setTarget(target targetUpdate) { func (f *FilterMaps) setTarget(target targetUpdate) {
f.targetView = target.targetView f.targetView = target.targetView
f.historyCutoff = target.historyCutoff f.historyCutoff = target.historyCutoff

View file

@ -153,7 +153,7 @@ func hashAlloc(ga *types.GenesisAlloc, isVerkle bool) (common.Hash, error) {
if account.Balance != nil { if account.Balance != nil {
statedb.AddBalance(addr, uint256.MustFromBig(account.Balance), tracing.BalanceIncreaseGenesisBalance) statedb.AddBalance(addr, uint256.MustFromBig(account.Balance), tracing.BalanceIncreaseGenesisBalance)
} }
statedb.SetCode(addr, account.Code) statedb.SetCode(addr, account.Code, tracing.CodeChangeGenesis)
statedb.SetNonce(addr, account.Nonce, tracing.NonceChangeGenesis) statedb.SetNonce(addr, account.Nonce, tracing.NonceChangeGenesis)
for key, value := range account.Storage { for key, value := range account.Storage {
statedb.SetState(addr, key, value) statedb.SetState(addr, key, value)
@ -179,7 +179,7 @@ func flushAlloc(ga *types.GenesisAlloc, triedb *triedb.Database) (common.Hash, e
// already captures the allocations. // already captures the allocations.
statedb.AddBalance(addr, uint256.MustFromBig(account.Balance), tracing.BalanceIncreaseGenesisBalance) statedb.AddBalance(addr, uint256.MustFromBig(account.Balance), tracing.BalanceIncreaseGenesisBalance)
} }
statedb.SetCode(addr, account.Code) statedb.SetCode(addr, account.Code, tracing.CodeChangeGenesis)
statedb.SetNonce(addr, account.Nonce, tracing.NonceChangeGenesis) statedb.SetNonce(addr, account.Nonce, tracing.NonceChangeGenesis)
for key, value := range account.Storage { for key, value := range account.Storage {
statedb.SetState(addr, key, value) statedb.SetState(addr, key, value)

View file

@ -174,16 +174,3 @@ func UpdateUncleanShutdownMarker(db ethdb.KeyValueStore) {
log.Warn("Failed to write unclean-shutdown marker", "err", err) log.Warn("Failed to write unclean-shutdown marker", "err", err)
} }
} }
// ReadTransitionStatus retrieves the eth2 transition status from the database
func ReadTransitionStatus(db ethdb.KeyValueReader) []byte {
data, _ := db.Get(transitionStatusKey)
return data
}
// WriteTransitionStatus stores the eth2 transition status to the database
func WriteTransitionStatus(db ethdb.KeyValueWriter, data []byte) {
if err := db.Put(transitionStatusKey, data); err != nil {
log.Crit("Failed to store the eth2 transition status", "err", err)
}
}

View file

@ -119,13 +119,6 @@ func WriteStateID(db ethdb.KeyValueWriter, root common.Hash, id uint64) {
} }
} }
// DeleteStateID deletes the specified state lookup from the database.
func DeleteStateID(db ethdb.KeyValueWriter, root common.Hash) {
if err := db.Delete(stateIDKey(root)); err != nil {
log.Crit("Failed to delete state ID", "err", err)
}
}
// ReadPersistentStateID retrieves the id of the persistent state from the database. // ReadPersistentStateID retrieves the id of the persistent state from the database.
func ReadPersistentStateID(db ethdb.KeyValueReader) uint64 { func ReadPersistentStateID(db ethdb.KeyValueReader) uint64 {
data, _ := db.Get(persistentStateIDKey) data, _ := db.Get(persistentStateIDKey)

View file

@ -48,12 +48,16 @@ func basicRead(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
) )
defer db.Close() defer db.Close()
db.ModifyAncients(func(op ethdb.AncientWriteOp) error { if _, err := db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := 0; i < len(data); i++ { for i := 0; i < len(data); i++ {
op.AppendRaw("a", uint64(i), data[i]) if err := op.AppendRaw("a", uint64(i), data[i]); err != nil {
return err
}
} }
return nil return nil
}) }); err != nil {
t.Fatalf("Failed to write ancient data %v", err)
}
db.TruncateTail(10) db.TruncateTail(10)
db.TruncateHead(90) db.TruncateHead(90)
@ -109,12 +113,16 @@ func batchRead(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
) )
defer db.Close() defer db.Close()
db.ModifyAncients(func(op ethdb.AncientWriteOp) error { if _, err := db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
op.AppendRaw("a", uint64(i), data[i]) if err := op.AppendRaw("a", uint64(i), data[i]); err != nil {
return err
}
} }
return nil return nil
}) }); err != nil {
t.Fatalf("Failed to write ancient data %v", err)
}
db.TruncateTail(10) db.TruncateTail(10)
db.TruncateHead(90) db.TruncateHead(90)
@ -189,7 +197,9 @@ func basicWrite(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
// The ancient write to tables should be aligned // The ancient write to tables should be aligned
_, err := db.ModifyAncients(func(op ethdb.AncientWriteOp) error { _, err := db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
op.AppendRaw("a", uint64(i), dataA[i]) if err := op.AppendRaw("a", uint64(i), dataA[i]); err != nil {
return err
}
} }
return nil return nil
}) })
@ -200,8 +210,12 @@ func basicWrite(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
// Test normal ancient write // Test normal ancient write
size, err := db.ModifyAncients(func(op ethdb.AncientWriteOp) error { size, err := db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
op.AppendRaw("a", uint64(i), dataA[i]) if err := op.AppendRaw("a", uint64(i), dataA[i]); err != nil {
op.AppendRaw("b", uint64(i), dataB[i]) return err
}
if err := op.AppendRaw("b", uint64(i), dataB[i]); err != nil {
return err
}
} }
return nil return nil
}) })
@ -217,8 +231,12 @@ func basicWrite(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
db.TruncateHead(90) db.TruncateHead(90)
_, err = db.ModifyAncients(func(op ethdb.AncientWriteOp) error { _, err = db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := 90; i < 100; i++ { for i := 90; i < 100; i++ {
op.AppendRaw("a", uint64(i), dataA[i]) if err := op.AppendRaw("a", uint64(i), dataA[i]); err != nil {
op.AppendRaw("b", uint64(i), dataB[i]) return err
}
if err := op.AppendRaw("b", uint64(i), dataB[i]); err != nil {
return err
}
} }
return nil return nil
}) })
@ -227,11 +245,15 @@ func basicWrite(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
} }
// Write should work after truncating everything // Write should work after truncating everything
db.TruncateTail(0) db.TruncateHead(0)
_, err = db.ModifyAncients(func(op ethdb.AncientWriteOp) error { _, err = db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
op.AppendRaw("a", uint64(i), dataA[i]) if err := op.AppendRaw("a", uint64(i), dataA[i]); err != nil {
op.AppendRaw("b", uint64(i), dataB[i]) return err
}
if err := op.AppendRaw("b", uint64(i), dataB[i]); err != nil {
return err
}
} }
return nil return nil
}) })
@ -245,14 +267,18 @@ func nonMutable(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
defer db.Close() defer db.Close()
// We write 100 zero-bytes to the freezer and immediately mutate the slice // We write 100 zero-bytes to the freezer and immediately mutate the slice
db.ModifyAncients(func(op ethdb.AncientWriteOp) error { if _, err := db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
data := make([]byte, 100) data := make([]byte, 100)
op.AppendRaw("a", uint64(0), data) if err := op.AppendRaw("a", uint64(0), data); err != nil {
return err
}
for i := range data { for i := range data {
data[i] = 0xff data[i] = 0xff
} }
return nil return nil
}) }); err != nil {
t.Fatalf("Failed to write ancient data %v", err)
}
// Now read it. // Now read it.
data, err := db.Ancient("a", uint64(0)) data, err := db.Ancient("a", uint64(0))
if err != nil { if err != nil {
@ -275,23 +301,31 @@ func TestResettableAncientSuite(t *testing.T, newFn func(kinds []string) ethdb.R
) )
defer db.Close() defer db.Close()
db.ModifyAncients(func(op ethdb.AncientWriteOp) error { if _, err := db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
op.AppendRaw("a", uint64(i), data[i]) if err := op.AppendRaw("a", uint64(i), data[i]); err != nil {
return err
}
} }
return nil return nil
}) }); err != nil {
t.Fatalf("Failed to write ancient data %v", err)
}
db.TruncateTail(10) db.TruncateTail(10)
db.TruncateHead(90) db.TruncateHead(90)
// Ancient write should work after resetting // Ancient write should work after resetting
db.Reset() db.Reset()
db.ModifyAncients(func(op ethdb.AncientWriteOp) error { if _, err := db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
op.AppendRaw("a", uint64(i), data[i]) if err := op.AppendRaw("a", uint64(i), data[i]); err != nil {
return err
}
} }
return nil return nil
}) }); err != nil {
t.Fatalf("Failed to write ancient data %v", err)
}
}) })
} }

View file

@ -18,13 +18,17 @@ package rawdb
import ( import (
"bytes" "bytes"
"context"
"errors" "errors"
"fmt" "fmt"
"maps" "maps"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
"slices" "slices"
"strings" "strings"
"sync"
"sync/atomic"
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -32,7 +36,7 @@ import (
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/ethdb/memorydb" "github.com/ethereum/go-ethereum/ethdb/memorydb"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/olekukonko/tablewriter" "golang.org/x/sync/errgroup"
) )
var ErrDeleteRangeInterrupted = errors.New("safe delete range operation interrupted") var ErrDeleteRangeInterrupted = errors.New("safe delete range operation interrupted")
@ -363,36 +367,36 @@ func (c counter) Percentage(current uint64) string {
return fmt.Sprintf("%d", current*100/uint64(c)) return fmt.Sprintf("%d", current*100/uint64(c))
} }
// stat stores sizes and count for a parameter // stat provides lock-free statistics aggregation using atomic operations
type stat struct { type stat struct {
size common.StorageSize size uint64
count counter count uint64
} }
// Add size to the stat and increase the counter by 1 func (s *stat) empty() bool {
func (s *stat) Add(size common.StorageSize) { return atomic.LoadUint64(&s.count) == 0
s.size += size
s.count++
} }
func (s *stat) Size() string { func (s *stat) add(size common.StorageSize) {
return s.size.String() atomic.AddUint64(&s.size, uint64(size))
atomic.AddUint64(&s.count, 1)
} }
func (s *stat) Count() string { func (s *stat) sizeString() string {
return s.count.String() return common.StorageSize(atomic.LoadUint64(&s.size)).String()
}
func (s *stat) countString() string {
return counter(atomic.LoadUint64(&s.count)).String()
} }
// InspectDatabase traverses the entire database and checks the size // InspectDatabase traverses the entire database and checks the size
// of all different categories of data. // of all different categories of data.
func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
it := db.NewIterator(keyPrefix, keyStart)
defer it.Release()
var ( var (
count int64
start = time.Now() start = time.Now()
logged = time.Now() count atomic.Int64
total atomic.Uint64
// Key-value store statistics // Key-value store statistics
headers stat headers stat
@ -428,144 +432,200 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
metadata stat metadata stat
unaccounted stat unaccounted stat
// Totals
total common.StorageSize
// This map tracks example keys for unaccounted data. // This map tracks example keys for unaccounted data.
// For each unique two-byte prefix, the first unaccounted key encountered // For each unique two-byte prefix, the first unaccounted key encountered
// by the iterator will be stored. // by the iterator will be stored.
unaccountedKeys = make(map[[2]byte][]byte) unaccountedKeys = make(map[[2]byte][]byte)
unaccountedMu sync.Mutex
) )
// Inspect key-value database first.
inspectRange := func(ctx context.Context, r byte) error {
var s []byte
if len(keyStart) > 0 {
switch {
case r < keyStart[0]:
return nil
case r == keyStart[0]:
s = keyStart[1:]
default:
// entire key range is included for inspection
}
}
it := db.NewIterator(append(keyPrefix, r), s)
defer it.Release()
for it.Next() { for it.Next() {
var ( var (
key = it.Key() key = it.Key()
size = common.StorageSize(len(key) + len(it.Value())) size = common.StorageSize(len(key) + len(it.Value()))
) )
total += size total.Add(uint64(size))
count.Add(1)
switch { switch {
case bytes.HasPrefix(key, headerPrefix) && len(key) == (len(headerPrefix)+8+common.HashLength): case bytes.HasPrefix(key, headerPrefix) && len(key) == (len(headerPrefix)+8+common.HashLength):
headers.Add(size) headers.add(size)
case bytes.HasPrefix(key, blockBodyPrefix) && len(key) == (len(blockBodyPrefix)+8+common.HashLength): case bytes.HasPrefix(key, blockBodyPrefix) && len(key) == (len(blockBodyPrefix)+8+common.HashLength):
bodies.Add(size) bodies.add(size)
case bytes.HasPrefix(key, blockReceiptsPrefix) && len(key) == (len(blockReceiptsPrefix)+8+common.HashLength): case bytes.HasPrefix(key, blockReceiptsPrefix) && len(key) == (len(blockReceiptsPrefix)+8+common.HashLength):
receipts.Add(size) receipts.add(size)
case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerTDSuffix): case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerTDSuffix):
tds.Add(size) tds.add(size)
case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerHashSuffix): case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerHashSuffix):
numHashPairings.Add(size) numHashPairings.add(size)
case bytes.HasPrefix(key, headerNumberPrefix) && len(key) == (len(headerNumberPrefix)+common.HashLength): case bytes.HasPrefix(key, headerNumberPrefix) && len(key) == (len(headerNumberPrefix)+common.HashLength):
hashNumPairings.Add(size) hashNumPairings.add(size)
case IsLegacyTrieNode(key, it.Value()): case IsLegacyTrieNode(key, it.Value()):
legacyTries.Add(size) legacyTries.add(size)
case bytes.HasPrefix(key, stateIDPrefix) && len(key) == len(stateIDPrefix)+common.HashLength: case bytes.HasPrefix(key, stateIDPrefix) && len(key) == len(stateIDPrefix)+common.HashLength:
stateLookups.Add(size) stateLookups.add(size)
case IsAccountTrieNode(key): case IsAccountTrieNode(key):
accountTries.Add(size) accountTries.add(size)
case IsStorageTrieNode(key): case IsStorageTrieNode(key):
storageTries.Add(size) storageTries.add(size)
case bytes.HasPrefix(key, CodePrefix) && len(key) == len(CodePrefix)+common.HashLength: case bytes.HasPrefix(key, CodePrefix) && len(key) == len(CodePrefix)+common.HashLength:
codes.Add(size) codes.add(size)
case bytes.HasPrefix(key, txLookupPrefix) && len(key) == (len(txLookupPrefix)+common.HashLength): case bytes.HasPrefix(key, txLookupPrefix) && len(key) == (len(txLookupPrefix)+common.HashLength):
txLookups.Add(size) txLookups.add(size)
case bytes.HasPrefix(key, SnapshotAccountPrefix) && len(key) == (len(SnapshotAccountPrefix)+common.HashLength): case bytes.HasPrefix(key, SnapshotAccountPrefix) && len(key) == (len(SnapshotAccountPrefix)+common.HashLength):
accountSnaps.Add(size) accountSnaps.add(size)
case bytes.HasPrefix(key, SnapshotStoragePrefix) && len(key) == (len(SnapshotStoragePrefix)+2*common.HashLength): case bytes.HasPrefix(key, SnapshotStoragePrefix) && len(key) == (len(SnapshotStoragePrefix)+2*common.HashLength):
storageSnaps.Add(size) storageSnaps.add(size)
case bytes.HasPrefix(key, PreimagePrefix) && len(key) == (len(PreimagePrefix)+common.HashLength): case bytes.HasPrefix(key, PreimagePrefix) && len(key) == (len(PreimagePrefix)+common.HashLength):
preimages.Add(size) preimages.add(size)
case bytes.HasPrefix(key, configPrefix) && len(key) == (len(configPrefix)+common.HashLength): case bytes.HasPrefix(key, configPrefix) && len(key) == (len(configPrefix)+common.HashLength):
metadata.Add(size) metadata.add(size)
case bytes.HasPrefix(key, genesisPrefix) && len(key) == (len(genesisPrefix)+common.HashLength): case bytes.HasPrefix(key, genesisPrefix) && len(key) == (len(genesisPrefix)+common.HashLength):
metadata.Add(size) metadata.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:
cliqueSnaps.Add(size) cliqueSnaps.add(size)
// new log index // new log index
case bytes.HasPrefix(key, filterMapRowPrefix) && len(key) <= len(filterMapRowPrefix)+9: case bytes.HasPrefix(key, filterMapRowPrefix) && len(key) <= len(filterMapRowPrefix)+9:
filterMapRows.Add(size) filterMapRows.add(size)
case bytes.HasPrefix(key, filterMapLastBlockPrefix) && len(key) == len(filterMapLastBlockPrefix)+4: case bytes.HasPrefix(key, filterMapLastBlockPrefix) && len(key) == len(filterMapLastBlockPrefix)+4:
filterMapLastBlock.Add(size) filterMapLastBlock.add(size)
case bytes.HasPrefix(key, filterMapBlockLVPrefix) && len(key) == len(filterMapBlockLVPrefix)+8: case bytes.HasPrefix(key, filterMapBlockLVPrefix) && len(key) == len(filterMapBlockLVPrefix)+8:
filterMapBlockLV.Add(size) filterMapBlockLV.add(size)
// old log index (deprecated) // old log index (deprecated)
case bytes.HasPrefix(key, bloomBitsPrefix) && len(key) == (len(bloomBitsPrefix)+10+common.HashLength): case bytes.HasPrefix(key, bloomBitsPrefix) && len(key) == (len(bloomBitsPrefix)+10+common.HashLength):
bloomBits.Add(size) bloomBits.add(size)
case bytes.HasPrefix(key, bloomBitsMetaPrefix) && len(key) < len(bloomBitsMetaPrefix)+8: case bytes.HasPrefix(key, bloomBitsMetaPrefix) && len(key) < len(bloomBitsMetaPrefix)+8:
bloomBits.Add(size) bloomBits.add(size)
// Path-based historic state indexes // Path-based historic state indexes
case bytes.HasPrefix(key, StateHistoryIndexPrefix) && len(key) >= len(StateHistoryIndexPrefix)+common.HashLength: case bytes.HasPrefix(key, StateHistoryIndexPrefix) && len(key) >= len(StateHistoryIndexPrefix)+common.HashLength:
stateIndex.Add(size) stateIndex.add(size)
// Verkle trie data is detected, determine the sub-category // Verkle trie data is detected, determine the sub-category
case bytes.HasPrefix(key, VerklePrefix): case bytes.HasPrefix(key, VerklePrefix):
remain := key[len(VerklePrefix):] remain := key[len(VerklePrefix):]
switch { switch {
case IsAccountTrieNode(remain): case IsAccountTrieNode(remain):
verkleTries.Add(size) verkleTries.add(size)
case bytes.HasPrefix(remain, stateIDPrefix) && len(remain) == len(stateIDPrefix)+common.HashLength: case bytes.HasPrefix(remain, stateIDPrefix) && len(remain) == len(stateIDPrefix)+common.HashLength:
verkleStateLookups.Add(size) verkleStateLookups.add(size)
case bytes.Equal(remain, persistentStateIDKey): case bytes.Equal(remain, persistentStateIDKey):
metadata.Add(size) metadata.add(size)
case bytes.Equal(remain, trieJournalKey): case bytes.Equal(remain, trieJournalKey):
metadata.Add(size) metadata.add(size)
case bytes.Equal(remain, snapSyncStatusFlagKey): case bytes.Equal(remain, snapSyncStatusFlagKey):
metadata.Add(size) metadata.add(size)
default: default:
unaccounted.Add(size) unaccounted.add(size)
} }
// Metadata keys // Metadata keys
case slices.ContainsFunc(knownMetadataKeys, func(x []byte) bool { return bytes.Equal(x, key) }): case slices.ContainsFunc(knownMetadataKeys, func(x []byte) bool { return bytes.Equal(x, key) }):
metadata.Add(size) metadata.add(size)
default: default:
unaccounted.Add(size) unaccounted.add(size)
if len(key) >= 2 { if len(key) >= 2 {
prefix := [2]byte(key[:2]) prefix := [2]byte(key[:2])
unaccountedMu.Lock()
if _, ok := unaccountedKeys[prefix]; !ok { if _, ok := unaccountedKeys[prefix]; !ok {
unaccountedKeys[prefix] = bytes.Clone(key) unaccountedKeys[prefix] = bytes.Clone(key)
} }
unaccountedMu.Unlock()
} }
} }
count++
if count%1000 == 0 && time.Since(logged) > 8*time.Second { select {
log.Info("Inspecting database", "count", count, "elapsed", common.PrettyDuration(time.Since(start))) case <-ctx.Done():
logged = time.Now() return ctx.Err()
default:
} }
} }
return it.Error()
}
var (
eg, ctx = errgroup.WithContext(context.Background())
workers = runtime.NumCPU()
)
eg.SetLimit(workers)
// Progress reporter
done := make(chan struct{})
go func() {
ticker := time.NewTicker(8 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
log.Info("Inspecting database", "count", count.Load(), "size", common.StorageSize(total.Load()), "elapsed", common.PrettyDuration(time.Since(start)))
case <-done:
return
}
}
}()
// Inspect key-value database in parallel.
for i := 0; i < 256; i++ {
eg.Go(func() error { return inspectRange(ctx, byte(i)) })
}
if err := eg.Wait(); err != nil {
close(done)
return err
}
close(done)
// Display the database statistic of key-value store. // Display the database statistic of key-value store.
stats := [][]string{ stats := [][]string{
{"Key-Value store", "Headers", headers.Size(), headers.Count()}, {"Key-Value store", "Headers", headers.sizeString(), headers.countString()},
{"Key-Value store", "Bodies", bodies.Size(), bodies.Count()}, {"Key-Value store", "Bodies", bodies.sizeString(), bodies.countString()},
{"Key-Value store", "Receipt lists", receipts.Size(), receipts.Count()}, {"Key-Value store", "Receipt lists", receipts.sizeString(), receipts.countString()},
{"Key-Value store", "Difficulties (deprecated)", tds.Size(), tds.Count()}, {"Key-Value store", "Difficulties (deprecated)", tds.sizeString(), tds.countString()},
{"Key-Value store", "Block number->hash", numHashPairings.Size(), numHashPairings.Count()}, {"Key-Value store", "Block number->hash", numHashPairings.sizeString(), numHashPairings.countString()},
{"Key-Value store", "Block hash->number", hashNumPairings.Size(), hashNumPairings.Count()}, {"Key-Value store", "Block hash->number", hashNumPairings.sizeString(), hashNumPairings.countString()},
{"Key-Value store", "Transaction index", txLookups.Size(), txLookups.Count()}, {"Key-Value store", "Transaction index", txLookups.sizeString(), txLookups.countString()},
{"Key-Value store", "Log index filter-map rows", filterMapRows.Size(), filterMapRows.Count()}, {"Key-Value store", "Log index filter-map rows", filterMapRows.sizeString(), filterMapRows.countString()},
{"Key-Value store", "Log index last-block-of-map", filterMapLastBlock.Size(), filterMapLastBlock.Count()}, {"Key-Value store", "Log index last-block-of-map", filterMapLastBlock.sizeString(), filterMapLastBlock.countString()},
{"Key-Value store", "Log index block-lv", filterMapBlockLV.Size(), filterMapBlockLV.Count()}, {"Key-Value store", "Log index block-lv", filterMapBlockLV.sizeString(), filterMapBlockLV.countString()},
{"Key-Value store", "Log bloombits (deprecated)", bloomBits.Size(), bloomBits.Count()}, {"Key-Value store", "Log bloombits (deprecated)", bloomBits.sizeString(), bloomBits.countString()},
{"Key-Value store", "Contract codes", codes.Size(), codes.Count()}, {"Key-Value store", "Contract codes", codes.sizeString(), codes.countString()},
{"Key-Value store", "Hash trie nodes", legacyTries.Size(), legacyTries.Count()}, {"Key-Value store", "Hash trie nodes", legacyTries.sizeString(), legacyTries.countString()},
{"Key-Value store", "Path trie state lookups", stateLookups.Size(), stateLookups.Count()}, {"Key-Value store", "Path trie state lookups", stateLookups.sizeString(), stateLookups.countString()},
{"Key-Value store", "Path trie account nodes", accountTries.Size(), accountTries.Count()}, {"Key-Value store", "Path trie account nodes", accountTries.sizeString(), accountTries.countString()},
{"Key-Value store", "Path trie storage nodes", storageTries.Size(), storageTries.Count()}, {"Key-Value store", "Path trie storage nodes", storageTries.sizeString(), storageTries.countString()},
{"Key-Value store", "Path state history indexes", stateIndex.Size(), stateIndex.Count()}, {"Key-Value store", "Path state history indexes", stateIndex.sizeString(), stateIndex.countString()},
{"Key-Value store", "Verkle trie nodes", verkleTries.Size(), verkleTries.Count()}, {"Key-Value store", "Verkle trie nodes", verkleTries.sizeString(), verkleTries.countString()},
{"Key-Value store", "Verkle trie state lookups", verkleStateLookups.Size(), verkleStateLookups.Count()}, {"Key-Value store", "Verkle trie state lookups", verkleStateLookups.sizeString(), verkleStateLookups.countString()},
{"Key-Value store", "Trie preimages", preimages.Size(), preimages.Count()}, {"Key-Value store", "Trie preimages", preimages.sizeString(), preimages.countString()},
{"Key-Value store", "Account snapshot", accountSnaps.Size(), accountSnaps.Count()}, {"Key-Value store", "Account snapshot", accountSnaps.sizeString(), accountSnaps.countString()},
{"Key-Value store", "Storage snapshot", storageSnaps.Size(), storageSnaps.Count()}, {"Key-Value store", "Storage snapshot", storageSnaps.sizeString(), storageSnaps.countString()},
{"Key-Value store", "Beacon sync headers", beaconHeaders.Size(), beaconHeaders.Count()}, {"Key-Value store", "Beacon sync headers", beaconHeaders.sizeString(), beaconHeaders.countString()},
{"Key-Value store", "Clique snapshots", cliqueSnaps.Size(), cliqueSnaps.Count()}, {"Key-Value store", "Clique snapshots", cliqueSnaps.sizeString(), cliqueSnaps.countString()},
{"Key-Value store", "Singleton metadata", metadata.Size(), metadata.Count()}, {"Key-Value store", "Singleton metadata", metadata.sizeString(), metadata.countString()},
} }
// Inspect all registered append-only file store then. // Inspect all registered append-only file store then.
ancients, err := inspectFreezers(db) ancients, err := inspectFreezers(db)
if err != nil { if err != nil {
@ -580,16 +640,17 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
fmt.Sprintf("%d", ancient.count()), fmt.Sprintf("%d", ancient.count()),
}) })
} }
total += ancient.size() total.Add(uint64(ancient.size()))
} }
table := tablewriter.NewWriter(os.Stdout)
table := newTableWriter(os.Stdout)
table.SetHeader([]string{"Database", "Category", "Size", "Items"}) table.SetHeader([]string{"Database", "Category", "Size", "Items"})
table.SetFooter([]string{"", "Total", total.String(), " "}) table.SetFooter([]string{"", "Total", common.StorageSize(total.Load()).String(), fmt.Sprintf("%d", count.Load())})
table.AppendBulk(stats) table.AppendBulk(stats)
table.Render() table.Render()
if unaccounted.size > 0 { if !unaccounted.empty() {
log.Error("Database contains unaccounted data", "size", unaccounted.size, "count", unaccounted.count) log.Error("Database contains unaccounted data", "size", unaccounted.sizeString(), "count", unaccounted.countString())
for _, e := range slices.SortedFunc(maps.Values(unaccountedKeys), bytes.Compare) { for _, e := range slices.SortedFunc(maps.Values(unaccountedKeys), bytes.Compare) {
log.Error(fmt.Sprintf(" example key: %x", e)) log.Error(fmt.Sprintf(" example key: %x", e))
} }

View file

@ -0,0 +1,208 @@
// 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/>.
// TODO: naive stub implementation for tablewriter
//go:build tinygo
// +build tinygo
package rawdb
import (
"errors"
"fmt"
"io"
"strings"
)
const (
cellPadding = 1 // Number of spaces on each side of cell content
totalPadding = 2 * cellPadding // Total padding per cell. Its two because we pad equally on both sides
)
type Table struct {
out io.Writer
headers []string
footer []string
rows [][]string
}
func newTableWriter(w io.Writer) *Table {
return &Table{out: w}
}
// SetHeader sets the header row for the table. Headers define the column names
// and determine the number of columns for the entire table.
//
// All data rows and footer must have the same number of columns as the headers.
//
// Note: Headers are required - tables without headers will fail validation.
func (t *Table) SetHeader(headers []string) {
t.headers = headers
}
// SetFooter sets an optional footer row for the table.
//
// The footer must have the same number of columns as the headers, or validation will fail.
func (t *Table) SetFooter(footer []string) {
t.footer = footer
}
// AppendBulk sets all data rows for the table at once, replacing any existing rows.
//
// Each row must have the same number of columns as the headers, or validation
// will fail during Render().
func (t *Table) AppendBulk(rows [][]string) {
t.rows = rows
}
// Render outputs the complete table to the configured writer. The table is rendered
// with headers, data rows, and optional footer.
//
// If validation fails, an error message is written to the output and rendering stops.
func (t *Table) Render() {
if err := t.render(); err != nil {
fmt.Fprintf(t.out, "Error: %v\n", err)
return
}
}
func (t *Table) render() error {
if err := t.validateColumnCount(); err != nil {
return err
}
widths := t.calculateColumnWidths()
rowSeparator := t.buildRowSeparator(widths)
if len(t.headers) > 0 {
t.printRow(t.headers, widths)
fmt.Fprintln(t.out, rowSeparator)
}
for _, row := range t.rows {
t.printRow(row, widths)
}
if len(t.footer) > 0 {
fmt.Fprintln(t.out, rowSeparator)
t.printRow(t.footer, widths)
}
return nil
}
// validateColumnCount checks that all rows and footer match the header column count
func (t *Table) validateColumnCount() error {
if len(t.headers) == 0 {
return errors.New("table must have headers")
}
expectedCols := len(t.headers)
// Check all rows have same column count as headers
for i, row := range t.rows {
if len(row) != expectedCols {
return fmt.Errorf("row %d has %d columns, expected %d", i, len(row), expectedCols)
}
}
// Check footer has same column count as headers (if present)
footerPresent := len(t.footer) > 0
if footerPresent && len(t.footer) != expectedCols {
return fmt.Errorf("footer has %d columns, expected %d", len(t.footer), expectedCols)
}
return nil
}
// calculateColumnWidths determines the minimum width needed for each column.
//
// This is done by finding the longest content in each column across headers, rows, and footer.
//
// Returns an int slice where widths[i] is the display width for column i (including padding).
func (t *Table) calculateColumnWidths() []int {
// Headers define the number of columns
cols := len(t.headers)
if cols == 0 {
return nil
}
// Track maximum content width for each column (before padding)
widths := make([]int, cols)
// Start with header widths
for i, h := range t.headers {
widths[i] = len(h)
}
// Find max width needed for data cells in each column
for _, row := range t.rows {
for i, cell := range row {
widths[i] = max(widths[i], len(cell))
}
}
// Find max width needed for footer in each column
for i, f := range t.footer {
widths[i] = max(widths[i], len(f))
}
for i := range widths {
widths[i] += totalPadding
}
return widths
}
// buildRowSeparator creates a horizontal line to separate table rows.
//
// It generates a string with dashes (-) for each column width, joined by plus signs (+).
//
// Example output: "----------+--------+-----------"
func (t *Table) buildRowSeparator(widths []int) string {
parts := make([]string, len(widths))
for i, w := range widths {
parts[i] = strings.Repeat("-", w)
}
return strings.Join(parts, "+")
}
// printRow outputs a single row to the table writer.
//
// Each cell is padded with spaces and separated by pipe characters (|).
//
// Example output: " Database | Size | Items "
func (t *Table) printRow(row []string, widths []int) {
for i, cell := range row {
if i > 0 {
fmt.Fprint(t.out, "|")
}
// Calculate centering pad without padding
contentWidth := widths[i] - totalPadding
cellLen := len(cell)
leftPadCentering := (contentWidth - cellLen) / 2
rightPadCentering := contentWidth - cellLen - leftPadCentering
// Build padded cell with centering
leftPadding := strings.Repeat(" ", cellPadding+leftPadCentering)
rightPadding := strings.Repeat(" ", cellPadding+rightPadCentering)
fmt.Fprintf(t.out, "%s%s%s", leftPadding, cell, rightPadding)
}
fmt.Fprintln(t.out)
}

View file

@ -0,0 +1,124 @@
// 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/>.
//go:build tinygo
// +build tinygo
package rawdb
import (
"bytes"
"strings"
"testing"
)
func TestTableWriterTinyGo(t *testing.T) {
var buf bytes.Buffer
table := newTableWriter(&buf)
headers := []string{"Database", "Size", "Items", "Status"}
rows := [][]string{
{"chaindata", "2.5 GB", "1,234,567", "Active"},
{"state", "890 MB", "456,789", "Active"},
{"ancient", "15.2 GB", "2,345,678", "Readonly"},
{"logs", "120 MB", "89,012", "Active"},
}
footer := []string{"Total", "18.71 GB", "4,125,046", "-"}
table.SetHeader(headers)
table.AppendBulk(rows)
table.SetFooter(footer)
table.Render()
output := buf.String()
t.Logf("Table output using custom stub implementation:\n%s", output)
}
func TestTableWriterValidationErrors(t *testing.T) {
// Test missing headers
t.Run("MissingHeaders", func(t *testing.T) {
var buf bytes.Buffer
table := newTableWriter(&buf)
rows := [][]string{{"x", "y", "z"}}
table.AppendBulk(rows)
table.Render()
output := buf.String()
if !strings.Contains(output, "table must have headers") {
t.Errorf("Expected error for missing headers, got: %s", output)
}
})
t.Run("NotEnoughRowColumns", func(t *testing.T) {
var buf bytes.Buffer
table := newTableWriter(&buf)
headers := []string{"A", "B", "C"}
badRows := [][]string{
{"x", "y"}, // Missing column
}
table.SetHeader(headers)
table.AppendBulk(badRows)
table.Render()
output := buf.String()
if !strings.Contains(output, "row 0 has 2 columns, expected 3") {
t.Errorf("Expected validation error for row 0, got: %s", output)
}
})
t.Run("TooManyRowColumns", func(t *testing.T) {
var buf bytes.Buffer
table := newTableWriter(&buf)
headers := []string{"A", "B", "C"}
badRows := [][]string{
{"p", "q", "r", "s"}, // Extra column
}
table.SetHeader(headers)
table.AppendBulk(badRows)
table.Render()
output := buf.String()
if !strings.Contains(output, "row 0 has 4 columns, expected 3") {
t.Errorf("Expected validation error for row 0, got: %s", output)
}
})
// Test mismatched footer columns
t.Run("MismatchedFooterColumns", func(t *testing.T) {
var buf bytes.Buffer
table := newTableWriter(&buf)
headers := []string{"A", "B", "C"}
rows := [][]string{{"x", "y", "z"}}
footer := []string{"total", "sum"} // Missing column
table.SetHeader(headers)
table.AppendBulk(rows)
table.SetFooter(footer)
table.Render()
output := buf.String()
if !strings.Contains(output, "footer has 2 columns, expected 3") {
t.Errorf("Expected validation error for footer, got: %s", output)
}
})
}

View file

@ -0,0 +1,33 @@
// 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/>.
//go:build !tinygo
// +build !tinygo
package rawdb
import (
"io"
"github.com/olekukonko/tablewriter"
)
// Re-export the real tablewriter types and functions
type Table = tablewriter.Table
func newTableWriter(w io.Writer) *Table {
return tablewriter.NewWriter(w)
}

View file

@ -95,7 +95,7 @@ var (
uncleanShutdownKey = []byte("unclean-shutdown") // config prefix for the db uncleanShutdownKey = []byte("unclean-shutdown") // config prefix for the db
// transitionStatusKey tracks the eth2 transition status. // transitionStatusKey tracks the eth2 transition status.
transitionStatusKey = []byte("eth2-transition") transitionStatusKey = []byte("eth2-transition") // deprecated
// snapSyncStatusFlagKey flags that status of snap sync. // snapSyncStatusFlagKey flags that status of snap sync.
snapSyncStatusFlagKey = []byte("SnapSyncStatus") snapSyncStatusFlagKey = []byte("SnapSyncStatus")
@ -384,21 +384,48 @@ func accountHistoryIndexKey(addressHash common.Hash) []byte {
// storageHistoryIndexKey = StateHistoryStorageMetadataPrefix + addressHash + storageHash // storageHistoryIndexKey = StateHistoryStorageMetadataPrefix + addressHash + storageHash
func storageHistoryIndexKey(addressHash common.Hash, storageHash common.Hash) []byte { func storageHistoryIndexKey(addressHash common.Hash, storageHash common.Hash) []byte {
return append(append(StateHistoryStorageMetadataPrefix, addressHash.Bytes()...), storageHash.Bytes()...) totalLen := len(StateHistoryStorageMetadataPrefix) + 2*common.HashLength
out := make([]byte, totalLen)
off := 0
off += copy(out[off:], StateHistoryStorageMetadataPrefix)
off += copy(out[off:], addressHash.Bytes())
copy(out[off:], storageHash.Bytes())
return out
} }
// accountHistoryIndexBlockKey = StateHistoryAccountBlockPrefix + addressHash + blockID // accountHistoryIndexBlockKey = StateHistoryAccountBlockPrefix + addressHash + blockID
func accountHistoryIndexBlockKey(addressHash common.Hash, blockID uint32) []byte { func accountHistoryIndexBlockKey(addressHash common.Hash, blockID uint32) []byte {
var buf [4]byte var buf4 [4]byte
binary.BigEndian.PutUint32(buf[:], blockID) binary.BigEndian.PutUint32(buf4[:], blockID)
return append(append(StateHistoryAccountBlockPrefix, addressHash.Bytes()...), buf[:]...)
totalLen := len(StateHistoryAccountBlockPrefix) + common.HashLength + 4
out := make([]byte, totalLen)
off := 0
off += copy(out[off:], StateHistoryAccountBlockPrefix)
off += copy(out[off:], addressHash.Bytes())
copy(out[off:], buf4[:])
return out
} }
// storageHistoryIndexBlockKey = StateHistoryStorageBlockPrefix + addressHash + storageHash + blockID // storageHistoryIndexBlockKey = StateHistoryStorageBlockPrefix + addressHash + storageHash + blockID
func storageHistoryIndexBlockKey(addressHash common.Hash, storageHash common.Hash, blockID uint32) []byte { func storageHistoryIndexBlockKey(addressHash common.Hash, storageHash common.Hash, blockID uint32) []byte {
var buf [4]byte var buf4 [4]byte
binary.BigEndian.PutUint32(buf[:], blockID) binary.BigEndian.PutUint32(buf4[:], blockID)
return append(append(append(StateHistoryStorageBlockPrefix, addressHash.Bytes()...), storageHash.Bytes()...), buf[:]...)
totalLen := len(StateHistoryStorageBlockPrefix) + 2*common.HashLength + 4
out := make([]byte, totalLen)
off := 0
off += copy(out[off:], StateHistoryStorageBlockPrefix)
off += copy(out[off:], addressHash.Bytes())
off += copy(out[off:], storageHash.Bytes())
copy(out[off:], buf4[:])
return out
} }
// transitionStateKey = transitionStatusKey + hash // transitionStateKey = transitionStatusKey + hash

View file

@ -81,11 +81,19 @@ type Trie interface {
// be returned. // be returned.
GetAccount(address common.Address) (*types.StateAccount, error) GetAccount(address common.Address) (*types.StateAccount, error)
// PrefetchAccount attempts to resolve specific accounts from the database
// to accelerate subsequent trie operations.
PrefetchAccount([]common.Address) error
// GetStorage returns the value for key stored in the trie. The value bytes // GetStorage returns the value for key stored in the trie. The value bytes
// must not be modified by the caller. If a node was not found in the database, // must not be modified by the caller. If a node was not found in the database,
// a trie.MissingNodeError is returned. // a trie.MissingNodeError is returned.
GetStorage(addr common.Address, key []byte) ([]byte, error) GetStorage(addr common.Address, key []byte) ([]byte, error)
// PrefetchStorage attempts to resolve specific storage slots from the database
// to accelerate subsequent trie operations.
PrefetchStorage(addr common.Address, keys [][]byte) error
// UpdateAccount abstracts an account write to the trie. It encodes the // UpdateAccount abstracts an account write to the trie. It encodes the
// provided account object with associated algorithm and then updates it // provided account object with associated algorithm and then updates it
// in the trie with provided address. // in the trie with provided address.
@ -122,7 +130,7 @@ type Trie interface {
// Witness returns a set containing all trie nodes that have been accessed. // Witness returns a set containing all trie nodes that have been accessed.
// The returned map could be nil if the witness is empty. // The returned map could be nil if the witness is empty.
Witness() map[string]struct{} Witness() map[string][]byte
// NodeIterator returns an iterator that returns nodes of the trie. Iteration // NodeIterator returns an iterator that returns nodes of the trie. Iteration
// starts at the key after the given start key. And error will be returned // starts at the key after the given start key. And error will be returned

View file

@ -160,11 +160,8 @@ func prune(snaptree *snapshot.Tree, root common.Hash, maindb ethdb.Database, sta
var eta time.Duration // Realistically will never remain uninited var eta time.Duration // Realistically will never remain uninited
if done := binary.BigEndian.Uint64(key[:8]); done > 0 { if done := binary.BigEndian.Uint64(key[:8]); done > 0 {
var ( left := math.MaxUint64 - binary.BigEndian.Uint64(key[:8])
left = math.MaxUint64 - binary.BigEndian.Uint64(key[:8]) eta = common.CalculateETA(done, left, time.Since(pstart))
speed = done/uint64(time.Since(pstart)/time.Millisecond+1) + 1 // +1s to avoid division by zero
)
eta = time.Duration(left/speed) * time.Millisecond
} }
if time.Since(logged) > 8*time.Second { if time.Since(logged) > 8*time.Second {
log.Info("Pruning state data", "nodes", count, "skipped", skipped, "size", size, log.Info("Pruning state data", "nodes", count, "skipped", skipped, "size", size,

View file

@ -232,7 +232,7 @@ type trieReader struct {
lock sync.Mutex // Lock for protecting concurrent read lock sync.Mutex // Lock for protecting concurrent read
} }
// trieReader constructs a trie reader of the specific state. An error will be // newTrieReader constructs a trie reader of the specific state. An error will be
// returned if the associated trie specified by root is not existent. // returned if the associated trie specified by root is not existent.
func newTrieReader(root common.Hash, db *triedb.Database, cache *utils.PointCache) (*trieReader, error) { func newTrieReader(root common.Hash, db *triedb.Database, cache *utils.PointCache) (*trieReader, error) {
var ( var (
@ -253,7 +253,7 @@ func newTrieReader(root common.Hash, db *triedb.Database, cache *utils.PointCach
if err != nil { if err != nil {
return nil, err return nil, err
} }
tr = trie.NewTransitionTree(mpt, tr.(*trie.VerkleTrie), false) tr = trie.NewTransitionTrie(mpt, tr.(*trie.VerkleTrie), false)
} }
} }
if err != nil { if err != nil {

View file

@ -172,19 +172,15 @@ func (stat *generateStats) report() {
if done := binary.BigEndian.Uint64(stat.head[:8]) / stat.accounts; done > 0 { if done := binary.BigEndian.Uint64(stat.head[:8]) / stat.accounts; done > 0 {
var ( var (
left = (math.MaxUint64 - binary.BigEndian.Uint64(stat.head[:8])) / stat.accounts left = (math.MaxUint64 - binary.BigEndian.Uint64(stat.head[:8])) / stat.accounts
speed = done/uint64(time.Since(stat.start)/time.Millisecond+1) + 1 // +1s to avoid division by zero eta = common.CalculateETA(done, left, time.Since(stat.start))
eta = time.Duration(left/speed) * time.Millisecond
) )
// If there are large contract crawls in progress, estimate their finish time // If there are large contract crawls in progress, estimate their finish time
for acc, head := range stat.slotsHead { for acc, head := range stat.slotsHead {
start := stat.slotsStart[acc] start := stat.slotsStart[acc]
if done := binary.BigEndian.Uint64(head[:8]); done > 0 { if done := binary.BigEndian.Uint64(head[:8]); done > 0 {
var ( left := math.MaxUint64 - binary.BigEndian.Uint64(head[:8])
left = math.MaxUint64 - binary.BigEndian.Uint64(head[:8])
speed = done/uint64(time.Since(start)/time.Millisecond+1) + 1 // +1s to avoid division by zero
)
// Override the ETA if larger than the largest until now // Override the ETA if larger than the largest until now
if slotETA := time.Duration(left/speed) * time.Millisecond; eta < slotETA { if slotETA := common.CalculateETA(done, left, time.Since(start)); eta < slotETA {
eta = slotETA eta = slotETA
} }
} }

638
core/state/state_sizer.go Normal file
View file

@ -0,0 +1,638 @@
// 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 state
import (
"container/heap"
"errors"
"fmt"
"maps"
"runtime"
"slices"
"sync"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/triedb"
"golang.org/x/sync/errgroup"
)
const (
statEvictThreshold = 128 // the depth of statistic to be preserved
)
// Database key scheme for states.
var (
accountKeySize = int64(len(rawdb.SnapshotAccountPrefix) + common.HashLength)
storageKeySize = int64(len(rawdb.SnapshotStoragePrefix) + common.HashLength*2)
accountTrienodePrefixSize = int64(len(rawdb.TrieNodeAccountPrefix))
storageTrienodePrefixSize = int64(len(rawdb.TrieNodeStoragePrefix) + common.HashLength)
codeKeySize = int64(len(rawdb.CodePrefix) + common.HashLength)
)
// SizeStats represents either the current state size statistics or the size
// differences resulting from a state transition.
type SizeStats struct {
StateRoot common.Hash // State root hash at the time of measurement
BlockNumber uint64 // Associated block number at the time of measurement
Accounts int64 // Total number of accounts in the state
AccountBytes int64 // Total storage size used by all account data (in bytes)
Storages int64 // Total number of storage slots across all accounts
StorageBytes int64 // Total storage size used by all storage slot data (in bytes)
AccountTrienodes int64 // Total number of account trie nodes in the state
AccountTrienodeBytes int64 // Total storage size occupied by account trie nodes (in bytes)
StorageTrienodes int64 // Total number of storage trie nodes in the state
StorageTrienodeBytes int64 // Total storage size occupied by storage trie nodes (in bytes)
ContractCodes int64 // Total number of contract codes in the state
ContractCodeBytes int64 // Total size of all contract code (in bytes)
}
func (s SizeStats) String() string {
return fmt.Sprintf("Accounts: %d(%s), Storages: %d(%s), AccountTrienodes: %d(%s), StorageTrienodes: %d(%s), Codes: %d(%s)",
s.Accounts, common.StorageSize(s.AccountBytes),
s.Storages, common.StorageSize(s.StorageBytes),
s.AccountTrienodes, common.StorageSize(s.AccountTrienodeBytes),
s.StorageTrienodes, common.StorageSize(s.StorageTrienodeBytes),
s.ContractCodes, common.StorageSize(s.ContractCodeBytes),
)
}
// add applies the given state diffs and produces a new version of the statistics.
func (s SizeStats) add(diff SizeStats) SizeStats {
s.StateRoot = diff.StateRoot
s.BlockNumber = diff.BlockNumber
s.Accounts += diff.Accounts
s.AccountBytes += diff.AccountBytes
s.Storages += diff.Storages
s.StorageBytes += diff.StorageBytes
s.AccountTrienodes += diff.AccountTrienodes
s.AccountTrienodeBytes += diff.AccountTrienodeBytes
s.StorageTrienodes += diff.StorageTrienodes
s.StorageTrienodeBytes += diff.StorageTrienodeBytes
s.ContractCodes += diff.ContractCodes
s.ContractCodeBytes += diff.ContractCodeBytes
return s
}
// calSizeStats measures the state size changes of the provided state update.
func calSizeStats(update *stateUpdate) (SizeStats, error) {
stats := SizeStats{
BlockNumber: update.blockNumber,
StateRoot: update.root,
}
// Measure the account changes
for addr, oldValue := range update.accountsOrigin {
addrHash := crypto.Keccak256Hash(addr.Bytes())
newValue, exists := update.accounts[addrHash]
if !exists {
return SizeStats{}, fmt.Errorf("account %x not found", addr)
}
oldLen, newLen := len(oldValue), len(newValue)
switch {
case oldLen > 0 && newLen == 0:
// Account deletion
stats.Accounts -= 1
stats.AccountBytes -= accountKeySize + int64(oldLen)
case oldLen == 0 && newLen > 0:
// Account creation
stats.Accounts += 1
stats.AccountBytes += accountKeySize + int64(newLen)
default:
// Account update
stats.AccountBytes += int64(newLen - oldLen)
}
}
// Measure storage changes
for addr, slots := range update.storagesOrigin {
addrHash := crypto.Keccak256Hash(addr.Bytes())
subset, exists := update.storages[addrHash]
if !exists {
return SizeStats{}, fmt.Errorf("storage %x not found", addr)
}
for key, oldValue := range slots {
var (
exists bool
newValue []byte
)
if update.rawStorageKey {
newValue, exists = subset[crypto.Keccak256Hash(key.Bytes())]
} else {
newValue, exists = subset[key]
}
if !exists {
return SizeStats{}, fmt.Errorf("storage slot %x-%x not found", addr, key)
}
oldLen, newLen := len(oldValue), len(newValue)
switch {
case oldLen > 0 && newLen == 0:
// Storage deletion
stats.Storages -= 1
stats.StorageBytes -= storageKeySize + int64(oldLen)
case oldLen == 0 && newLen > 0:
// Storage creation
stats.Storages += 1
stats.StorageBytes += storageKeySize + int64(newLen)
default:
// Storage update
stats.StorageBytes += int64(newLen - oldLen)
}
}
}
// Measure trienode changes
for owner, subset := range update.nodes.Sets {
var (
keyPrefix int64
isAccount = owner == (common.Hash{})
)
if isAccount {
keyPrefix = accountTrienodePrefixSize
} else {
keyPrefix = storageTrienodePrefixSize
}
// Iterate over Origins since every modified node has an origin entry
for path, oldNode := range subset.Origins {
newNode, exists := subset.Nodes[path]
if !exists {
return SizeStats{}, fmt.Errorf("node %x-%v not found", owner, path)
}
keySize := keyPrefix + int64(len(path))
switch {
case len(oldNode) > 0 && len(newNode.Blob) == 0:
// Node deletion
if isAccount {
stats.AccountTrienodes -= 1
stats.AccountTrienodeBytes -= keySize + int64(len(oldNode))
} else {
stats.StorageTrienodes -= 1
stats.StorageTrienodeBytes -= keySize + int64(len(oldNode))
}
case len(oldNode) == 0 && len(newNode.Blob) > 0:
// Node creation
if isAccount {
stats.AccountTrienodes += 1
stats.AccountTrienodeBytes += keySize + int64(len(newNode.Blob))
} else {
stats.StorageTrienodes += 1
stats.StorageTrienodeBytes += keySize + int64(len(newNode.Blob))
}
default:
// Node update
if isAccount {
stats.AccountTrienodeBytes += int64(len(newNode.Blob) - len(oldNode))
} else {
stats.StorageTrienodeBytes += int64(len(newNode.Blob) - len(oldNode))
}
}
}
}
// Measure code changes. Note that the reported contract code size may be slightly
// inaccurate due to database deduplication (code is stored by its hash). However,
// this deviation is negligible and acceptable for measurement purposes.
for _, code := range update.codes {
stats.ContractCodes += 1
stats.ContractCodeBytes += codeKeySize + int64(len(code.blob))
}
return stats, nil
}
type stateSizeQuery struct {
root *common.Hash // nil means latest
err error // non-nil if the state size is not yet initialized
result chan *SizeStats // nil means the state is unknown
}
// SizeTracker handles the state size initialization and tracks of state size metrics.
type SizeTracker struct {
db ethdb.KeyValueStore
triedb *triedb.Database
abort chan struct{}
aborted chan struct{}
updateCh chan *stateUpdate
queryCh chan *stateSizeQuery
}
// NewSizeTracker creates a new state size tracker and starts it automatically
func NewSizeTracker(db ethdb.KeyValueStore, triedb *triedb.Database) (*SizeTracker, error) {
if triedb.Scheme() != rawdb.PathScheme {
return nil, errors.New("state size tracker is not compatible with hash mode")
}
t := &SizeTracker{
db: db,
triedb: triedb,
abort: make(chan struct{}),
aborted: make(chan struct{}),
updateCh: make(chan *stateUpdate),
queryCh: make(chan *stateSizeQuery),
}
go t.run()
return t, nil
}
func (t *SizeTracker) Stop() {
close(t.abort)
<-t.aborted
}
// sizeStatsHeap is a heap.Interface implementation over statesize statistics for
// retrieving the oldest statistics for eviction.
type sizeStatsHeap []SizeStats
func (h sizeStatsHeap) Len() int { return len(h) }
func (h sizeStatsHeap) Less(i, j int) bool { return h[i].BlockNumber < h[j].BlockNumber }
func (h sizeStatsHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *sizeStatsHeap) Push(x any) {
*h = append(*h, x.(SizeStats))
}
func (h *sizeStatsHeap) Pop() any {
old := *h
n := len(old)
x := old[n-1]
*h = old[0 : n-1]
return x
}
// run performs the state size initialization and handles updates
func (t *SizeTracker) run() {
defer close(t.aborted)
var last common.Hash
stats, err := t.init() // launch background thread for state size init
if err != nil {
return
}
h := sizeStatsHeap(slices.Collect(maps.Values(stats)))
heap.Init(&h)
for {
select {
case u := <-t.updateCh:
base, found := stats[u.originRoot]
if !found {
log.Debug("Ignored the state size without parent", "parent", u.originRoot, "root", u.root, "number", u.blockNumber)
continue
}
diff, err := calSizeStats(u)
if err != nil {
continue
}
stat := base.add(diff)
stats[u.root] = stat
last = u.root
heap.Push(&h, stats[u.root])
for u.blockNumber-h[0].BlockNumber > statEvictThreshold {
delete(stats, h[0].StateRoot)
heap.Pop(&h)
}
log.Debug("Update state size", "number", stat.BlockNumber, "root", stat.StateRoot, "stat", stat)
case r := <-t.queryCh:
var root common.Hash
if r.root != nil {
root = *r.root
} else {
root = last
}
if s, ok := stats[root]; ok {
r.result <- &s
} else {
r.result <- nil
}
case <-t.abort:
return
}
}
}
type buildResult struct {
stat SizeStats
root common.Hash
blockNumber uint64
elapsed time.Duration
err error
}
func (t *SizeTracker) init() (map[common.Hash]SizeStats, error) {
// Wait for snapshot completion and then init
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
wait:
for {
select {
case <-ticker.C:
if t.triedb.SnapshotCompleted() {
break wait
}
case <-t.updateCh:
continue
case r := <-t.queryCh:
r.err = errors.New("state size is not initialized yet")
r.result <- nil
case <-t.abort:
return nil, errors.New("size tracker closed")
}
}
var (
updates = make(map[common.Hash]*stateUpdate)
children = make(map[common.Hash][]common.Hash)
done chan buildResult
)
for {
select {
case u := <-t.updateCh:
updates[u.root] = u
children[u.originRoot] = append(children[u.originRoot], u.root)
log.Debug("Received state update", "root", u.root, "blockNumber", u.blockNumber)
case r := <-t.queryCh:
r.err = errors.New("state size is not initialized yet")
r.result <- nil
case <-ticker.C:
// Only check timer if build hasn't started yet
if done != nil {
continue
}
root := rawdb.ReadSnapshotRoot(t.db)
if root == (common.Hash{}) {
continue
}
entry, exists := updates[root]
if !exists {
continue
}
done = make(chan buildResult)
go t.build(entry.root, entry.blockNumber, done)
log.Info("Measuring persistent state size", "root", root.Hex(), "number", entry.blockNumber)
case result := <-done:
if result.err != nil {
return nil, result.err
}
var (
stats = make(map[common.Hash]SizeStats)
apply func(root common.Hash, stat SizeStats) error
)
apply = func(root common.Hash, base SizeStats) error {
for _, child := range children[root] {
entry, ok := updates[child]
if !ok {
return fmt.Errorf("the state update is not found, %x", child)
}
diff, err := calSizeStats(entry)
if err != nil {
return err
}
stats[child] = base.add(diff)
if err := apply(child, stats[child]); err != nil {
return err
}
}
return nil
}
if err := apply(result.root, result.stat); err != nil {
return nil, err
}
// Set initial latest stats
stats[result.root] = result.stat
log.Info("Measured persistent state size", "root", result.root, "number", result.blockNumber, "stat", result.stat, "elapsed", common.PrettyDuration(result.elapsed))
return stats, nil
case <-t.abort:
return nil, errors.New("size tracker closed")
}
}
}
func (t *SizeTracker) build(root common.Hash, blockNumber uint64, done chan buildResult) {
// Metrics will be directly updated by each goroutine
var (
accounts, accountBytes int64
storages, storageBytes int64
codes, codeBytes int64
accountTrienodes, accountTrienodeBytes int64
storageTrienodes, storageTrienodeBytes int64
group errgroup.Group
start = time.Now()
)
// Start all table iterations concurrently with direct metric updates
group.Go(func() error {
count, bytes, err := t.iterateTableParallel(t.abort, rawdb.SnapshotAccountPrefix, "account")
if err != nil {
return err
}
accounts, accountBytes = count, bytes
return nil
})
group.Go(func() error {
count, bytes, err := t.iterateTableParallel(t.abort, rawdb.SnapshotStoragePrefix, "storage")
if err != nil {
return err
}
storages, storageBytes = count, bytes
return nil
})
group.Go(func() error {
count, bytes, err := t.iterateTableParallel(t.abort, rawdb.TrieNodeAccountPrefix, "accountnode")
if err != nil {
return err
}
accountTrienodes, accountTrienodeBytes = count, bytes
return nil
})
group.Go(func() error {
count, bytes, err := t.iterateTableParallel(t.abort, rawdb.TrieNodeStoragePrefix, "storagenode")
if err != nil {
return err
}
storageTrienodes, storageTrienodeBytes = count, bytes
return nil
})
group.Go(func() error {
count, bytes, err := t.iterateTable(t.abort, rawdb.CodePrefix, "contractcode")
if err != nil {
return err
}
codes, codeBytes = count, bytes
return nil
})
// Wait for all goroutines to complete
if err := group.Wait(); err != nil {
done <- buildResult{err: err}
} else {
stat := SizeStats{
StateRoot: root,
BlockNumber: blockNumber,
Accounts: accounts,
AccountBytes: accountBytes,
Storages: storages,
StorageBytes: storageBytes,
AccountTrienodes: accountTrienodes,
AccountTrienodeBytes: accountTrienodeBytes,
StorageTrienodes: storageTrienodes,
StorageTrienodeBytes: storageTrienodeBytes,
ContractCodes: codes,
ContractCodeBytes: codeBytes,
}
done <- buildResult{
root: root,
blockNumber: blockNumber,
stat: stat,
elapsed: time.Since(start),
}
}
}
// iterateTable performs iteration over a specific table and returns the results.
func (t *SizeTracker) iterateTable(closed chan struct{}, prefix []byte, name string) (int64, int64, error) {
var (
start = time.Now()
logged = time.Now()
count, bytes int64
)
iter := t.db.NewIterator(prefix, nil)
defer iter.Release()
log.Debug("Iterating state", "category", name)
for iter.Next() {
count++
bytes += int64(len(iter.Key()) + len(iter.Value()))
if time.Since(logged) > time.Second*8 {
logged = time.Now()
select {
case <-closed:
log.Debug("State iteration cancelled", "category", name)
return 0, 0, errors.New("size tracker closed")
default:
log.Debug("Iterating state", "category", name, "count", count, "size", common.StorageSize(bytes))
}
}
}
// Check for iterator errors
if err := iter.Error(); err != nil {
log.Error("Iterator error", "category", name, "err", err)
return 0, 0, err
}
log.Debug("Finished state iteration", "category", name, "count", count, "size", common.StorageSize(bytes), "elapsed", common.PrettyDuration(time.Since(start)))
return count, bytes, nil
}
// iterateTableParallel performs parallel iteration over a table by splitting into
// hex ranges. For storage tables, it splits on the first byte of the account hash
// (after the prefix).
func (t *SizeTracker) iterateTableParallel(closed chan struct{}, prefix []byte, name string) (int64, int64, error) {
var (
totalCount int64
totalBytes int64
start = time.Now()
workers = runtime.NumCPU()
group errgroup.Group
mu sync.Mutex
)
group.SetLimit(workers)
log.Debug("Starting parallel state iteration", "category", name, "workers", workers)
if len(prefix) > 0 {
if blob, err := t.db.Get(prefix); err == nil && len(blob) > 0 {
// If there's a direct hit on the prefix, include it in the stats
totalCount = 1
totalBytes = int64(len(prefix) + len(blob))
}
}
for i := 0; i < 256; i++ {
h := byte(i)
group.Go(func() error {
count, bytes, err := t.iterateTable(closed, slices.Concat(prefix, []byte{h}), fmt.Sprintf("%s-%02x", name, h))
if err != nil {
return err
}
mu.Lock()
totalCount += count
totalBytes += bytes
mu.Unlock()
return nil
})
}
if err := group.Wait(); err != nil {
return 0, 0, err
}
log.Debug("Finished parallel state iteration", "category", name, "count", totalCount, "size", common.StorageSize(totalBytes), "elapsed", common.PrettyDuration(time.Since(start)))
return totalCount, totalBytes, nil
}
// Notify is an async method used to send the state update to the size tracker.
// It ignores empty updates (where no state changes occurred).
// If the channel is full, it drops the update to avoid blocking.
func (t *SizeTracker) Notify(update *stateUpdate) {
if update == nil || update.empty() {
return
}
select {
case t.updateCh <- update:
case <-t.abort:
return
}
}
// Query returns the state size specified by the root, or nil if not available.
// If the root is nil, query the size of latest chain head;
// If the root is non-nil, query the size of the specified state;
func (t *SizeTracker) Query(root *common.Hash) (*SizeStats, error) {
r := &stateSizeQuery{
root: root,
result: make(chan *SizeStats, 1),
}
select {
case <-t.aborted:
return nil, errors.New("state sizer has been closed")
case t.queryCh <- r:
return <-r.result, r.err
}
}

View file

@ -0,0 +1,231 @@
// 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 state
import (
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/triedb"
"github.com/ethereum/go-ethereum/triedb/pathdb"
"github.com/holiman/uint256"
)
func TestSizeTracker(t *testing.T) {
db := rawdb.NewMemoryDatabase()
defer db.Close()
tdb := triedb.NewDatabase(db, &triedb.Config{PathDB: pathdb.Defaults})
sdb := NewDatabase(tdb, nil)
// Generate 50 blocks to establish a baseline
baselineBlockNum := uint64(50)
currentRoot := types.EmptyRootHash
addr1 := common.BytesToAddress([]byte{1, 0, 0, 1})
addr2 := common.BytesToAddress([]byte{1, 0, 0, 2})
addr3 := common.BytesToAddress([]byte{1, 0, 0, 3})
// Create initial state with fixed accounts
state, _ := New(currentRoot, sdb)
state.AddBalance(addr1, uint256.NewInt(1000), tracing.BalanceChangeUnspecified)
state.SetNonce(addr1, 1, tracing.NonceChangeUnspecified)
state.SetState(addr1, common.HexToHash("0x1111"), common.HexToHash("0xaaaa"))
state.SetState(addr1, common.HexToHash("0x2222"), common.HexToHash("0xbbbb"))
state.AddBalance(addr2, uint256.NewInt(2000), tracing.BalanceChangeUnspecified)
state.SetNonce(addr2, 2, tracing.NonceChangeUnspecified)
state.SetCode(addr2, []byte{0x60, 0x80, 0x60, 0x40, 0x52}, tracing.CodeChangeUnspecified)
state.AddBalance(addr3, uint256.NewInt(3000), tracing.BalanceChangeUnspecified)
state.SetNonce(addr3, 3, tracing.NonceChangeUnspecified)
currentRoot, _, err := state.CommitWithUpdate(1, true, false)
if err != nil {
t.Fatalf("Failed to commit initial state: %v", err)
}
if err := tdb.Commit(currentRoot, false); err != nil {
t.Fatalf("Failed to commit initial trie: %v", err)
}
for i := 1; i < 50; i++ { // blocks 2-50
blockNum := uint64(i + 1)
newState, err := New(currentRoot, sdb)
if err != nil {
t.Fatalf("Failed to create new state at block %d: %v", blockNum, err)
}
testAddr := common.BigToAddress(uint256.NewInt(uint64(i + 100)).ToBig())
newState.AddBalance(testAddr, uint256.NewInt(uint64((i+1)*1000)), tracing.BalanceChangeUnspecified)
newState.SetNonce(testAddr, uint64(i+10), tracing.NonceChangeUnspecified)
if i%2 == 0 {
newState.SetState(addr1, common.BigToHash(uint256.NewInt(uint64(i+0x1000)).ToBig()), common.BigToHash(uint256.NewInt(uint64(i+0x2000)).ToBig()))
}
if i%3 == 0 {
newState.SetCode(testAddr, []byte{byte(i), 0x60, 0x80, byte(i + 1), 0x52}, tracing.CodeChangeUnspecified)
}
root, _, err := newState.CommitWithUpdate(blockNum, true, false)
if err != nil {
t.Fatalf("Failed to commit state at block %d: %v", blockNum, err)
}
if err := tdb.Commit(root, false); err != nil {
t.Fatalf("Failed to commit trie at block %d: %v", blockNum, err)
}
currentRoot = root
}
baselineRoot := currentRoot
// Wait for snapshot completion
for !tdb.SnapshotCompleted() {
time.Sleep(100 * time.Millisecond)
}
// Calculate baseline from the intermediate persisted state
baselineTracker := &SizeTracker{
db: db,
triedb: tdb,
abort: make(chan struct{}),
}
done := make(chan buildResult)
go baselineTracker.build(baselineRoot, baselineBlockNum, done)
var baselineResult buildResult
select {
case baselineResult = <-done:
if baselineResult.err != nil {
t.Fatalf("Failed to get baseline stats: %v", baselineResult.err)
}
case <-time.After(30 * time.Second):
t.Fatal("Timeout waiting for baseline stats")
}
baseline := baselineResult.stat
// Now start the tracker and notify it of updates that happen AFTER the baseline
tracker, err := NewSizeTracker(db, tdb)
if err != nil {
t.Fatalf("Failed to create size tracker: %v", err)
}
defer tracker.Stop()
var trackedUpdates []SizeStats
currentRoot = baselineRoot
// Generate additional blocks beyond the baseline and track them
for i := 49; i < 130; i++ { // blocks 51-132
blockNum := uint64(i + 2)
newState, err := New(currentRoot, sdb)
if err != nil {
t.Fatalf("Failed to create new state at block %d: %v", blockNum, err)
}
testAddr := common.BigToAddress(uint256.NewInt(uint64(i + 100)).ToBig())
newState.AddBalance(testAddr, uint256.NewInt(uint64((i+1)*1000)), tracing.BalanceChangeUnspecified)
newState.SetNonce(testAddr, uint64(i+10), tracing.NonceChangeUnspecified)
if i%2 == 0 {
newState.SetState(addr1, common.BigToHash(uint256.NewInt(uint64(i+0x1000)).ToBig()), common.BigToHash(uint256.NewInt(uint64(i+0x2000)).ToBig()))
}
if i%3 == 0 {
newState.SetCode(testAddr, []byte{byte(i), 0x60, 0x80, byte(i + 1), 0x52}, tracing.CodeChangeUnspecified)
}
root, update, err := newState.CommitWithUpdate(blockNum, true, false)
if err != nil {
t.Fatalf("Failed to commit state at block %d: %v", blockNum, err)
}
if err := tdb.Commit(root, false); err != nil {
t.Fatalf("Failed to commit trie at block %d: %v", blockNum, err)
}
diff, err := calSizeStats(update)
if err != nil {
t.Fatalf("Failed to calculate size stats for block %d: %v", blockNum, err)
}
trackedUpdates = append(trackedUpdates, diff)
tracker.Notify(update)
currentRoot = root
}
finalRoot := rawdb.ReadSnapshotRoot(db)
// Ensure all commits are flushed to disk
if err := tdb.Close(); err != nil {
t.Fatalf("Failed to close triedb: %v", err)
}
// Reopen the database to simulate a restart
tdb = triedb.NewDatabase(db, &triedb.Config{PathDB: pathdb.Defaults})
defer tdb.Close()
finalTracker := &SizeTracker{
db: db,
triedb: tdb,
abort: make(chan struct{}),
}
finalDone := make(chan buildResult)
go finalTracker.build(finalRoot, uint64(132), finalDone)
var result buildResult
select {
case result = <-finalDone:
if result.err != nil {
t.Fatalf("Failed to build final stats: %v", result.err)
}
case <-time.After(30 * time.Second):
t.Fatal("Timeout waiting for final stats")
}
actualStats := result.stat
expectedStats := baseline
for _, diff := range trackedUpdates {
expectedStats = expectedStats.add(diff)
}
// The final measured stats should match our calculated expected stats exactly
if actualStats.Accounts != expectedStats.Accounts {
t.Errorf("Account count mismatch: baseline(%d) + tracked_changes = %d, but final_measurement = %d", baseline.Accounts, expectedStats.Accounts, actualStats.Accounts)
}
if actualStats.AccountBytes != expectedStats.AccountBytes {
t.Errorf("Account bytes mismatch: expected %d, got %d", expectedStats.AccountBytes, actualStats.AccountBytes)
}
if actualStats.Storages != expectedStats.Storages {
t.Errorf("Storage count mismatch: baseline(%d) + tracked_changes = %d, but final_measurement = %d", baseline.Storages, expectedStats.Storages, actualStats.Storages)
}
if actualStats.StorageBytes != expectedStats.StorageBytes {
t.Errorf("Storage bytes mismatch: expected %d, got %d", expectedStats.StorageBytes, actualStats.StorageBytes)
}
if actualStats.ContractCodes != expectedStats.ContractCodes {
t.Errorf("Contract code count mismatch: baseline(%d) + tracked_changes = %d, but final_measurement = %d", baseline.ContractCodes, expectedStats.ContractCodes, actualStats.ContractCodes)
}
if actualStats.ContractCodeBytes != expectedStats.ContractCodeBytes {
t.Errorf("Contract code bytes mismatch: expected %d, got %d", expectedStats.ContractCodeBytes, actualStats.ContractCodeBytes)
}
// TODO: failed on github actions, need to investigate
// if actualStats.AccountTrienodes != expectedStats.AccountTrienodes {
// t.Errorf("Account trie nodes mismatch: expected %d, got %d", expectedStats.AccountTrienodes, actualStats.AccountTrienodes)
// }
// if actualStats.AccountTrienodeBytes != expectedStats.AccountTrienodeBytes {
// t.Errorf("Account trie node bytes mismatch: expected %d, got %d", expectedStats.AccountTrienodeBytes, actualStats.AccountTrienodeBytes)
// }
if actualStats.StorageTrienodes != expectedStats.StorageTrienodes {
t.Errorf("Storage trie nodes mismatch: expected %d, got %d", expectedStats.StorageTrienodes, actualStats.StorageTrienodes)
}
if actualStats.StorageTrienodeBytes != expectedStats.StorageTrienodeBytes {
t.Errorf("Storage trie node bytes mismatch: expected %d, got %d", expectedStats.StorageTrienodeBytes, actualStats.StorageTrienodeBytes)
}
}

View file

@ -137,6 +137,7 @@ type StateDB struct {
// State witness if cross validation is needed // State witness if cross validation is needed
witness *stateless.Witness witness *stateless.Witness
witnessStats *stateless.WitnessStats
// Measurements gathered during execution for debugging purposes // Measurements gathered during execution for debugging purposes
AccountReads time.Duration AccountReads time.Duration
@ -191,12 +192,13 @@ func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, erro
// StartPrefetcher initializes a new trie prefetcher to pull in nodes from the // StartPrefetcher initializes a new trie prefetcher to pull in nodes from the
// state trie concurrently while the state is mutated so that when we reach the // state trie concurrently while the state is mutated so that when we reach the
// commit phase, most of the needed data is already hot. // commit phase, most of the needed data is already hot.
func (s *StateDB) StartPrefetcher(namespace string, witness *stateless.Witness) { func (s *StateDB) StartPrefetcher(namespace string, witness *stateless.Witness, witnessStats *stateless.WitnessStats) {
// Terminate any previously running prefetcher // Terminate any previously running prefetcher
s.StopPrefetcher() s.StopPrefetcher()
// Enable witness collection if requested // Enable witness collection if requested
s.witness = witness s.witness = witness
s.witnessStats = witnessStats
// With the switch to the Proof-of-Stake consensus algorithm, block production // With the switch to the Proof-of-Stake consensus algorithm, block production
// rewards are now handled at the consensus layer. Consequently, a block may // rewards are now handled at the consensus layer. Consequently, a block may
@ -456,7 +458,7 @@ func (s *StateDB) SetNonce(addr common.Address, nonce uint64, reason tracing.Non
} }
} }
func (s *StateDB) SetCode(addr common.Address, code []byte) (prev []byte) { func (s *StateDB) SetCode(addr common.Address, code []byte, reason tracing.CodeChangeReason) (prev []byte) {
stateObject := s.getOrNewStateObject(addr) stateObject := s.getOrNewStateObject(addr)
if stateObject != nil { if stateObject != nil {
return stateObject.SetCode(crypto.Keccak256Hash(code), code) return stateObject.SetCode(crypto.Keccak256Hash(code), code)
@ -858,9 +860,17 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
continue continue
} }
if trie := obj.getPrefetchedTrie(); trie != nil { if trie := obj.getPrefetchedTrie(); trie != nil {
s.witness.AddState(trie.Witness()) witness := trie.Witness()
s.witness.AddState(witness)
if s.witnessStats != nil {
s.witnessStats.Add(witness, obj.addrHash)
}
} else if obj.trie != nil { } else if obj.trie != nil {
s.witness.AddState(obj.trie.Witness()) witness := obj.trie.Witness()
s.witness.AddState(witness)
if s.witnessStats != nil {
s.witnessStats.Add(witness, obj.addrHash)
}
} }
} }
// Pull in only-read and non-destructed trie witnesses // Pull in only-read and non-destructed trie witnesses
@ -874,9 +884,17 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
continue continue
} }
if trie := obj.getPrefetchedTrie(); trie != nil { if trie := obj.getPrefetchedTrie(); trie != nil {
s.witness.AddState(trie.Witness()) witness := trie.Witness()
s.witness.AddState(witness)
if s.witnessStats != nil {
s.witnessStats.Add(witness, obj.addrHash)
}
} else if obj.trie != nil { } else if obj.trie != nil {
s.witness.AddState(obj.trie.Witness()) witness := obj.trie.Witness()
s.witness.AddState(witness)
if s.witnessStats != nil {
s.witnessStats.Add(witness, obj.addrHash)
}
} }
} }
} }
@ -942,7 +960,11 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
// If witness building is enabled, gather the account trie witness // If witness building is enabled, gather the account trie witness
if s.witness != nil { if s.witness != nil {
s.witness.AddState(s.trie.Witness()) witness := s.trie.Witness()
s.witness.AddState(witness)
if s.witnessStats != nil {
s.witnessStats.Add(witness, common.Hash{})
}
} }
return hash return hash
} }
@ -1133,7 +1155,7 @@ func (s *StateDB) GetTrie() Trie {
// commit gathers the state mutations accumulated along with the associated // commit gathers the state mutations accumulated along with the associated
// trie changes, resetting all internal flags with the new state as the base. // trie changes, resetting all internal flags with the new state as the base.
func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool) (*stateUpdate, error) { func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNumber uint64) (*stateUpdate, error) {
// Short circuit in case any database failure occurred earlier. // Short circuit in case any database failure occurred earlier.
if s.dbErr != nil { if s.dbErr != nil {
return nil, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr) return nil, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr)
@ -1285,13 +1307,13 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool) (*stateU
origin := s.originalRoot origin := s.originalRoot
s.originalRoot = root s.originalRoot = root
return newStateUpdate(noStorageWiping, origin, root, deletes, updates, nodes), nil return newStateUpdate(noStorageWiping, origin, root, blockNumber, deletes, updates, nodes), nil
} }
// commitAndFlush is a wrapper of commit which also commits the state mutations // commitAndFlush is a wrapper of commit which also commits the state mutations
// to the configured data stores. // to the configured data stores.
func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool, noStorageWiping bool) (*stateUpdate, error) { func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool, noStorageWiping bool) (*stateUpdate, error) {
ret, err := s.commit(deleteEmptyObjects, noStorageWiping) ret, err := s.commit(deleteEmptyObjects, noStorageWiping, block)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1356,6 +1378,16 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool, noStorageWiping
return ret.root, nil return ret.root, nil
} }
// CommitWithUpdate writes the state mutations and returns both the root hash and the state update.
// This is useful for tracking state changes at the blockchain level.
func (s *StateDB) CommitWithUpdate(block uint64, deleteEmptyObjects bool, noStorageWiping bool) (common.Hash, *stateUpdate, error) {
ret, err := s.commitAndFlush(block, deleteEmptyObjects, noStorageWiping)
if err != nil {
return common.Hash{}, nil, err
}
return ret.root, ret, nil
}
// Prepare handles the preparatory steps for executing a state transition with. // Prepare handles the preparatory steps for executing a state transition with.
// This method must be invoked before state transition. // This method must be invoked before state transition.
// //

View file

@ -89,7 +89,7 @@ func newStateTestAction(addr common.Address, r *rand.Rand, index int) testAction
code := make([]byte, 16) code := make([]byte, 16)
binary.BigEndian.PutUint64(code, uint64(a.args[0])) binary.BigEndian.PutUint64(code, uint64(a.args[0]))
binary.BigEndian.PutUint64(code[8:], uint64(a.args[1])) binary.BigEndian.PutUint64(code[8:], uint64(a.args[1]))
s.SetCode(addr, code) s.SetCode(addr, code, tracing.CodeChangeUnspecified)
}, },
args: make([]int64, 2), args: make([]int64, 2),
}, },

View file

@ -189,14 +189,20 @@ func (s *hookedStateDB) SetNonce(address common.Address, nonce uint64, reason tr
} }
} }
func (s *hookedStateDB) SetCode(address common.Address, code []byte) []byte { func (s *hookedStateDB) SetCode(address common.Address, code []byte, reason tracing.CodeChangeReason) []byte {
prev := s.inner.SetCode(address, code) prev := s.inner.SetCode(address, code, reason)
if s.hooks.OnCodeChange != nil { if s.hooks.OnCodeChangeV2 != nil || s.hooks.OnCodeChange != nil {
prevHash := types.EmptyCodeHash prevHash := types.EmptyCodeHash
if len(prev) != 0 { if len(prev) != 0 {
prevHash = crypto.Keccak256Hash(prev) prevHash = crypto.Keccak256Hash(prev)
} }
s.hooks.OnCodeChange(address, prevHash, prev, crypto.Keccak256Hash(code), code) codeHash := crypto.Keccak256Hash(code)
if s.hooks.OnCodeChangeV2 != nil {
s.hooks.OnCodeChangeV2(address, prevHash, prev, codeHash, code, reason)
} else if s.hooks.OnCodeChange != nil {
s.hooks.OnCodeChange(address, prevHash, prev, codeHash, code)
}
} }
return prev return prev
} }
@ -224,9 +230,13 @@ func (s *hookedStateDB) SelfDestruct(address common.Address) uint256.Int {
s.hooks.OnBalanceChange(address, prev.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestruct) s.hooks.OnBalanceChange(address, prev.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestruct)
} }
if s.hooks.OnCodeChange != nil && len(prevCode) > 0 { if len(prevCode) > 0 {
if s.hooks.OnCodeChangeV2 != nil {
s.hooks.OnCodeChangeV2(address, prevCodeHash, prevCode, types.EmptyCodeHash, nil, tracing.CodeChangeSelfDestruct)
} else if s.hooks.OnCodeChange != nil {
s.hooks.OnCodeChange(address, prevCodeHash, prevCode, types.EmptyCodeHash, nil) s.hooks.OnCodeChange(address, prevCodeHash, prevCode, types.EmptyCodeHash, nil)
} }
}
return prev return prev
} }
@ -242,13 +252,17 @@ func (s *hookedStateDB) SelfDestruct6780(address common.Address) (uint256.Int, b
prev, changed := s.inner.SelfDestruct6780(address) prev, changed := s.inner.SelfDestruct6780(address)
if s.hooks.OnBalanceChange != nil && changed && !prev.IsZero() { if s.hooks.OnBalanceChange != nil && !prev.IsZero() {
s.hooks.OnBalanceChange(address, prev.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestruct) s.hooks.OnBalanceChange(address, prev.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestruct)
} }
if s.hooks.OnCodeChange != nil && changed && len(prevCode) > 0 { if changed && len(prevCode) > 0 {
if s.hooks.OnCodeChangeV2 != nil {
s.hooks.OnCodeChangeV2(address, prevCodeHash, prevCode, types.EmptyCodeHash, nil, tracing.CodeChangeSelfDestruct)
} else if s.hooks.OnCodeChange != nil {
s.hooks.OnCodeChange(address, prevCodeHash, prevCode, types.EmptyCodeHash, nil) s.hooks.OnCodeChange(address, prevCodeHash, prevCode, types.EmptyCodeHash, nil)
} }
}
return prev, changed return prev, changed
} }

View file

@ -114,7 +114,7 @@ func TestHooks(t *testing.T) {
sdb.AddBalance(common.Address{0xaa}, uint256.NewInt(100), tracing.BalanceChangeUnspecified) sdb.AddBalance(common.Address{0xaa}, uint256.NewInt(100), tracing.BalanceChangeUnspecified)
sdb.SubBalance(common.Address{0xaa}, uint256.NewInt(50), tracing.BalanceChangeTransfer) sdb.SubBalance(common.Address{0xaa}, uint256.NewInt(50), tracing.BalanceChangeTransfer)
sdb.SetNonce(common.Address{0xaa}, 1337, tracing.NonceChangeGenesis) sdb.SetNonce(common.Address{0xaa}, 1337, tracing.NonceChangeGenesis)
sdb.SetCode(common.Address{0xaa}, []byte{0x13, 37}) sdb.SetCode(common.Address{0xaa}, []byte{0x13, 37}, tracing.CodeChangeUnspecified)
sdb.SetState(common.Address{0xaa}, common.HexToHash("0x01"), common.HexToHash("0x11")) sdb.SetState(common.Address{0xaa}, common.HexToHash("0x01"), common.HexToHash("0x11"))
sdb.SetState(common.Address{0xaa}, common.HexToHash("0x01"), common.HexToHash("0x22")) sdb.SetState(common.Address{0xaa}, common.HexToHash("0x01"), common.HexToHash("0x22"))
sdb.SetTransientState(common.Address{0xaa}, common.HexToHash("0x02"), common.HexToHash("0x01")) sdb.SetTransientState(common.Address{0xaa}, common.HexToHash("0x02"), common.HexToHash("0x01"))

View file

@ -65,7 +65,7 @@ func TestUpdateLeaks(t *testing.T) {
state.SetState(addr, common.BytesToHash([]byte{i, i, i}), common.BytesToHash([]byte{i, i, i, i})) state.SetState(addr, common.BytesToHash([]byte{i, i, i}), common.BytesToHash([]byte{i, i, i, i}))
} }
if i%3 == 0 { if i%3 == 0 {
state.SetCode(addr, []byte{i, i, i, i, i}) state.SetCode(addr, []byte{i, i, i, i, i}, tracing.CodeChangeUnspecified)
} }
} }
@ -101,7 +101,7 @@ func TestIntermediateLeaks(t *testing.T) {
state.SetState(addr, common.Hash{i, i, i, tweak}, common.Hash{i, i, i, i, tweak}) state.SetState(addr, common.Hash{i, i, i, tweak}, common.Hash{i, i, i, i, tweak})
} }
if i%3 == 0 { if i%3 == 0 {
state.SetCode(addr, []byte{i, i, i, i, i, tweak}) state.SetCode(addr, []byte{i, i, i, i, i, tweak}, tracing.CodeChangeUnspecified)
} }
} }
@ -374,7 +374,7 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction {
code := make([]byte, 16) code := make([]byte, 16)
binary.BigEndian.PutUint64(code, uint64(a.args[0])) binary.BigEndian.PutUint64(code, uint64(a.args[0]))
binary.BigEndian.PutUint64(code[8:], uint64(a.args[1])) binary.BigEndian.PutUint64(code[8:], uint64(a.args[1]))
s.SetCode(addr, code) s.SetCode(addr, code, tracing.CodeChangeUnspecified)
}, },
args: make([]int64, 2), args: make([]int64, 2),
}, },
@ -403,7 +403,7 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction {
// which would cause a difference in state when unrolling // which would cause a difference in state when unrolling
// the journal. (CreateContact assumes created was false prior to // the journal. (CreateContact assumes created was false prior to
// invocation, and the journal rollback sets it to false). // invocation, and the journal rollback sets it to false).
s.SetCode(addr, []byte{1}) s.SetCode(addr, []byte{1}, tracing.CodeChangeUnspecified)
} }
}, },
}, },
@ -731,7 +731,7 @@ func TestCopyCommitCopy(t *testing.T) {
sval := common.HexToHash("bbb") sval := common.HexToHash("bbb")
state.SetBalance(addr, uint256.NewInt(42), tracing.BalanceChangeUnspecified) // Change the account trie state.SetBalance(addr, uint256.NewInt(42), tracing.BalanceChangeUnspecified) // Change the account trie
state.SetCode(addr, []byte("hello")) // Change an external metadata state.SetCode(addr, []byte("hello"), tracing.CodeChangeUnspecified) // Change an external metadata
state.SetState(addr, skey, sval) // Change the storage trie state.SetState(addr, skey, sval) // Change the storage trie
if balance := state.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 { if balance := state.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 {
@ -804,7 +804,7 @@ func TestCopyCopyCommitCopy(t *testing.T) {
sval := common.HexToHash("bbb") sval := common.HexToHash("bbb")
state.SetBalance(addr, uint256.NewInt(42), tracing.BalanceChangeUnspecified) // Change the account trie state.SetBalance(addr, uint256.NewInt(42), tracing.BalanceChangeUnspecified) // Change the account trie
state.SetCode(addr, []byte("hello")) // Change an external metadata state.SetCode(addr, []byte("hello"), tracing.CodeChangeUnspecified) // Change an external metadata
state.SetState(addr, skey, sval) // Change the storage trie state.SetState(addr, skey, sval) // Change the storage trie
if balance := state.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 { if balance := state.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 {
@ -874,7 +874,7 @@ func TestCommitCopy(t *testing.T) {
sval1, sval2 := common.HexToHash("b1"), common.HexToHash("b2") sval1, sval2 := common.HexToHash("b1"), common.HexToHash("b2")
state.SetBalance(addr, uint256.NewInt(42), tracing.BalanceChangeUnspecified) // Change the account trie state.SetBalance(addr, uint256.NewInt(42), tracing.BalanceChangeUnspecified) // Change the account trie
state.SetCode(addr, []byte("hello")) // Change an external metadata state.SetCode(addr, []byte("hello"), tracing.CodeChangeUnspecified) // Change an external metadata
state.SetState(addr, skey1, sval1) // Change the storage trie state.SetState(addr, skey1, sval1) // Change the storage trie
if balance := state.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 { if balance := state.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 {
@ -987,10 +987,10 @@ func testMissingTrieNodes(t *testing.T, scheme string) {
addr := common.BytesToAddress([]byte("so")) addr := common.BytesToAddress([]byte("so"))
{ {
state.SetBalance(addr, uint256.NewInt(1), tracing.BalanceChangeUnspecified) state.SetBalance(addr, uint256.NewInt(1), tracing.BalanceChangeUnspecified)
state.SetCode(addr, []byte{1, 2, 3}) state.SetCode(addr, []byte{1, 2, 3}, tracing.CodeChangeUnspecified)
a2 := common.BytesToAddress([]byte("another")) a2 := common.BytesToAddress([]byte("another"))
state.SetBalance(a2, uint256.NewInt(100), tracing.BalanceChangeUnspecified) state.SetBalance(a2, uint256.NewInt(100), tracing.BalanceChangeUnspecified)
state.SetCode(a2, []byte{1, 2, 4}) state.SetCode(a2, []byte{1, 2, 4}, tracing.CodeChangeUnspecified)
root, _ = state.Commit(0, false, false) root, _ = state.Commit(0, false, false)
t.Logf("root: %x", root) t.Logf("root: %x", root)
// force-flush // force-flush

View file

@ -66,6 +66,8 @@ type accountUpdate struct {
type stateUpdate struct { type stateUpdate struct {
originRoot common.Hash // hash of the state before applying mutation originRoot common.Hash // hash of the state before applying mutation
root common.Hash // hash of the state after applying mutation root common.Hash // hash of the state after applying mutation
blockNumber uint64 // Associated block number
accounts map[common.Hash][]byte // accounts stores mutated accounts in 'slim RLP' encoding accounts map[common.Hash][]byte // accounts stores mutated accounts in 'slim RLP' encoding
accountsOrigin map[common.Address][]byte // accountsOrigin stores the original values of mutated accounts in 'slim RLP' encoding accountsOrigin map[common.Address][]byte // accountsOrigin stores the original values of mutated accounts in 'slim RLP' encoding
@ -95,7 +97,7 @@ func (sc *stateUpdate) empty() bool {
// //
// rawStorageKey is a flag indicating whether to use the raw storage slot key or // rawStorageKey is a flag indicating whether to use the raw storage slot key or
// the hash of the slot key for constructing state update object. // the hash of the slot key for constructing state update object.
func newStateUpdate(rawStorageKey bool, originRoot common.Hash, root common.Hash, deletes map[common.Hash]*accountDelete, updates map[common.Hash]*accountUpdate, nodes *trienode.MergedNodeSet) *stateUpdate { func newStateUpdate(rawStorageKey bool, originRoot common.Hash, root common.Hash, blockNumber uint64, deletes map[common.Hash]*accountDelete, updates map[common.Hash]*accountUpdate, nodes *trienode.MergedNodeSet) *stateUpdate {
var ( var (
accounts = make(map[common.Hash][]byte) accounts = make(map[common.Hash][]byte)
accountsOrigin = make(map[common.Address][]byte) accountsOrigin = make(map[common.Address][]byte)
@ -164,6 +166,7 @@ func newStateUpdate(rawStorageKey bool, originRoot common.Hash, root common.Hash
return &stateUpdate{ return &stateUpdate{
originRoot: originRoot, originRoot: originRoot,
root: root, root: root,
blockNumber: blockNumber,
accounts: accounts, accounts: accounts,
accountsOrigin: accountsOrigin, accountsOrigin: accountsOrigin,
storages: storages, storages: storages,

View file

@ -388,6 +388,10 @@ func (sf *subfetcher) loop() {
sf.tasks = nil sf.tasks = nil
sf.lock.Unlock() sf.lock.Unlock()
var (
addresses []common.Address
slots [][]byte
)
for _, task := range tasks { for _, task := range tasks {
if task.addr != nil { if task.addr != nil {
key := *task.addr key := *task.addr
@ -400,6 +404,7 @@ func (sf *subfetcher) loop() {
sf.dupsCross++ sf.dupsCross++
continue continue
} }
sf.seenReadAddr[key] = struct{}{}
} else { } else {
if _, ok := sf.seenReadAddr[key]; ok { if _, ok := sf.seenReadAddr[key]; ok {
sf.dupsCross++ sf.dupsCross++
@ -409,7 +414,9 @@ func (sf *subfetcher) loop() {
sf.dupsWrite++ sf.dupsWrite++
continue continue
} }
sf.seenWriteAddr[key] = struct{}{}
} }
addresses = append(addresses, *task.addr)
} else { } else {
key := *task.slot key := *task.slot
if task.read { if task.read {
@ -421,6 +428,7 @@ func (sf *subfetcher) loop() {
sf.dupsCross++ sf.dupsCross++
continue continue
} }
sf.seenReadSlot[key] = struct{}{}
} else { } else {
if _, ok := sf.seenReadSlot[key]; ok { if _, ok := sf.seenReadSlot[key]; ok {
sf.dupsCross++ sf.dupsCross++
@ -430,25 +438,19 @@ func (sf *subfetcher) loop() {
sf.dupsWrite++ sf.dupsWrite++
continue continue
} }
sf.seenWriteSlot[key] = struct{}{}
}
slots = append(slots, key.Bytes())
} }
} }
if task.addr != nil { if len(addresses) != 0 {
sf.trie.GetAccount(*task.addr) if err := sf.trie.PrefetchAccount(addresses); err != nil {
} else { log.Error("Failed to prefetch accounts", "err", err)
sf.trie.GetStorage(sf.addr, (*task.slot)[:])
} }
if task.read {
if task.addr != nil {
sf.seenReadAddr[*task.addr] = struct{}{}
} else {
sf.seenReadSlot[*task.slot] = struct{}{}
}
} else {
if task.addr != nil {
sf.seenWriteAddr[*task.addr] = struct{}{}
} else {
sf.seenWriteSlot[*task.slot] = struct{}{}
} }
if len(slots) != 0 {
if err := sf.trie.PrefetchStorage(sf.addr, slots); err != nil {
log.Error("Failed to prefetch storage", "err", err)
} }
} }

View file

@ -39,7 +39,7 @@ func filledStateDB() *StateDB {
sval := common.HexToHash("bbb") sval := common.HexToHash("bbb")
state.SetBalance(addr, uint256.NewInt(42), tracing.BalanceChangeUnspecified) // Change the account trie state.SetBalance(addr, uint256.NewInt(42), tracing.BalanceChangeUnspecified) // Change the account trie
state.SetCode(addr, []byte("hello")) // Change an external metadata state.SetCode(addr, []byte("hello"), tracing.CodeChangeUnspecified) // Change an external metadata
state.SetState(addr, skey, sval) // Change the storage trie state.SetState(addr, skey, sval) // Change the storage trie
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
sk := common.BigToHash(big.NewInt(int64(i))) sk := common.BigToHash(big.NewInt(int64(i)))
@ -81,7 +81,7 @@ func TestVerklePrefetcher(t *testing.T) {
sval := testrand.Hash() sval := testrand.Hash()
state.SetBalance(addr, uint256.NewInt(42), tracing.BalanceChangeUnspecified) // Change the account trie state.SetBalance(addr, uint256.NewInt(42), tracing.BalanceChangeUnspecified) // Change the account trie
state.SetCode(addr, []byte("hello")) // Change an external metadata state.SetCode(addr, []byte("hello"), tracing.CodeChangeUnspecified) // Change an external metadata
state.SetState(addr, skey, sval) // Change the storage trie state.SetState(addr, skey, sval) // Change the storage trie
root, _ := state.Commit(0, true, false) root, _ := state.Commit(0, true, false)

View file

@ -111,12 +111,6 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c
fails.Add(1) fails.Add(1)
return nil // Ugh, something went horribly wrong, bail out return nil // Ugh, something went horribly wrong, bail out
} }
// Pre-load trie nodes for the intermediate root.
//
// This operation incurs significant memory allocations due to
// trie hashing and node decoding. TODO(rjl493456442): investigate
// ways to mitigate this overhead.
stateCpy.IntermediateRoot(true)
return nil return nil
}) })
} }

View file

@ -110,15 +110,15 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
requests = [][]byte{} requests = [][]byte{}
// EIP-6110 // EIP-6110
if err := ParseDepositLogs(&requests, allLogs, p.config); err != nil { if err := ParseDepositLogs(&requests, allLogs, p.config); err != nil {
return nil, err return nil, fmt.Errorf("failed to parse deposit logs: %w", err)
} }
// EIP-7002 // EIP-7002
if err := ProcessWithdrawalQueue(&requests, evm); err != nil { if err := ProcessWithdrawalQueue(&requests, evm); err != nil {
return nil, err return nil, fmt.Errorf("failed to process withdrawal queue: %w", err)
} }
// EIP-7251 // EIP-7251
if err := ProcessConsolidationQueue(&requests, evm); err != nil { if err := ProcessConsolidationQueue(&requests, evm); err != nil {
return nil, err return nil, fmt.Errorf("failed to process consolidation queue: %w", err)
} }
} }

View file

@ -642,12 +642,12 @@ func (st *stateTransition) applyAuthorization(auth *types.SetCodeAuthorization)
st.state.SetNonce(authority, auth.Nonce+1, tracing.NonceChangeAuthorization) st.state.SetNonce(authority, auth.Nonce+1, tracing.NonceChangeAuthorization)
if auth.Address == (common.Address{}) { if auth.Address == (common.Address{}) {
// Delegation to zero address means clear. // Delegation to zero address means clear.
st.state.SetCode(authority, nil) st.state.SetCode(authority, nil, tracing.CodeChangeAuthorizationClear)
return nil return nil
} }
// Otherwise install delegation to auth.Address. // Otherwise install delegation to auth.Address.
st.state.SetCode(authority, types.AddressToDelegation(auth.Address)) st.state.SetCode(authority, types.AddressToDelegation(auth.Address), tracing.CodeChangeAuthorization)
return nil return nil
} }

78
core/stateless/stats.go Normal file
View file

@ -0,0 +1,78 @@
// 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 stateless
import (
"maps"
"slices"
"sort"
"strconv"
"strings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/metrics"
)
var accountTrieLeavesAtDepth [16]*metrics.Counter
var storageTrieLeavesAtDepth [16]*metrics.Counter
func init() {
for i := 0; i < 16; i++ {
accountTrieLeavesAtDepth[i] = metrics.NewRegisteredCounter("witness/trie/account/leaves/depth_"+strconv.Itoa(i), nil)
storageTrieLeavesAtDepth[i] = metrics.NewRegisteredCounter("witness/trie/storage/leaves/depth_"+strconv.Itoa(i), nil)
}
}
// WitnessStats aggregates statistics for account and storage trie accesses.
type WitnessStats struct {
accountTrieLeaves [16]int64
storageTrieLeaves [16]int64
}
// NewWitnessStats creates a new WitnessStats collector.
func NewWitnessStats() *WitnessStats {
return &WitnessStats{}
}
// Add records trie access depths from the given node paths.
// If `owner` is the zero hash, accesses are attributed to the account trie;
// otherwise, they are attributed to the storage trie of that account.
func (s *WitnessStats) Add(nodes map[string][]byte, owner common.Hash) {
// Extract paths from the nodes map
paths := slices.Collect(maps.Keys(nodes))
sort.Strings(paths)
for i, path := range paths {
// If current path is a prefix of the next path, it's not a leaf.
// The last path is always a leaf.
if i == len(paths)-1 || !strings.HasPrefix(paths[i+1], paths[i]) {
if owner == (common.Hash{}) {
s.accountTrieLeaves[len(path)] += 1
} else {
s.storageTrieLeaves[len(path)] += 1
}
}
}
}
// ReportMetrics reports the collected statistics to the global metrics registry.
func (s *WitnessStats) ReportMetrics() {
for i := 0; i < 16; i++ {
accountTrieLeavesAtDepth[i].Inc(s.accountTrieLeaves[i])
storageTrieLeavesAtDepth[i].Inc(s.storageTrieLeaves[i])
}
}

View file

@ -0,0 +1,161 @@
// 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 stateless
import (
"testing"
"github.com/ethereum/go-ethereum/common"
)
func TestWitnessStatsAdd(t *testing.T) {
tests := []struct {
name string
nodes map[string][]byte
owner common.Hash
expectedAccountLeaves map[int64]int64
expectedStorageLeaves map[int64]int64
}{
{
name: "empty nodes",
nodes: map[string][]byte{},
owner: common.Hash{},
},
{
name: "single account trie leaf at depth 0",
nodes: map[string][]byte{
"": []byte("data"),
},
owner: common.Hash{},
expectedAccountLeaves: map[int64]int64{0: 1},
},
{
name: "single account trie leaf",
nodes: map[string][]byte{
"abc": []byte("data"),
},
owner: common.Hash{},
expectedAccountLeaves: map[int64]int64{3: 1},
},
{
name: "account trie with internal nodes",
nodes: map[string][]byte{
"a": []byte("data1"),
"ab": []byte("data2"),
"abc": []byte("data3"),
},
owner: common.Hash{},
expectedAccountLeaves: map[int64]int64{3: 1}, // Only "abc" is a leaf
},
{
name: "multiple account trie branches",
nodes: map[string][]byte{
"a": []byte("data1"),
"ab": []byte("data2"),
"abc": []byte("data3"),
"b": []byte("data4"),
"bc": []byte("data5"),
"bcd": []byte("data6"),
},
owner: common.Hash{},
expectedAccountLeaves: map[int64]int64{3: 2}, // "abc" (3) + "bcd" (3)
},
{
name: "siblings are all leaves",
nodes: map[string][]byte{
"aa": []byte("data1"),
"ab": []byte("data2"),
"ac": []byte("data3"),
},
owner: common.Hash{},
expectedAccountLeaves: map[int64]int64{2: 3},
},
{
name: "storage trie leaves",
nodes: map[string][]byte{
"1": []byte("data1"),
"12": []byte("data2"),
"123": []byte("data3"),
"124": []byte("data4"),
},
owner: common.HexToHash("0x1234"),
expectedStorageLeaves: map[int64]int64{3: 2}, // "123" (3) + "124" (3)
},
{
name: "complex trie structure",
nodes: map[string][]byte{
"1": []byte("data1"),
"12": []byte("data2"),
"123": []byte("data3"),
"124": []byte("data4"),
"2": []byte("data5"),
"23": []byte("data6"),
"234": []byte("data7"),
"235": []byte("data8"),
"3": []byte("data9"),
},
owner: common.Hash{},
expectedAccountLeaves: map[int64]int64{1: 1, 3: 4}, // "123"(3) + "124"(3) + "234"(3) + "235"(3) + "3"(1)
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
stats := NewWitnessStats()
stats.Add(tt.nodes, tt.owner)
var expectedAccountTrieLeaves [16]int64
for depth, count := range tt.expectedAccountLeaves {
expectedAccountTrieLeaves[depth] = count
}
var expectedStorageTrieLeaves [16]int64
for depth, count := range tt.expectedStorageLeaves {
expectedStorageTrieLeaves[depth] = count
}
// Check account trie depth
if stats.accountTrieLeaves != expectedAccountTrieLeaves {
t.Errorf("Account trie total depth = %v, want %v", stats.accountTrieLeaves, expectedAccountTrieLeaves)
}
// Check storage trie depth
if stats.storageTrieLeaves != expectedStorageTrieLeaves {
t.Errorf("Storage trie total depth = %v, want %v", stats.storageTrieLeaves, expectedStorageTrieLeaves)
}
})
}
}
func BenchmarkWitnessStatsAdd(b *testing.B) {
// Create a realistic trie node structure
nodes := make(map[string][]byte)
for i := 0; i < 100; i++ {
base := string(rune('a' + i%26))
nodes[base] = []byte("data")
for j := 0; j < 9; j++ {
key := base + string(rune('0'+j))
nodes[key] = []byte("data")
}
}
stats := NewWitnessStats()
b.ResetTimer()
for i := 0; i < b.N; i++ {
stats.Add(nodes, common.Hash{})
}
}

View file

@ -58,7 +58,7 @@ func NewWitness(context *types.Header, chain HeaderReader) (*Witness, error) {
} }
headers = append(headers, parent) headers = append(headers, parent)
} }
// Create the wtness with a reconstructed gutted out block // Create the witness with a reconstructed gutted out block
return &Witness{ return &Witness{
context: context, context: context,
Headers: headers, Headers: headers,
@ -88,14 +88,16 @@ func (w *Witness) AddCode(code []byte) {
} }
// AddState inserts a batch of MPT trie nodes into the witness. // AddState inserts a batch of MPT trie nodes into the witness.
func (w *Witness) AddState(nodes map[string]struct{}) { func (w *Witness) AddState(nodes map[string][]byte) {
if len(nodes) == 0 { if len(nodes) == 0 {
return return
} }
w.lock.Lock() w.lock.Lock()
defer w.lock.Unlock() defer w.lock.Unlock()
maps.Copy(w.State, nodes) for _, value := range nodes {
w.State[string(value)] = struct{}{}
}
} }
// Copy deep-copies the witness object. Witness.Block isn't deep-copied as it // Copy deep-copies the witness object. Witness.Block isn't deep-copied as it

View file

@ -4,6 +4,27 @@ All notable changes to the tracing interface will be documented in this file.
## [Unreleased] ## [Unreleased]
### Deprecated methods
- `OnCodeChange(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte)`: This hook is deprecated in favor of `OnCodeChangeV2` which includes a reason parameter ([#32525](https://github.com/ethereum/go-ethereum/pull/32525)).
### New methods
- `OnCodeChangeV2(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte, reason CodeChangeReason)`: This hook is called when a code change occurs. It is a successor to `OnCodeChange` with an additional reason parameter ([#32525](https://github.com/ethereum/go-ethereum/pull/32525)).
### New types
- `CodeChangeReason` is a new type used to provide a reason for code changes. It includes various reasons such as contract creation, genesis initialization, EIP-7702 authorization, self-destruct, and revert operations ([#32525](https://github.com/ethereum/go-ethereum/pull/32525)).
## [v1.15.4](https://github.com/ethereum/go-ethereum/releases/tag/v1.15.4)
### Modified types
- `GasChangeReason` has been extended with auto-generated String() methods for better debugging and logging ([#31234](https://github.com/ethereum/go-ethereum/pull/31234)).
- `NonceChangeReason` has been extended with auto-generated String() methods for better debugging and logging ([#31234](https://github.com/ethereum/go-ethereum/pull/31234)).
## [v1.15.0](https://github.com/ethereum/go-ethereum/releases/tag/v1.15.0)
The tracing interface has been extended with backwards-compatible changes to support more use-cases and simplify tracer code. The most notable change is a state journaling library which emits reverse events when a call is reverted. The tracing interface has been extended with backwards-compatible changes to support more use-cases and simplify tracer code. The most notable change is a state journaling library which emits reverse events when a call is reverted.
### Deprecated methods ### Deprecated methods
@ -23,8 +44,13 @@ The tracing interface has been extended with backwards-compatible changes to sup
### Modified types ### Modified types
- `VMContext.StateDB` has been extended with `GetCodeHash(addr common.Address) common.Hash` method used to retrieve the code hash an account. - `VMContext.StateDB` has been extended with the following method:
- `GetCodeHash(addr common.Address) common.Hash` method used to retrieve the code hash of an account.
- `BlockEvent` has been modified:
- The `TD` (Total Difficulty) field has been removed ([#30744](https://github.com/ethereum/go-ethereum/pull/30744)).
- `BalanceChangeReason` has been extended with the `BalanceChangeRevert` reason. More on that below. - `BalanceChangeReason` has been extended with the `BalanceChangeRevert` reason. More on that below.
- `GasChangeReason` has been extended with the following reason:
- `GasChangeTxDataFloor` is the amount of extra gas the transaction has to pay to reach the minimum gas requirement for the transaction data. This change will always be a negative change.
### State journaling ### State journaling
@ -49,6 +75,26 @@ The state changes that are covered by the journaling library are:
- `OnCodeChange` - `OnCodeChange`
- `OnStorageChange` - `OnStorageChange`
## [v1.14.12](https://github.com/ethereum/go-ethereum/releases/tag/v1.14.12)
This release contains a change in behavior for `OnCodeChange` hook and an extension to the StateDB interface.
### Modified types
- `VMContext.StateDB` has been extended with the following method:
- `GetTransientState(addr common.Address, slot common.Hash) common.Hash` method used to access contract transient storage ([#30531](https://github.com/ethereum/go-ethereum/pull/30531)).
### `OnCodeChange` change
The `OnCodeChange` hook is now called when the code of a contract is removed due to a selfdestruct. Previously, no code change was emitted on such occasions.
## [v1.14.10](https://github.com/ethereum/go-ethereum/releases/tag/v1.14.10)
### Modified types
- `OpContext` has been extended with the following method:
- `ContractCode() []byte` provides access to the contract bytecode within the OpContext interface ([#30466](https://github.com/ethereum/go-ethereum/pull/30466)).
## [v1.14.9](https://github.com/ethereum/go-ethereum/releases/tag/v1.14.9) ## [v1.14.9](https://github.com/ethereum/go-ethereum/releases/tag/v1.14.9)
### Modified types ### Modified types
@ -56,13 +102,6 @@ The state changes that are covered by the journaling library are:
- `GasChangeReason` has been extended with the following reasons which will be enabled only post-Verkle. There shouldn't be any gas changes with those reasons prior to the fork. - `GasChangeReason` has been extended with the following reasons which will be enabled only post-Verkle. There shouldn't be any gas changes with those reasons prior to the fork.
- `GasChangeWitnessContractCollisionCheck` flags the event of adding to the witness when checking for contract address collision. - `GasChangeWitnessContractCollisionCheck` flags the event of adding to the witness when checking for contract address collision.
## [v1.14.12]
This release contains a change in behavior for `OnCodeChange` hook.
### `OnCodeChange` change
The `OnCodeChange` hook is now called when the code of a contract is removed due to a selfdestruct. Previously, no code change was emitted on such occasions.
## [v1.14.4] ## [v1.14.4]
@ -148,7 +187,12 @@ The hooks `CaptureStart` and `CaptureEnd` have been removed. These hooks signale
- `CaptureState` -> `OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error)`. `op` is of type `byte` which can be cast to `vm.OpCode` when necessary. A `*vm.ScopeContext` is not passed anymore. It is replaced by `tracing.OpContext` which offers access to the memory, stack and current contract. - `CaptureState` -> `OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error)`. `op` is of type `byte` which can be cast to `vm.OpCode` when necessary. A `*vm.ScopeContext` is not passed anymore. It is replaced by `tracing.OpContext` which offers access to the memory, stack and current contract.
- `CaptureFault` -> `OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error)`. Similar to above. - `CaptureFault` -> `OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error)`. Similar to above.
[unreleased]: https://github.com/ethereum/go-ethereum/compare/v1.14.8...master [unreleased]: https://github.com/ethereum/go-ethereum/compare/v1.16.3...master
[v1.14.0]: https://github.com/ethereum/go-ethereum/releases/tag/v1.14.0 [v1.15.4]: https://github.com/ethereum/go-ethereum/releases/tag/v1.15.4
[v1.14.3]: https://github.com/ethereum/go-ethereum/releases/tag/v1.14.3 [v1.15.0]: https://github.com/ethereum/go-ethereum/releases/tag/v1.15.0
[v1.14.12]: https://github.com/ethereum/go-ethereum/releases/tag/v1.14.12
[v1.14.10]: https://github.com/ethereum/go-ethereum/releases/tag/v1.14.10
[v1.14.9]: https://github.com/ethereum/go-ethereum/releases/tag/v1.14.9
[v1.14.4]: https://github.com/ethereum/go-ethereum/releases/tag/v1.14.4 [v1.14.4]: https://github.com/ethereum/go-ethereum/releases/tag/v1.14.4
[v1.14.3]: https://github.com/ethereum/go-ethereum/releases/tag/v1.14.3
[v1.14.0]: https://github.com/ethereum/go-ethereum/releases/tag/v1.14.0

View file

@ -0,0 +1,29 @@
// Code generated by "stringer -type=CodeChangeReason -trimprefix=CodeChange -output gen_code_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[CodeChangeUnspecified-0]
_ = x[CodeChangeContractCreation-1]
_ = x[CodeChangeGenesis-2]
_ = x[CodeChangeAuthorization-3]
_ = x[CodeChangeAuthorizationClear-4]
_ = x[CodeChangeSelfDestruct-5]
_ = x[CodeChangeRevert-6]
}
const _CodeChangeReason_name = "UnspecifiedContractCreationGenesisAuthorizationAuthorizationClearSelfDestructRevert"
var _CodeChangeReason_index = [...]uint8{0, 11, 27, 34, 47, 65, 77, 83}
func (i CodeChangeReason) String() string {
if i >= CodeChangeReason(len(_CodeChangeReason_index)-1) {
return "CodeChangeReason(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _CodeChangeReason_name[_CodeChangeReason_index[i]:_CodeChangeReason_index[i+1]]
}

View file

@ -177,6 +177,9 @@ type (
// CodeChangeHook is called when the code of an account changes. // CodeChangeHook is called when the code of an account changes.
CodeChangeHook = func(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte) CodeChangeHook = func(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte)
// CodeChangeHookV2 is called when the code of an account changes.
CodeChangeHookV2 = func(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte, reason CodeChangeReason)
// StorageChangeHook is called when the storage of an account changes. // StorageChangeHook is called when the storage of an account changes.
StorageChangeHook = func(addr common.Address, slot common.Hash, prev, new common.Hash) StorageChangeHook = func(addr common.Address, slot common.Hash, prev, new common.Hash)
@ -211,6 +214,7 @@ type Hooks struct {
OnNonceChange NonceChangeHook OnNonceChange NonceChangeHook
OnNonceChangeV2 NonceChangeHookV2 OnNonceChangeV2 NonceChangeHookV2
OnCodeChange CodeChangeHook OnCodeChange CodeChangeHook
OnCodeChangeV2 CodeChangeHookV2
OnStorageChange StorageChangeHook OnStorageChange StorageChangeHook
OnLog LogHook OnLog LogHook
// Block hash read // Block hash read
@ -372,3 +376,31 @@ const (
// It is only emitted when the tracer has opted in to use the journaling wrapper (WrapWithJournal). // It is only emitted when the tracer has opted in to use the journaling wrapper (WrapWithJournal).
NonceChangeRevert NonceChangeReason = 6 NonceChangeRevert NonceChangeReason = 6
) )
// CodeChangeReason is used to indicate the reason for a code change.
type CodeChangeReason byte
//go:generate go run golang.org/x/tools/cmd/stringer -type=CodeChangeReason -trimprefix=CodeChange -output gen_code_change_reason_stringer.go
const (
CodeChangeUnspecified CodeChangeReason = 0
// CodeChangeContractCreation is when a new contract is deployed via CREATE/CREATE2 operations.
CodeChangeContractCreation CodeChangeReason = 1
// CodeChangeGenesis is when contract code is set during blockchain genesis or initial setup.
CodeChangeGenesis CodeChangeReason = 2
// CodeChangeAuthorization is when code is set via EIP-7702 Set Code Authorization.
CodeChangeAuthorization CodeChangeReason = 3
// CodeChangeAuthorizationClear is when EIP-7702 delegation is cleared by setting to zero address.
CodeChangeAuthorizationClear CodeChangeReason = 4
// CodeChangeSelfDestruct is when contract code is cleared due to self-destruct.
CodeChangeSelfDestruct CodeChangeReason = 5
// CodeChangeRevert is emitted when the code is reverted back to a previous value due to call failure.
// It is only emitted when the tracer has opted in to use the journaling wrapper (WrapWithJournal).
CodeChangeRevert CodeChangeReason = 6
)

View file

@ -42,12 +42,15 @@ func WrapWithJournal(hooks *Hooks) (*Hooks, error) {
return nil, errors.New("wrapping nil tracer") return nil, errors.New("wrapping nil tracer")
} }
// No state change to journal, return the wrapped hooks as is // No state change to journal, return the wrapped hooks as is
if hooks.OnBalanceChange == nil && hooks.OnNonceChange == nil && hooks.OnNonceChangeV2 == nil && hooks.OnCodeChange == nil && hooks.OnStorageChange == nil { if hooks.OnBalanceChange == nil && hooks.OnNonceChange == nil && hooks.OnNonceChangeV2 == nil && hooks.OnCodeChange == nil && hooks.OnCodeChangeV2 == nil && hooks.OnStorageChange == nil {
return hooks, nil return hooks, nil
} }
if hooks.OnNonceChange != nil && hooks.OnNonceChangeV2 != nil { if hooks.OnNonceChange != nil && hooks.OnNonceChangeV2 != nil {
return nil, errors.New("cannot have both OnNonceChange and OnNonceChangeV2") return nil, errors.New("cannot have both OnNonceChange and OnNonceChangeV2")
} }
if hooks.OnCodeChange != nil && hooks.OnCodeChangeV2 != nil {
return nil, errors.New("cannot have both OnCodeChange and OnCodeChangeV2")
}
// Create a new Hooks instance and copy all hooks // Create a new Hooks instance and copy all hooks
wrapped := *hooks wrapped := *hooks
@ -72,6 +75,9 @@ func WrapWithJournal(hooks *Hooks) (*Hooks, error) {
if hooks.OnCodeChange != nil { if hooks.OnCodeChange != nil {
wrapped.OnCodeChange = j.OnCodeChange wrapped.OnCodeChange = j.OnCodeChange
} }
if hooks.OnCodeChangeV2 != nil {
wrapped.OnCodeChangeV2 = j.OnCodeChangeV2
}
if hooks.OnStorageChange != nil { if hooks.OnStorageChange != nil {
wrapped.OnStorageChange = j.OnStorageChange wrapped.OnStorageChange = j.OnStorageChange
} }
@ -174,6 +180,19 @@ func (j *journal) OnCodeChange(addr common.Address, prevCodeHash common.Hash, pr
} }
} }
func (j *journal) OnCodeChangeV2(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte, reason CodeChangeReason) {
j.entries = append(j.entries, codeChange{
addr: addr,
prevCodeHash: prevCodeHash,
prevCode: prevCode,
newCodeHash: codeHash,
newCode: code,
})
if j.hooks.OnCodeChangeV2 != nil {
j.hooks.OnCodeChangeV2(addr, prevCodeHash, prevCode, codeHash, code, reason)
}
}
func (j *journal) OnStorageChange(addr common.Address, slot common.Hash, prev, new common.Hash) { func (j *journal) OnStorageChange(addr common.Address, slot common.Hash, prev, new common.Hash) {
j.entries = append(j.entries, storageChange{addr: addr, slot: slot, prev: prev, new: new}) j.entries = append(j.entries, storageChange{addr: addr, slot: slot, prev: prev, new: new})
if j.hooks.OnStorageChange != nil { if j.hooks.OnStorageChange != nil {
@ -225,7 +244,9 @@ func (n nonceChange) revert(hooks *Hooks) {
} }
func (c codeChange) revert(hooks *Hooks) { func (c codeChange) revert(hooks *Hooks) {
if hooks.OnCodeChange != nil { if hooks.OnCodeChangeV2 != nil {
hooks.OnCodeChangeV2(c.addr, c.newCodeHash, c.newCode, c.prevCodeHash, c.prevCode, CodeChangeRevert)
} else if hooks.OnCodeChange != nil {
hooks.OnCodeChange(c.addr, c.newCodeHash, c.newCode, c.prevCodeHash, c.prevCode) hooks.OnCodeChange(c.addr, c.newCodeHash, c.newCode, c.prevCodeHash, c.prevCode)
} }
} }

View file

@ -23,6 +23,7 @@ import (
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
) )
type testTracer struct { type testTracer struct {
@ -56,6 +57,11 @@ func (t *testTracer) OnCodeChange(addr common.Address, prevCodeHash common.Hash,
t.code = code t.code = code
} }
func (t *testTracer) OnCodeChangeV2(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte, reason CodeChangeReason) {
t.t.Logf("OnCodeChangeV2(%v, %v -> %v, %v)", addr, prevCodeHash, codeHash, reason)
t.code = code
}
func (t *testTracer) OnStorageChange(addr common.Address, slot common.Hash, prev common.Hash, new common.Hash) { func (t *testTracer) OnStorageChange(addr common.Address, slot common.Hash, prev common.Hash, new common.Hash) {
t.t.Logf("OnStorageCodeChange(%v, %v, %v -> %v)", addr, slot, prev, new) t.t.Logf("OnStorageCodeChange(%v, %v, %v -> %v)", addr, slot, prev, new)
if t.storage == nil { if t.storage == nil {
@ -232,6 +238,27 @@ func TestOnNonceChangeV2(t *testing.T) {
} }
} }
func TestOnCodeChangeV2(t *testing.T) {
tr := &testTracer{t: t}
wr, err := WrapWithJournal(&Hooks{OnCodeChangeV2: tr.OnCodeChangeV2})
if err != nil {
t.Fatalf("failed to wrap test tracer: %v", err)
}
addr := common.HexToAddress("0x1234")
code := []byte{1, 2, 3}
{
wr.OnEnter(2, 0, addr, addr, nil, 1000, big.NewInt(0))
wr.OnCodeChangeV2(addr, common.Hash{}, nil, crypto.Keccak256Hash(code), code, CodeChangeContractCreation)
wr.OnExit(2, nil, 100, nil, true)
}
// After revert, code should be nil
if tr.code != nil {
t.Fatalf("unexpected code after revert: %v", tr.code)
}
}
func TestAllHooksCalled(t *testing.T) { func TestAllHooksCalled(t *testing.T) {
tracer := newTracerAllHooks() tracer := newTracerAllHooks()
hooks := tracer.hooks() hooks := tracer.hooks()
@ -298,6 +325,7 @@ func newTracerAllHooks() *tracerAllHooks {
t.hooksCalled[hooksType.Field(i).Name] = false t.hooksCalled[hooksType.Field(i).Name] = false
} }
delete(t.hooksCalled, "OnNonceChange") delete(t.hooksCalled, "OnNonceChange")
delete(t.hooksCalled, "OnCodeChange")
return t return t
} }
@ -322,7 +350,7 @@ func (t *tracerAllHooks) hooks() *Hooks {
hooksValue := reflect.ValueOf(h).Elem() hooksValue := reflect.ValueOf(h).Elem()
for i := 0; i < hooksValue.NumField(); i++ { for i := 0; i < hooksValue.NumField(); i++ {
field := hooksValue.Type().Field(i) field := hooksValue.Type().Field(i)
if field.Name == "OnNonceChange" { if field.Name == "OnNonceChange" || field.Name == "OnCodeChange" {
continue continue
} }
hookMethod := reflect.MakeFunc(field.Type, func(args []reflect.Value) []reflect.Value { hookMethod := reflect.MakeFunc(field.Type, func(args []reflect.Value) []reflect.Value {

View file

@ -1298,6 +1298,13 @@ func (p *BlobPool) GetMetadata(hash common.Hash) *txpool.TxMetadata {
} }
// GetBlobs returns a number of blobs and proofs for the given versioned hashes. // GetBlobs returns a number of blobs and proofs for the given versioned hashes.
// Blobpool must place responses in the order given in the request, using null
// for any missing blobs.
//
// For instance, if the request is [A_versioned_hash, B_versioned_hash,
// C_versioned_hash] and blobpool has data for blobs A and C, but doesn't have
// data for B, the response MUST be [A, null, C].
//
// This is a utility method for the engine API, enabling consensus clients to // This is a utility method for the engine API, enabling consensus clients to
// retrieve blobs from the pools directly instead of the network. // retrieve blobs from the pools directly instead of the network.
func (p *BlobPool) GetBlobs(vhashes []common.Hash, version byte) ([]*kzg4844.Blob, []kzg4844.Commitment, [][]kzg4844.Proof, error) { func (p *BlobPool) GetBlobs(vhashes []common.Hash, version byte) ([]*kzg4844.Blob, []kzg4844.Commitment, [][]kzg4844.Proof, error) {
@ -1317,26 +1324,30 @@ func (p *BlobPool) GetBlobs(vhashes []common.Hash, version byte) ([]*kzg4844.Blo
if _, ok := filled[vhash]; ok { if _, ok := filled[vhash]; ok {
continue continue
} }
// Retrieve the corresponding blob tx with the vhash // Retrieve the corresponding blob tx with the vhash, skip blob resolution
// if it's not found locally and place the null instead.
p.lock.RLock() p.lock.RLock()
txID, exists := p.lookup.storeidOfBlob(vhash) txID, exists := p.lookup.storeidOfBlob(vhash)
p.lock.RUnlock() p.lock.RUnlock()
if !exists { if !exists {
return nil, nil, nil, fmt.Errorf("blob with vhash %x is not found", vhash) continue
} }
data, err := p.store.Get(txID) data, err := p.store.Get(txID)
if err != nil { if err != nil {
return nil, nil, nil, err log.Error("Tracked blob transaction missing from store", "id", txID, "err", err)
continue
} }
// Decode the blob transaction // Decode the blob transaction
tx := new(types.Transaction) tx := new(types.Transaction)
if err := rlp.DecodeBytes(data, tx); err != nil { if err := rlp.DecodeBytes(data, tx); err != nil {
return nil, nil, nil, err log.Error("Blobs corrupted for traced transaction", "id", txID, "err", err)
continue
} }
sidecar := tx.BlobTxSidecar() sidecar := tx.BlobTxSidecar()
if sidecar == nil { if sidecar == nil {
return nil, nil, nil, fmt.Errorf("blob tx without sidecar %x", tx.Hash()) log.Error("Blob tx without sidecar", "hash", tx.Hash(), "id", txID)
continue
} }
// Traverse the blobs in the transaction // Traverse the blobs in the transaction
for i, hash := range tx.BlobHashes() { for i, hash := range tx.BlobHashes() {
@ -1397,6 +1408,31 @@ func (p *BlobPool) AvailableBlobs(vhashes []common.Hash) int {
return available return available
} }
// convertSidecar converts the legacy sidecar in the submitted transactions
// if Osaka fork has been activated.
func (p *BlobPool) convertSidecar(txs []*types.Transaction) ([]*types.Transaction, []error) {
head := p.chain.CurrentBlock()
if !p.chain.Config().IsOsaka(head.Number, head.Time) {
return txs, make([]error, len(txs))
}
var errs []error
for _, tx := range txs {
sidecar := tx.BlobTxSidecar()
if sidecar == nil {
errs = append(errs, errors.New("missing sidecar in blob transaction"))
continue
}
if sidecar.Version == types.BlobSidecarVersion0 {
if err := sidecar.ToV1(); err != nil {
errs = append(errs, err)
continue
}
}
errs = append(errs, nil)
}
return txs, errs
}
// Add inserts a set of blob transactions into the pool if they pass validation (both // Add inserts a set of blob transactions into the pool if they pass validation (both
// consensus validity and pool restrictions). // consensus validity and pool restrictions).
// //
@ -1404,10 +1440,14 @@ func (p *BlobPool) AvailableBlobs(vhashes []common.Hash) int {
// related to the add is finished. Only use this during tests for determinism. // related to the add is finished. Only use this during tests for determinism.
func (p *BlobPool) Add(txs []*types.Transaction, sync bool) []error { func (p *BlobPool) Add(txs []*types.Transaction, sync bool) []error {
var ( var (
errs []error
adds = make([]*types.Transaction, 0, len(txs)) adds = make([]*types.Transaction, 0, len(txs))
errs = make([]error, len(txs))
) )
txs, errs = p.convertSidecar(txs)
for i, tx := range txs { for i, tx := range txs {
if errs[i] != nil {
continue
}
errs[i] = p.add(tx) errs[i] = p.add(tx)
if errs[i] == nil { if errs[i] == nil {
adds = append(adds, tx.WithoutBlobTxSidecar()) adds = append(adds, tx.WithoutBlobTxSidecar())

View file

@ -24,9 +24,11 @@ import (
"fmt" "fmt"
"math" "math"
"math/big" "math/big"
"math/rand"
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
"slices"
"sync" "sync"
"testing" "testing"
@ -40,6 +42,7 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/internal/testrand"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/holiman/billy" "github.com/holiman/billy"
@ -50,6 +53,7 @@ var (
testBlobs []*kzg4844.Blob testBlobs []*kzg4844.Blob
testBlobCommits []kzg4844.Commitment testBlobCommits []kzg4844.Commitment
testBlobProofs []kzg4844.Proof testBlobProofs []kzg4844.Proof
testBlobCellProofs [][]kzg4844.Proof
testBlobVHashes [][32]byte testBlobVHashes [][32]byte
testBlobIndices = make(map[[32]byte]int) testBlobIndices = make(map[[32]byte]int)
) )
@ -67,6 +71,9 @@ func init() {
testBlobProof, _ := kzg4844.ComputeBlobProof(testBlob, testBlobCommit) testBlobProof, _ := kzg4844.ComputeBlobProof(testBlob, testBlobCommit)
testBlobProofs = append(testBlobProofs, testBlobProof) testBlobProofs = append(testBlobProofs, testBlobProof)
testBlobCellProof, _ := kzg4844.ComputeCellProofs(testBlob)
testBlobCellProofs = append(testBlobCellProofs, testBlobCellProof)
testBlobVHash := kzg4844.CalcBlobHashV1(sha256.New(), &testBlobCommit) testBlobVHash := kzg4844.CalcBlobHashV1(sha256.New(), &testBlobCommit)
testBlobIndices[testBlobVHash] = len(testBlobVHashes) testBlobIndices[testBlobVHash] = len(testBlobVHashes)
testBlobVHashes = append(testBlobVHashes, testBlobVHash) testBlobVHashes = append(testBlobVHashes, testBlobVHash)
@ -257,8 +264,8 @@ func makeUnsignedTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap
return makeUnsignedTxWithTestBlob(nonce, gasTipCap, gasFeeCap, blobFeeCap, rnd.Intn(len(testBlobs))) return makeUnsignedTxWithTestBlob(nonce, gasTipCap, gasFeeCap, blobFeeCap, rnd.Intn(len(testBlobs)))
} }
// makeUnsignedTx is a utility method to construct a random blob transaction // makeUnsignedTxWithTestBlob is a utility method to construct a random blob transaction
// without signing it. // with a specific test blob without signing it.
func makeUnsignedTxWithTestBlob(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64, blobIdx int) *types.BlobTx { func makeUnsignedTxWithTestBlob(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64, blobIdx int) *types.BlobTx {
return &types.BlobTx{ return &types.BlobTx{
ChainID: uint256.MustFromBig(params.MainnetChainConfig.ChainID), ChainID: uint256.MustFromBig(params.MainnetChainConfig.ChainID),
@ -416,24 +423,40 @@ func verifyBlobRetrievals(t *testing.T, pool *BlobPool) {
hashes = append(hashes, tx.vhashes...) hashes = append(hashes, tx.vhashes...)
} }
} }
blobs, _, proofs, err := pool.GetBlobs(hashes, types.BlobSidecarVersion0) blobs1, _, proofs1, err := pool.GetBlobs(hashes, types.BlobSidecarVersion0)
if err != nil {
t.Fatal(err)
}
blobs2, _, proofs2, err := pool.GetBlobs(hashes, types.BlobSidecarVersion1)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
// Cross validate what we received vs what we wanted // Cross validate what we received vs what we wanted
if len(blobs) != len(hashes) || len(proofs) != len(hashes) { if len(blobs1) != len(hashes) || len(proofs1) != len(hashes) {
t.Errorf("retrieved blobs/proofs size mismatch: have %d/%d, want %d", len(blobs), len(proofs), len(hashes)) t.Errorf("retrieved blobs/proofs size mismatch: have %d/%d, want %d", len(blobs1), len(proofs1), len(hashes))
return
}
if len(blobs2) != len(hashes) || len(proofs2) != len(hashes) {
t.Errorf("retrieved blobs/proofs size mismatch: have %d/%d, want blobs %d, want proofs: %d", len(blobs2), len(proofs2), len(hashes), len(hashes))
return return
} }
for i, hash := range hashes { for i, hash := range hashes {
// If an item is missing, but shouldn't, error // If an item is missing, but shouldn't, error
if blobs[i] == nil || proofs[i] == nil { if blobs1[i] == nil || proofs1[i] == nil {
t.Errorf("tracked blob retrieval failed: item %d, hash %x", i, hash)
continue
}
if blobs2[i] == nil || proofs2[i] == nil {
t.Errorf("tracked blob retrieval failed: item %d, hash %x", i, hash) t.Errorf("tracked blob retrieval failed: item %d, hash %x", i, hash)
continue continue
} }
// Item retrieved, make sure it matches the expectation // Item retrieved, make sure it matches the expectation
index := testBlobIndices[hash] index := testBlobIndices[hash]
if *blobs[i] != *testBlobs[index] || proofs[i][0] != testBlobProofs[index] { if *blobs1[i] != *testBlobs[index] || proofs1[i][0] != testBlobProofs[index] {
t.Errorf("retrieved blob or proof mismatch: item %d, hash %x", i, hash)
continue
}
if *blobs2[i] != *testBlobs[index] || !slices.Equal(proofs2[i], testBlobCellProofs[index]) {
t.Errorf("retrieved blob or proof mismatch: item %d, hash %x", i, hash) t.Errorf("retrieved blob or proof mismatch: item %d, hash %x", i, hash)
continue continue
} }
@ -1668,6 +1691,49 @@ func TestAdd(t *testing.T) {
} }
} }
// Tests that adding the transactions with legacy sidecar and expect them to
// be converted to new format correctly.
func TestAddLegacyBlobTx(t *testing.T) {
var (
key1, _ = crypto.GenerateKey()
key2, _ = crypto.GenerateKey()
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
addr2 = crypto.PubkeyToAddress(key2.PublicKey)
)
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
statedb.AddBalance(addr1, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
statedb.AddBalance(addr2, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
statedb.Commit(0, true, false)
chain := &testBlockChain{
config: params.MergedTestChainConfig,
basefee: uint256.NewInt(1050),
blobfee: uint256.NewInt(105),
statedb: statedb,
}
pool := New(Config{Datadir: t.TempDir()}, chain, nil)
if err := pool.Init(1, chain.CurrentBlock(), newReserver()); err != nil {
t.Fatalf("failed to create blob pool: %v", err)
}
// Attempt to add legacy blob transactions.
var (
tx1 = makeMultiBlobTx(0, 1, 1000, 100, 6, 0, key1, types.BlobSidecarVersion0)
tx2 = makeMultiBlobTx(0, 1, 800, 70, 6, 6, key2, types.BlobSidecarVersion0)
tx3 = makeMultiBlobTx(1, 1, 800, 70, 6, 12, key2, types.BlobSidecarVersion1)
)
errs := pool.Add([]*types.Transaction{tx1, tx2, tx3}, true)
for _, err := range errs {
if err != nil {
t.Fatalf("failed to add tx: %v", err)
}
}
verifyPoolInternals(t, pool)
pool.Close()
}
func TestGetBlobs(t *testing.T) { func TestGetBlobs(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)))
@ -1752,8 +1818,8 @@ func TestGetBlobs(t *testing.T) {
cases := []struct { cases := []struct {
start int start int
limit int limit int
fillRandom bool
version byte version byte
expErr bool
}{ }{
{ {
start: 0, limit: 6, start: 0, limit: 6,
@ -1763,6 +1829,14 @@ func TestGetBlobs(t *testing.T) {
start: 0, limit: 6, start: 0, limit: 6,
version: types.BlobSidecarVersion1, version: types.BlobSidecarVersion1,
}, },
{
start: 0, limit: 6, fillRandom: true,
version: types.BlobSidecarVersion0,
},
{
start: 0, limit: 6, fillRandom: true,
version: types.BlobSidecarVersion1,
},
{ {
start: 3, limit: 9, start: 3, limit: 9,
version: types.BlobSidecarVersion0, version: types.BlobSidecarVersion0,
@ -1771,6 +1845,14 @@ func TestGetBlobs(t *testing.T) {
start: 3, limit: 9, start: 3, limit: 9,
version: types.BlobSidecarVersion1, version: types.BlobSidecarVersion1,
}, },
{
start: 3, limit: 9, fillRandom: true,
version: types.BlobSidecarVersion0,
},
{
start: 3, limit: 9, fillRandom: true,
version: types.BlobSidecarVersion1,
},
{ {
start: 3, limit: 15, start: 3, limit: 15,
version: types.BlobSidecarVersion0, version: types.BlobSidecarVersion0,
@ -1779,6 +1861,14 @@ func TestGetBlobs(t *testing.T) {
start: 3, limit: 15, start: 3, limit: 15,
version: types.BlobSidecarVersion1, version: types.BlobSidecarVersion1,
}, },
{
start: 3, limit: 15, fillRandom: true,
version: types.BlobSidecarVersion0,
},
{
start: 3, limit: 15, fillRandom: true,
version: types.BlobSidecarVersion1,
},
{ {
start: 0, limit: 18, start: 0, limit: 18,
version: types.BlobSidecarVersion0, version: types.BlobSidecarVersion0,
@ -1788,46 +1878,69 @@ func TestGetBlobs(t *testing.T) {
version: types.BlobSidecarVersion1, version: types.BlobSidecarVersion1,
}, },
{ {
start: 18, limit: 20, start: 0, limit: 18, fillRandom: true,
version: types.BlobSidecarVersion0, version: types.BlobSidecarVersion0,
expErr: true, },
{
start: 0, limit: 18, fillRandom: true,
version: types.BlobSidecarVersion1,
}, },
} }
for i, c := range cases { for i, c := range cases {
var vhashes []common.Hash var (
vhashes []common.Hash
filled = make(map[int]struct{})
)
if c.fillRandom {
filled[len(vhashes)] = struct{}{}
vhashes = append(vhashes, testrand.Hash())
}
for j := c.start; j < c.limit; j++ { for j := c.start; j < c.limit; j++ {
vhashes = append(vhashes, testBlobVHashes[j]) vhashes = append(vhashes, testBlobVHashes[j])
if c.fillRandom && rand.Intn(2) == 0 {
filled[len(vhashes)] = struct{}{}
vhashes = append(vhashes, testrand.Hash())
}
}
if c.fillRandom {
filled[len(vhashes)] = struct{}{}
vhashes = append(vhashes, testrand.Hash())
} }
blobs, _, proofs, err := pool.GetBlobs(vhashes, c.version) blobs, _, proofs, err := pool.GetBlobs(vhashes, c.version)
if c.expErr {
if err == nil {
t.Errorf("Unexpected return, want error for case %d", i)
}
} else {
if err != nil { if err != nil {
t.Errorf("Unexpected error for case %d, %v", i, err) t.Errorf("Unexpected error for case %d, %v", i, err)
} }
// Cross validate what we received vs what we wanted // Cross validate what we received vs what we wanted
length := c.limit - c.start length := c.limit - c.start
if len(blobs) != length || len(proofs) != length { wantLen := length + len(filled)
t.Errorf("retrieved blobs/proofs size mismatch: have %d/%d, want %d", len(blobs), len(proofs), length) if len(blobs) != wantLen || len(proofs) != wantLen {
t.Errorf("retrieved blobs/proofs size mismatch: have %d/%d, want %d", len(blobs), len(proofs), wantLen)
continue continue
} }
var unknown int
for j := 0; j < len(blobs); j++ { for j := 0; j < len(blobs); j++ {
if _, exist := filled[j]; exist {
if blobs[j] != nil || proofs[j] != nil {
t.Errorf("Unexpected blob and proof, item %d", j)
}
unknown++
continue
}
// If an item is missing, but shouldn't, error // If an item is missing, but shouldn't, error
if blobs[j] == nil || proofs[j] == nil { if blobs[j] == nil || proofs[j] == nil {
t.Errorf("tracked blob retrieval failed: item %d, hash %x", j, vhashes[j]) t.Errorf("tracked blob retrieval failed: item %d, hash %x", j, vhashes[j])
continue continue
} }
// Item retrieved, make sure the blob matches the expectation // Item retrieved, make sure the blob matches the expectation
if *blobs[j] != *testBlobs[c.start+j] { if *blobs[j] != *testBlobs[c.start+j-unknown] {
t.Errorf("retrieved blob mismatch: item %d, hash %x", j, vhashes[j]) t.Errorf("retrieved blob mismatch: item %d, hash %x", j, vhashes[j])
continue continue
} }
// Item retrieved, make sure the proof matches the expectation // Item retrieved, make sure the proof matches the expectation
if c.version == types.BlobSidecarVersion0 { if c.version == types.BlobSidecarVersion0 {
if proofs[j][0] != testBlobProofs[c.start+j] { if proofs[j][0] != testBlobProofs[c.start+j-unknown] {
t.Errorf("retrieved proof mismatch: item %d, hash %x", j, vhashes[j]) t.Errorf("retrieved proof mismatch: item %d, hash %x", j, vhashes[j])
} }
} else { } else {
@ -1838,8 +1951,6 @@ func TestGetBlobs(t *testing.T) {
} }
} }
} }
}
pool.Close() pool.Close()
} }

View file

@ -514,26 +514,15 @@ func (pool *LegacyPool) Pending(filter txpool.PendingFilter) map[common.Address]
pool.mu.Lock() pool.mu.Lock()
defer pool.mu.Unlock() defer pool.mu.Unlock()
// Convert the new uint256.Int types to the old big.Int ones used by the legacy pool
var (
minTipBig *big.Int
baseFeeBig *big.Int
)
if filter.MinTip != nil {
minTipBig = filter.MinTip.ToBig()
}
if filter.BaseFee != nil {
baseFeeBig = filter.BaseFee.ToBig()
}
pending := make(map[common.Address][]*txpool.LazyTransaction, len(pool.pending)) pending := make(map[common.Address][]*txpool.LazyTransaction, len(pool.pending))
for addr, list := range pool.pending { for addr, list := range pool.pending {
txs := list.Flatten() txs := list.Flatten()
// If the miner requests tip enforcement, cap the lists now // If the miner requests tip enforcement, cap the lists now
if minTipBig != nil || filter.GasLimitCap != 0 { if filter.MinTip != nil || filter.GasLimitCap != 0 {
for i, tx := range txs { for i, tx := range txs {
if minTipBig != nil { if filter.MinTip != nil {
if tx.EffectiveGasTipIntCmp(minTipBig, baseFeeBig) < 0 { if tx.EffectiveGasTipIntCmp(filter.MinTip, filter.BaseFee) < 0 {
txs = txs[:i] txs = txs[:i]
break break
} }

View file

@ -2292,8 +2292,8 @@ func TestSetCodeTransactions(t *testing.T) {
pending: 1, pending: 1,
run: func(name string) { run: func(name string) {
aa := common.Address{0xaa, 0xaa} aa := common.Address{0xaa, 0xaa}
statedb.SetCode(addrA, append(types.DelegationPrefix, aa.Bytes()...)) statedb.SetCode(addrA, append(types.DelegationPrefix, aa.Bytes()...), tracing.CodeChangeUnspecified)
statedb.SetCode(aa, []byte{byte(vm.ADDRESS), byte(vm.PUSH0), byte(vm.SSTORE)}) statedb.SetCode(aa, []byte{byte(vm.ADDRESS), byte(vm.PUSH0), byte(vm.SSTORE)}, tracing.CodeChangeUnspecified)
// Send gapped transaction, it should be rejected. // Send gapped transaction, it should be rejected.
if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrOutOfOrderTxFromDelegated) { if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrOutOfOrderTxFromDelegated) {
@ -2317,7 +2317,7 @@ func TestSetCodeTransactions(t *testing.T) {
} }
// Reset the delegation, avoid leaking state into the other tests // Reset the delegation, avoid leaking state into the other tests
statedb.SetCode(addrA, nil) statedb.SetCode(addrA, nil, tracing.CodeChangeUnspecified)
}, },
}, },
{ {
@ -2583,7 +2583,7 @@ func TestSetCodeTransactionsReorg(t *testing.T) {
} }
// Simulate the chain moving // Simulate the chain moving
blockchain.statedb.SetNonce(addrA, 1, tracing.NonceChangeAuthorization) blockchain.statedb.SetNonce(addrA, 1, tracing.NonceChangeAuthorization)
blockchain.statedb.SetCode(addrA, types.AddressToDelegation(auth.Address)) blockchain.statedb.SetCode(addrA, types.AddressToDelegation(auth.Address), tracing.CodeChangeUnspecified)
<-pool.requestReset(nil, nil) <-pool.requestReset(nil, nil)
// Set an authorization for 0x00 // Set an authorization for 0x00
auth, _ = types.SignSetCode(keyA, types.SetCodeAuthorization{ auth, _ = types.SignSetCode(keyA, types.SetCodeAuthorization{
@ -2601,7 +2601,7 @@ func TestSetCodeTransactionsReorg(t *testing.T) {
} }
// Simulate the chain moving // Simulate the chain moving
blockchain.statedb.SetNonce(addrA, 2, tracing.NonceChangeAuthorization) blockchain.statedb.SetNonce(addrA, 2, tracing.NonceChangeAuthorization)
blockchain.statedb.SetCode(addrA, nil) blockchain.statedb.SetCode(addrA, nil, tracing.CodeChangeUnspecified)
<-pool.requestReset(nil, nil) <-pool.requestReset(nil, nil)
// Now send two transactions from addrA // Now send two transactions from addrA
if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1000), keyA)); err != nil { if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1000), keyA)); err != nil {

View file

@ -475,7 +475,7 @@ func (l *list) subTotalCost(txs []*types.Transaction) {
// then the heap is sorted based on the effective tip based on the given base fee. // then the heap is sorted based on the effective tip based on the given base fee.
// If baseFee is nil then the sorting is based on gasFeeCap. // If baseFee is nil then the sorting is based on gasFeeCap.
type priceHeap struct { type priceHeap struct {
baseFee *big.Int // heap should always be re-sorted after baseFee is changed baseFee *uint256.Int // heap should always be re-sorted after baseFee is changed
list []*types.Transaction list []*types.Transaction
} }
@ -677,6 +677,10 @@ func (l *pricedList) Reheap() {
// SetBaseFee updates the base fee and triggers a re-heap. Note that Removed is not // SetBaseFee updates the base fee and triggers a re-heap. Note that Removed is not
// necessary to call right before SetBaseFee when processing a new block. // necessary to call right before SetBaseFee when processing a new block.
func (l *pricedList) SetBaseFee(baseFee *big.Int) { func (l *pricedList) SetBaseFee(baseFee *big.Int) {
l.urgent.baseFee = baseFee base := new(uint256.Int)
if baseFee != nil {
base.SetFromBig(baseFee)
}
l.urgent.baseFee = base
l.Reheap() l.Reheap()
} }

View file

@ -22,7 +22,6 @@ import (
"math/big" "math/big"
"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/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
@ -167,9 +166,8 @@ func validateBlobTx(tx *types.Transaction, head *types.Header, opts *ValidationO
if len(hashes) == 0 { if len(hashes) == 0 {
return errors.New("blobless blob transaction") return errors.New("blobless blob transaction")
} }
maxBlobs := eip4844.MaxBlobsPerBlock(opts.Config, head.Time) if len(hashes) > params.BlobTxMaxBlobs {
if len(hashes) > maxBlobs { return fmt.Errorf("too many blobs in transaction: have %d, permitted %d", len(hashes), params.BlobTxMaxBlobs)
return fmt.Errorf("too many blobs in transaction: have %d, permitted %d", len(hashes), maxBlobs)
} }
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))

View file

@ -45,4 +45,7 @@ var (
// EmptyVerkleHash is the known hash of an empty verkle trie. // EmptyVerkleHash is the known hash of an empty verkle trie.
EmptyVerkleHash = common.Hash{} EmptyVerkleHash = common.Hash{}
// EmptyBinaryHash is the known hash of an empty binary trie.
EmptyBinaryHash = common.Hash{}
) )

View file

@ -28,6 +28,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/holiman/uint256"
) )
var ( var (
@ -36,6 +37,7 @@ var (
ErrInvalidTxType = errors.New("transaction type not valid in this context") ErrInvalidTxType = errors.New("transaction type not valid in this context")
ErrTxTypeNotSupported = errors.New("transaction type not supported") ErrTxTypeNotSupported = errors.New("transaction type not supported")
ErrGasFeeCapTooLow = errors.New("fee cap less than base fee") ErrGasFeeCapTooLow = errors.New("fee cap less than base fee")
ErrUint256Overflow = errors.New("bigint overflow, too large for uint256")
errShortTypedTx = errors.New("typed transaction too short") errShortTypedTx = errors.New("typed transaction too short")
errInvalidYParity = errors.New("'yParity' field must be 0 or 1") errInvalidYParity = errors.New("'yParity' field must be 0 or 1")
errVYParityMismatch = errors.New("'v' and 'yParity' fields do not match") errVYParityMismatch = errors.New("'v' and 'yParity' fields do not match")
@ -357,54 +359,66 @@ func (tx *Transaction) GasTipCapIntCmp(other *big.Int) int {
} }
// EffectiveGasTip returns the effective miner gasTipCap for the given base fee. // EffectiveGasTip returns the effective miner gasTipCap for the given base fee.
// Note: if the effective gasTipCap is negative, this method returns both error // Note: if the effective gasTipCap would be negative, this method
// the actual negative value, _and_ ErrGasFeeCapTooLow // returns ErrGasFeeCapTooLow, and value is undefined.
func (tx *Transaction) EffectiveGasTip(baseFee *big.Int) (*big.Int, error) { func (tx *Transaction) EffectiveGasTip(baseFee *big.Int) (*big.Int, error) {
dst := new(big.Int) dst := new(uint256.Int)
err := tx.calcEffectiveGasTip(dst, baseFee) base := new(uint256.Int)
return dst, err if baseFee != nil {
if base.SetFromBig(baseFee) {
return nil, ErrUint256Overflow
}
}
err := tx.calcEffectiveGasTip(dst, base)
return dst.ToBig(), err
} }
// calcEffectiveGasTip calculates the effective gas tip of the transaction and // calcEffectiveGasTip calculates the effective gas tip of the transaction and
// saves the result to dst. // saves the result to dst.
func (tx *Transaction) calcEffectiveGasTip(dst *big.Int, baseFee *big.Int) error { func (tx *Transaction) calcEffectiveGasTip(dst *uint256.Int, baseFee *uint256.Int) error {
if baseFee == nil { if baseFee == nil {
dst.Set(tx.inner.gasTipCap()) if dst.SetFromBig(tx.inner.gasTipCap()) {
return ErrUint256Overflow
}
return nil return nil
} }
var err error var err error
gasFeeCap := tx.inner.gasFeeCap() if dst.SetFromBig(tx.inner.gasFeeCap()) {
if gasFeeCap.Cmp(baseFee) < 0 { return ErrUint256Overflow
}
if dst.Cmp(baseFee) < 0 {
err = ErrGasFeeCapTooLow err = ErrGasFeeCapTooLow
} }
dst.Sub(gasFeeCap, baseFee) dst.Sub(dst, baseFee)
gasTipCap := tx.inner.gasTipCap() gasTipCap := new(uint256.Int)
if gasTipCap.SetFromBig(tx.inner.gasTipCap()) {
return ErrUint256Overflow
}
if gasTipCap.Cmp(dst) < 0 { if gasTipCap.Cmp(dst) < 0 {
dst.Set(gasTipCap) dst.Set(gasTipCap)
} }
return err return err
} }
// EffectiveGasTipCmp compares the effective gasTipCap of two transactions assuming the given base fee. func (tx *Transaction) EffectiveGasTipCmp(other *Transaction, baseFee *uint256.Int) int {
func (tx *Transaction) EffectiveGasTipCmp(other *Transaction, baseFee *big.Int) int {
if baseFee == nil { if baseFee == nil {
return tx.GasTipCapCmp(other) return tx.GasTipCapCmp(other)
} }
// Use more efficient internal method. // Use more efficient internal method.
txTip, otherTip := new(big.Int), new(big.Int) txTip, otherTip := new(uint256.Int), new(uint256.Int)
tx.calcEffectiveGasTip(txTip, baseFee) tx.calcEffectiveGasTip(txTip, baseFee)
other.calcEffectiveGasTip(otherTip, baseFee) other.calcEffectiveGasTip(otherTip, baseFee)
return txTip.Cmp(otherTip) return txTip.Cmp(otherTip)
} }
// EffectiveGasTipIntCmp compares the effective gasTipCap of a transaction to the given gasTipCap. // EffectiveGasTipIntCmp compares the effective gasTipCap of a transaction to the given gasTipCap.
func (tx *Transaction) EffectiveGasTipIntCmp(other *big.Int, baseFee *big.Int) int { func (tx *Transaction) EffectiveGasTipIntCmp(other *uint256.Int, baseFee *uint256.Int) int {
if baseFee == nil { if baseFee == nil {
return tx.GasTipCapIntCmp(other) return tx.GasTipCapIntCmp(other.ToBig())
} }
txTip := new(big.Int) txTip := new(uint256.Int)
tx.calcEffectiveGasTip(txTip, baseFee) tx.calcEffectiveGasTip(txTip, baseFee)
return txTip.Cmp(other) return txTip.Cmp(other)
} }

View file

@ -31,6 +31,7 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/holiman/uint256"
) )
// The values in those tests are from the Transaction Tests // The values in those tests are from the Transaction Tests
@ -609,12 +610,12 @@ func BenchmarkEffectiveGasTip(b *testing.B) {
Data: nil, Data: nil,
} }
tx, _ := SignNewTx(key, signer, txdata) tx, _ := SignNewTx(key, signer, txdata)
baseFee := big.NewInt(1000000000) // 1 gwei baseFee := uint256.NewInt(1000000000) // 1 gwei
b.Run("Original", func(b *testing.B) { b.Run("Original", func(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, err := tx.EffectiveGasTip(baseFee) _, err := tx.EffectiveGasTip(baseFee.ToBig())
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
@ -623,7 +624,7 @@ func BenchmarkEffectiveGasTip(b *testing.B) {
b.Run("IntoMethod", func(b *testing.B) { b.Run("IntoMethod", func(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
dst := new(big.Int) dst := new(uint256.Int)
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
err := tx.calcEffectiveGasTip(dst, baseFee) err := tx.calcEffectiveGasTip(dst, baseFee)
if err != nil { if err != nil {
@ -634,9 +635,6 @@ func BenchmarkEffectiveGasTip(b *testing.B) {
} }
func TestEffectiveGasTipInto(t *testing.T) { func TestEffectiveGasTipInto(t *testing.T) {
signer := LatestSigner(params.TestChainConfig)
key, _ := crypto.GenerateKey()
testCases := []struct { testCases := []struct {
tipCap int64 tipCap int64
feeCap int64 feeCap int64
@ -652,8 +650,26 @@ func TestEffectiveGasTipInto(t *testing.T) {
{tipCap: 50, feeCap: 100, baseFee: nil}, // nil base fee {tipCap: 50, feeCap: 100, baseFee: nil}, // nil base fee
} }
// original, non-allocation golfed version
orig := func(tx *Transaction, baseFee *big.Int) (*big.Int, error) {
if baseFee == nil {
return tx.GasTipCap(), nil
}
var err error
gasFeeCap := tx.GasFeeCap()
if gasFeeCap.Cmp(baseFee) < 0 {
err = ErrGasFeeCapTooLow
}
gasFeeCap = gasFeeCap.Sub(gasFeeCap, baseFee)
gasTipCap := tx.GasTipCap()
if gasTipCap.Cmp(gasFeeCap) < 0 {
return gasTipCap, err
}
return gasFeeCap, err
}
for i, tc := range testCases { for i, tc := range testCases {
txdata := &DynamicFeeTx{ tx := NewTx(&DynamicFeeTx{
ChainID: big.NewInt(1), ChainID: big.NewInt(1),
Nonce: 0, Nonce: 0,
GasTipCap: big.NewInt(tc.tipCap), GasTipCap: big.NewInt(tc.tipCap),
@ -662,27 +678,28 @@ func TestEffectiveGasTipInto(t *testing.T) {
To: &common.Address{}, To: &common.Address{},
Value: big.NewInt(0), Value: big.NewInt(0),
Data: nil, Data: nil,
} })
tx, _ := SignNewTx(key, signer, txdata)
var baseFee *big.Int var baseFee *big.Int
var baseFee2 *uint256.Int
if tc.baseFee != nil { if tc.baseFee != nil {
baseFee = big.NewInt(*tc.baseFee) baseFee = big.NewInt(*tc.baseFee)
baseFee2 = uint256.NewInt(uint64(*tc.baseFee))
} }
// Get result from original method // Get result from original method
orig, origErr := tx.EffectiveGasTip(baseFee) orig, origErr := orig(tx, baseFee)
// Get result from new method // Get result from new method
dst := new(big.Int) dst := new(uint256.Int)
newErr := tx.calcEffectiveGasTip(dst, baseFee) newErr := tx.calcEffectiveGasTip(dst, baseFee2)
// Compare results // Compare results
if (origErr != nil) != (newErr != nil) { if (origErr != nil) != (newErr != nil) {
t.Fatalf("case %d: error mismatch: orig %v, new %v", i, origErr, newErr) t.Fatalf("case %d: error mismatch: orig %v, new %v", i, origErr, newErr)
} }
if orig.Cmp(dst) != 0 { if origErr == nil && orig.Cmp(dst.ToBig()) != 0 {
t.Fatalf("case %d: result mismatch: orig %v, new %v", i, orig, dst) t.Fatalf("case %d: result mismatch: orig %v, new %v", i, orig, dst)
} }
} }
@ -692,3 +709,28 @@ func TestEffectiveGasTipInto(t *testing.T) {
func intPtr(i int64) *int64 { func intPtr(i int64) *int64 {
return &i return &i
} }
func BenchmarkEffectiveGasTipCmp(b *testing.B) {
signer := LatestSigner(params.TestChainConfig)
key, _ := crypto.GenerateKey()
txdata := &DynamicFeeTx{
ChainID: big.NewInt(1),
Nonce: 0,
GasTipCap: big.NewInt(2000000000),
GasFeeCap: big.NewInt(3000000000),
Gas: 21000,
To: &common.Address{},
Value: big.NewInt(0),
Data: nil,
}
tx, _ := SignNewTx(key, signer, txdata)
other, _ := SignNewTx(key, signer, txdata)
baseFee := uint256.NewInt(1000000000) // 1 gwei
b.Run("Original", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
tx.EffectiveGasTipCmp(other, baseFee)
}
})
}

View file

@ -232,7 +232,7 @@ func TestProcessParentBlockHash(t *testing.T) {
// etc // etc
checkBlockHashes := func(statedb *state.StateDB, isVerkle bool) { checkBlockHashes := func(statedb *state.StateDB, isVerkle bool) {
statedb.SetNonce(params.HistoryStorageAddress, 1, tracing.NonceChangeUnspecified) statedb.SetNonce(params.HistoryStorageAddress, 1, tracing.NonceChangeUnspecified)
statedb.SetCode(params.HistoryStorageAddress, params.HistoryStorageCode) statedb.SetCode(params.HistoryStorageAddress, params.HistoryStorageCode, tracing.CodeChangeUnspecified)
// Process n blocks, from 1 .. num // Process n blocks, from 1 .. num
var num = 2 var num = 2
for i := 1; i <= num; i++ { for i := 1; i <= num; i++ {
@ -787,7 +787,7 @@ func TestProcessVerkleSelfDestructInSeparateTx(t *testing.T) {
} }
} }
// TestProcessVerkleSelfDestructInSeparateTx controls the contents of the witness after // TestProcessVerkleSelfDestructInSameTx controls the contents of the witness after
// a eip6780-compliant selfdestruct occurs. // a eip6780-compliant selfdestruct occurs.
func TestProcessVerkleSelfDestructInSameTx(t *testing.T) { func TestProcessVerkleSelfDestructInSameTx(t *testing.T) {
// The test txs were taken from a secondary testnet with chain id 69421 // The test txs were taken from a secondary testnet with chain id 69421

View file

@ -24,6 +24,7 @@ import (
"maps" "maps"
"math" "math"
"math/big" "math/big"
"math/bits"
"github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark-crypto/ecc"
bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381"
@ -38,6 +39,7 @@ import (
"github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/crypto/secp256r1" "github.com/ethereum/go-ethereum/crypto/secp256r1"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
"golang.org/x/crypto/ripemd160" "golang.org/x/crypto/ripemd160"
) )
@ -47,6 +49,7 @@ import (
type PrecompiledContract interface { type PrecompiledContract interface {
RequiredGas(input []byte) uint64 // RequiredPrice calculates the contract gas use RequiredGas(input []byte) uint64 // RequiredPrice calculates the contract gas use
Run(input []byte) ([]byte, error) // Run runs the precompiled contract Run(input []byte) ([]byte, error) // Run runs the precompiled contract
Name() string
} }
// PrecompiledContracts contains the precompiled contracts supported at the given fork. // PrecompiledContracts contains the precompiled contracts supported at the given fork.
@ -309,6 +312,10 @@ func (c *ecrecover) Run(input []byte) ([]byte, error) {
return common.LeftPadBytes(crypto.Keccak256(pubKey[1:])[12:], 32), nil return common.LeftPadBytes(crypto.Keccak256(pubKey[1:])[12:], 32), nil
} }
func (c *ecrecover) Name() string {
return "ECREC"
}
// SHA256 implemented as a native contract. // SHA256 implemented as a native contract.
type sha256hash struct{} type sha256hash struct{}
@ -324,6 +331,10 @@ func (c *sha256hash) Run(input []byte) ([]byte, error) {
return h[:], nil return h[:], nil
} }
func (c *sha256hash) Name() string {
return "SHA256"
}
// RIPEMD160 implemented as a native contract. // RIPEMD160 implemented as a native contract.
type ripemd160hash struct{} type ripemd160hash struct{}
@ -340,6 +351,10 @@ func (c *ripemd160hash) Run(input []byte) ([]byte, error) {
return common.LeftPadBytes(ripemd.Sum(nil), 32), nil return common.LeftPadBytes(ripemd.Sum(nil), 32), nil
} }
func (c *ripemd160hash) Name() string {
return "RIPEMD160"
}
// data copy implemented as a native contract. // data copy implemented as a native contract.
type dataCopy struct{} type dataCopy struct{}
@ -354,6 +369,10 @@ func (c *dataCopy) Run(in []byte) ([]byte, error) {
return common.CopyBytes(in), nil return common.CopyBytes(in), nil
} }
func (c *dataCopy) Name() string {
return "ID"
}
// bigModExp implements a native big integer exponential modular operation. // bigModExp implements a native big integer exponential modular operation.
type bigModExp struct { type bigModExp struct {
eip2565 bool eip2565 bool
@ -361,21 +380,7 @@ type bigModExp struct {
eip7883 bool eip7883 bool
} }
var ( // byzantiumMultComplexity implements the bigModexp multComplexity formula, as defined in EIP-198.
big1 = big.NewInt(1)
big3 = big.NewInt(3)
big7 = big.NewInt(7)
big20 = big.NewInt(20)
big32 = big.NewInt(32)
big64 = big.NewInt(64)
big96 = big.NewInt(96)
big480 = big.NewInt(480)
big1024 = big.NewInt(1024)
big3072 = big.NewInt(3072)
big199680 = big.NewInt(199680)
)
// modexpMultComplexity implements bigModexp multComplexity formula, as defined in EIP-198
// //
// def mult_complexity(x): // def mult_complexity(x):
// if x <= 64: return x ** 2 // if x <= 64: return x ** 2
@ -383,121 +388,216 @@ var (
// else: return x ** 2 // 16 + 480 * x - 199680 // else: return x ** 2 // 16 + 480 * x - 199680
// //
// where is x is max(length_of_MODULUS, length_of_BASE) // where is x is max(length_of_MODULUS, length_of_BASE)
func modexpMultComplexity(x *big.Int) *big.Int { // returns MaxUint64 if an overflow occurred.
func byzantiumMultComplexity(x uint64) uint64 {
switch { switch {
case x.Cmp(big64) <= 0: case x <= 64:
x.Mul(x, x) // x ** 2 return x * x
case x.Cmp(big1024) <= 0: case x <= 1024:
// (x ** 2 // 4 ) + ( 96 * x - 3072) // x^2 / 4 + 96*x - 3072
x = new(big.Int).Add( return x*x/4 + 96*x - 3072
new(big.Int).Rsh(new(big.Int).Mul(x, x), 2),
new(big.Int).Sub(new(big.Int).Mul(big96, x), big3072),
)
default: default:
// (x ** 2 // 16) + (480 * x - 199680) // For large x, use uint256 arithmetic to avoid overflow
x = new(big.Int).Add( // x^2 / 16 + 480*x - 199680
new(big.Int).Rsh(new(big.Int).Mul(x, x), 4),
new(big.Int).Sub(new(big.Int).Mul(big480, x), big199680), // xSqr = x^2 / 16
) carry, xSqr := bits.Mul64(x, x)
if carry != 0 {
return math.MaxUint64
}
xSqr = xSqr >> 4
// Calculate 480 * x (can't overflow if x^2 didn't overflow)
x480 := x * 480
// Calculate 480 * x - 199680 (will not underflow, since x > 1024)
x480 = x480 - 199680
// xSqr + x480
sum, carry := bits.Add64(xSqr, x480, 0)
if carry != 0 {
return math.MaxUint64
}
return sum
}
}
// berlinMultComplexity implements the multiplication complexity formula for Berlin.
//
// def mult_complexity(x):
//
// ceiling(x/8)^2
//
// where is x is max(length_of_MODULUS, length_of_BASE)
func berlinMultComplexity(x uint64) uint64 {
// x = (x + 7) / 8
x, carry := bits.Add64(x, 7, 0)
if carry != 0 {
return math.MaxUint64
}
x /= 8
// x^2
carry, x = bits.Mul64(x, x)
if carry != 0 {
return math.MaxUint64
} }
return x return x
} }
// osakaMultComplexity implements the multiplication complexity formula for Osaka.
//
// For x <= 32: returns 16
// For x > 32: returns 2 * ceiling(x/8)^2
func osakaMultComplexity(x uint64) uint64 {
if x <= 32 {
return 16
}
// For x > 32, return 2 * berlinMultComplexity(x)
result := berlinMultComplexity(x)
carry, result := bits.Mul64(result, 2)
if carry != 0 {
return math.MaxUint64
}
return result
}
// modexpIterationCount calculates the number of iterations for the modexp precompile.
// This is the adjusted exponent length used in gas calculation.
func modexpIterationCount(expLen uint64, expHead uint256.Int, multiplier uint64) uint64 {
var iterationCount uint64
// For large exponents (expLen > 32), add (expLen - 32) * multiplier
if expLen > 32 {
iterationCount = (expLen - 32) * multiplier
}
// Add the MSB position - 1 if expHead is non-zero
if bitLen := expHead.BitLen(); bitLen > 0 {
iterationCount += uint64(bitLen - 1)
}
return max(iterationCount, 1)
}
// byzantiumModexpGas calculates the gas cost for the modexp precompile using Byzantium rules.
func byzantiumModexpGas(baseLen, expLen, modLen uint64, expHead uint256.Int) uint64 {
const (
multiplier = 8
divisor = 20
)
maxLen := max(baseLen, modLen)
multComplexity := byzantiumMultComplexity(maxLen)
if multComplexity == math.MaxUint64 {
return math.MaxUint64
}
iterationCount := modexpIterationCount(expLen, expHead, multiplier)
// Calculate gas: (multComplexity * iterationCount) / divisor
carry, gas := bits.Mul64(iterationCount, multComplexity)
gas /= divisor
if carry != 0 {
return math.MaxUint64
}
return gas
}
// berlinModexpGas calculates the gas cost for the modexp precompile using Berlin rules.
func berlinModexpGas(baseLen, expLen, modLen uint64, expHead uint256.Int) uint64 {
const (
multiplier = 8
divisor = 3
minGas = 200
)
maxLen := max(baseLen, modLen)
multComplexity := berlinMultComplexity(maxLen)
if multComplexity == math.MaxUint64 {
return math.MaxUint64
}
iterationCount := modexpIterationCount(expLen, expHead, multiplier)
// Calculate gas: (multComplexity * iterationCount) / divisor
carry, gas := bits.Mul64(iterationCount, multComplexity)
gas /= divisor
if carry != 0 {
return math.MaxUint64
}
return max(gas, minGas)
}
// osakaModexpGas calculates the gas cost for the modexp precompile using Osaka rules.
func osakaModexpGas(baseLen, expLen, modLen uint64, expHead uint256.Int) uint64 {
const (
multiplier = 16
divisor = 3
minGas = 500
)
maxLen := max(baseLen, modLen)
multComplexity := osakaMultComplexity(maxLen)
if multComplexity == math.MaxUint64 {
return math.MaxUint64
}
iterationCount := modexpIterationCount(expLen, expHead, multiplier)
// Calculate gas: (multComplexity * iterationCount) / osakaDivisor
carry, gas := bits.Mul64(iterationCount, multComplexity)
if carry != 0 {
return math.MaxUint64
}
return max(gas, minGas)
}
// RequiredGas returns the gas required to execute the pre-compiled contract. // RequiredGas returns the gas required to execute the pre-compiled contract.
func (c *bigModExp) RequiredGas(input []byte) uint64 { func (c *bigModExp) RequiredGas(input []byte) uint64 {
var ( // Parse input lengths
baseLen = new(big.Int).SetBytes(getData(input, 0, 32)) baseLenBig := new(uint256.Int).SetBytes(getData(input, 0, 32))
expLen = new(big.Int).SetBytes(getData(input, 32, 32)) expLenBig := new(uint256.Int).SetBytes(getData(input, 32, 32))
modLen = new(big.Int).SetBytes(getData(input, 64, 32)) modLenBig := new(uint256.Int).SetBytes(getData(input, 64, 32))
)
// Convert to uint64, capping at max value
baseLen := baseLenBig.Uint64()
if !baseLenBig.IsUint64() {
baseLen = math.MaxUint64
}
expLen := expLenBig.Uint64()
if !expLenBig.IsUint64() {
expLen = math.MaxUint64
}
modLen := modLenBig.Uint64()
if !modLenBig.IsUint64() {
modLen = math.MaxUint64
}
// Skip the header
if len(input) > 96 { if len(input) > 96 {
input = input[96:] input = input[96:]
} else { } else {
input = input[:0] input = input[:0]
} }
// Retrieve the head 32 bytes of exp for the adjusted exponent length // Retrieve the head 32 bytes of exp for the adjusted exponent length
var expHead *big.Int var expHead uint256.Int
if big.NewInt(int64(len(input))).Cmp(baseLen) <= 0 { if uint64(len(input)) > baseLen {
expHead = new(big.Int) if expLen > 32 {
expHead.SetBytes(getData(input, baseLen, 32))
} else { } else {
if expLen.Cmp(big32) > 0 { // TODO: Check that if expLen < baseLen, then getData will return an empty slice
expHead = new(big.Int).SetBytes(getData(input, baseLen.Uint64(), 32)) expHead.SetBytes(getData(input, baseLen, expLen))
} else {
expHead = new(big.Int).SetBytes(getData(input, baseLen.Uint64(), expLen.Uint64()))
} }
} }
// Calculate the adjusted exponent length
var msb int // Choose the appropriate gas calculation based on the EIP flags
if bitlen := expHead.BitLen(); bitlen > 0 {
msb = bitlen - 1
}
adjExpLen := new(big.Int)
if expLen.Cmp(big32) > 0 {
adjExpLen.Sub(expLen, big32)
if c.eip7883 { if c.eip7883 {
adjExpLen.Lsh(adjExpLen, 4) return osakaModexpGas(baseLen, expLen, modLen, expHead)
} else if c.eip2565 {
return berlinModexpGas(baseLen, expLen, modLen, expHead)
} else { } else {
adjExpLen.Lsh(adjExpLen, 3) return byzantiumModexpGas(baseLen, expLen, modLen, expHead)
} }
} }
adjExpLen.Add(adjExpLen, big.NewInt(int64(msb)))
// Calculate the gas cost of the operation
gas := new(big.Int)
if modLen.Cmp(baseLen) < 0 {
gas.Set(baseLen)
} else {
gas.Set(modLen)
}
maxLenOver32 := gas.Cmp(big32) > 0
if c.eip2565 {
// EIP-2565 (Berlin fork) has three changes:
//
// 1. Different multComplexity (inlined here)
// in EIP-2565 (https://eips.ethereum.org/EIPS/eip-2565):
//
// def mult_complexity(x):
// ceiling(x/8)^2
//
// where is x is max(length_of_MODULUS, length_of_BASE)
gas.Add(gas, big7)
gas.Rsh(gas, 3)
gas.Mul(gas, gas)
var minPrice uint64 = 200
if c.eip7883 {
minPrice = 500
if maxLenOver32 {
gas.Add(gas, gas)
} else {
gas = big.NewInt(16)
}
}
if adjExpLen.Cmp(big1) > 0 {
gas.Mul(gas, adjExpLen)
}
// 2. Different divisor (`GQUADDIVISOR`) (3)
if !c.eip7883 {
gas.Div(gas, big3)
}
if gas.BitLen() > 64 {
return math.MaxUint64
}
return max(minPrice, gas.Uint64())
}
// Pre-Berlin logic.
gas = modexpMultComplexity(gas)
if adjExpLen.Cmp(big1) > 0 {
gas.Mul(gas, adjExpLen)
}
gas.Div(gas, big20)
if gas.BitLen() > 64 {
return math.MaxUint64
}
return gas.Uint64()
}
func (c *bigModExp) Run(input []byte) ([]byte, error) { func (c *bigModExp) Run(input []byte) ([]byte, error) {
var ( var (
@ -543,6 +643,10 @@ func (c *bigModExp) Run(input []byte) ([]byte, error) {
return common.LeftPadBytes(v, int(modLen)), nil return common.LeftPadBytes(v, int(modLen)), nil
} }
func (c *bigModExp) Name() string {
return "MODEXP"
}
// newCurvePoint unmarshals a binary blob into a bn256 elliptic curve point, // newCurvePoint unmarshals a binary blob into a bn256 elliptic curve point,
// returning it, or an error if the point is invalid. // returning it, or an error if the point is invalid.
func newCurvePoint(blob []byte) (*bn256.G1, error) { func newCurvePoint(blob []byte) (*bn256.G1, error) {
@ -592,6 +696,10 @@ func (c *bn256AddIstanbul) Run(input []byte) ([]byte, error) {
return runBn256Add(input) return runBn256Add(input)
} }
func (c *bn256AddIstanbul) Name() string {
return "BN254_ADD"
}
// bn256AddByzantium implements a native elliptic curve point addition // bn256AddByzantium implements a native elliptic curve point addition
// conforming to Byzantium consensus rules. // conforming to Byzantium consensus rules.
type bn256AddByzantium struct{} type bn256AddByzantium struct{}
@ -605,6 +713,10 @@ func (c *bn256AddByzantium) Run(input []byte) ([]byte, error) {
return runBn256Add(input) return runBn256Add(input)
} }
func (c *bn256AddByzantium) Name() string {
return "BN254_ADD"
}
// runBn256ScalarMul implements the Bn256ScalarMul precompile, referenced by // runBn256ScalarMul implements the Bn256ScalarMul precompile, referenced by
// both Byzantium and Istanbul operations. // both Byzantium and Istanbul operations.
func runBn256ScalarMul(input []byte) ([]byte, error) { func runBn256ScalarMul(input []byte) ([]byte, error) {
@ -630,6 +742,10 @@ func (c *bn256ScalarMulIstanbul) Run(input []byte) ([]byte, error) {
return runBn256ScalarMul(input) return runBn256ScalarMul(input)
} }
func (c *bn256ScalarMulIstanbul) Name() string {
return "BN254_MUL"
}
// bn256ScalarMulByzantium implements a native elliptic curve scalar // bn256ScalarMulByzantium implements a native elliptic curve scalar
// multiplication conforming to Byzantium consensus rules. // multiplication conforming to Byzantium consensus rules.
type bn256ScalarMulByzantium struct{} type bn256ScalarMulByzantium struct{}
@ -643,6 +759,10 @@ func (c *bn256ScalarMulByzantium) Run(input []byte) ([]byte, error) {
return runBn256ScalarMul(input) return runBn256ScalarMul(input)
} }
func (c *bn256ScalarMulByzantium) Name() string {
return "BN254_MUL"
}
var ( var (
// true32Byte is returned if the bn256 pairing check succeeds. // true32Byte is returned if the bn256 pairing check succeeds.
true32Byte = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} true32Byte = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
@ -698,6 +818,10 @@ func (c *bn256PairingIstanbul) Run(input []byte) ([]byte, error) {
return runBn256Pairing(input) return runBn256Pairing(input)
} }
func (c *bn256PairingIstanbul) Name() string {
return "BN254_PAIRING"
}
// bn256PairingByzantium implements a pairing pre-compile for the bn256 curve // bn256PairingByzantium implements a pairing pre-compile for the bn256 curve
// conforming to Byzantium consensus rules. // conforming to Byzantium consensus rules.
type bn256PairingByzantium struct{} type bn256PairingByzantium struct{}
@ -711,6 +835,10 @@ func (c *bn256PairingByzantium) Run(input []byte) ([]byte, error) {
return runBn256Pairing(input) return runBn256Pairing(input)
} }
func (c *bn256PairingByzantium) Name() string {
return "BN254_PAIRING"
}
type blake2F struct{} type blake2F struct{}
func (c *blake2F) RequiredGas(input []byte) uint64 { func (c *blake2F) RequiredGas(input []byte) uint64 {
@ -772,6 +900,10 @@ func (c *blake2F) Run(input []byte) ([]byte, error) {
return output, nil return output, nil
} }
func (c *blake2F) Name() string {
return "BLAKE2F"
}
var ( var (
errBLS12381InvalidInputLength = errors.New("invalid input length") errBLS12381InvalidInputLength = errors.New("invalid input length")
errBLS12381InvalidFieldElementTopBytes = errors.New("invalid field element top bytes") errBLS12381InvalidFieldElementTopBytes = errors.New("invalid field element top bytes")
@ -815,6 +947,10 @@ func (c *bls12381G1Add) Run(input []byte) ([]byte, error) {
return encodePointG1(p0), nil return encodePointG1(p0), nil
} }
func (c *bls12381G1Add) Name() string {
return "BLS12_G1ADD"
}
// bls12381G1MultiExp implements EIP-2537 G1MultiExp precompile. // bls12381G1MultiExp implements EIP-2537 G1MultiExp precompile.
type bls12381G1MultiExp struct{} type bls12381G1MultiExp struct{}
@ -875,6 +1011,10 @@ func (c *bls12381G1MultiExp) Run(input []byte) ([]byte, error) {
return encodePointG1(r), nil return encodePointG1(r), nil
} }
func (c *bls12381G1MultiExp) Name() string {
return "BLS12_G1MSM"
}
// bls12381G2Add implements EIP-2537 G2Add precompile. // bls12381G2Add implements EIP-2537 G2Add precompile.
type bls12381G2Add struct{} type bls12381G2Add struct{}
@ -912,6 +1052,10 @@ func (c *bls12381G2Add) Run(input []byte) ([]byte, error) {
return encodePointG2(r), nil return encodePointG2(r), nil
} }
func (c *bls12381G2Add) Name() string {
return "BLS12_G2ADD"
}
// bls12381G2MultiExp implements EIP-2537 G2MultiExp precompile. // bls12381G2MultiExp implements EIP-2537 G2MultiExp precompile.
type bls12381G2MultiExp struct{} type bls12381G2MultiExp struct{}
@ -972,6 +1116,10 @@ func (c *bls12381G2MultiExp) Run(input []byte) ([]byte, error) {
return encodePointG2(r), nil return encodePointG2(r), nil
} }
func (c *bls12381G2MultiExp) Name() string {
return "BLS12_G2MSM"
}
// bls12381Pairing implements EIP-2537 Pairing precompile. // bls12381Pairing implements EIP-2537 Pairing precompile.
type bls12381Pairing struct{} type bls12381Pairing struct{}
@ -1035,6 +1183,10 @@ func (c *bls12381Pairing) Run(input []byte) ([]byte, error) {
return out, nil return out, nil
} }
func (c *bls12381Pairing) Name() string {
return "BLS12_PAIRING_CHECK"
}
func decodePointG1(in []byte) (*bls12381.G1Affine, error) { func decodePointG1(in []byte) (*bls12381.G1Affine, error) {
if len(in) != 128 { if len(in) != 128 {
return nil, errors.New("invalid g1 point length") return nil, errors.New("invalid g1 point length")
@ -1153,6 +1305,10 @@ func (c *bls12381MapG1) Run(input []byte) ([]byte, error) {
return encodePointG1(&r), nil return encodePointG1(&r), nil
} }
func (c *bls12381MapG1) Name() string {
return "BLS12_MAP_FP_TO_G1"
}
// bls12381MapG2 implements EIP-2537 MapG2 precompile. // bls12381MapG2 implements EIP-2537 MapG2 precompile.
type bls12381MapG2 struct{} type bls12381MapG2 struct{}
@ -1186,6 +1342,10 @@ func (c *bls12381MapG2) Run(input []byte) ([]byte, error) {
return encodePointG2(&r), nil return encodePointG2(&r), nil
} }
func (c *bls12381MapG2) Name() string {
return "BLS12_MAP_FP2_TO_G2"
}
// kzgPointEvaluation implements the EIP-4844 point evaluation precompile. // kzgPointEvaluation implements the EIP-4844 point evaluation precompile.
type kzgPointEvaluation struct{} type kzgPointEvaluation struct{}
@ -1242,6 +1402,10 @@ func (b *kzgPointEvaluation) Run(input []byte) ([]byte, error) {
return common.Hex2Bytes(blobPrecompileReturnValue), nil return common.Hex2Bytes(blobPrecompileReturnValue), nil
} }
func (b *kzgPointEvaluation) Name() string {
return "KZG_POINT_EVALUATION"
}
// kZGToVersionedHash implements kzg_to_versioned_hash from EIP-4844 // kZGToVersionedHash implements kzg_to_versioned_hash from EIP-4844
func kZGToVersionedHash(kzg kzg4844.Commitment) common.Hash { func kZGToVersionedHash(kzg kzg4844.Commitment) common.Hash {
h := sha256.Sum256(kzg[:]) h := sha256.Sum256(kzg[:])
@ -1277,3 +1441,7 @@ func (c *p256Verify) Run(input []byte) ([]byte, error) {
} }
return nil, nil return nil, nil
} }
func (c *p256Verify) Name() string {
return "P256VERIFY"
}

View file

@ -601,7 +601,7 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b
} }
} }
evm.StateDB.SetCode(address, ret) evm.StateDB.SetCode(address, ret, tracing.CodeChangeContractCreation)
return ret, nil return ret, nil
} }

View file

@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256" "github.com/holiman/uint256"
@ -87,7 +88,7 @@ func TestEIP2200(t *testing.T) {
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
statedb.CreateAccount(address) statedb.CreateAccount(address)
statedb.SetCode(address, hexutil.MustDecode(tt.input)) statedb.SetCode(address, hexutil.MustDecode(tt.input), tracing.CodeChangeUnspecified)
statedb.SetState(address, common.Hash{}, common.BytesToHash([]byte{tt.original})) statedb.SetState(address, common.Hash{}, common.BytesToHash([]byte{tt.original}))
statedb.Finalise(true) // Push the state into the "original" slot statedb.Finalise(true) // Push the state into the "original" slot
@ -139,7 +140,7 @@ func TestCreateGas(t *testing.T) {
address := common.BytesToAddress([]byte("contract")) address := common.BytesToAddress([]byte("contract"))
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
statedb.CreateAccount(address) statedb.CreateAccount(address)
statedb.SetCode(address, hexutil.MustDecode(tt.code)) statedb.SetCode(address, hexutil.MustDecode(tt.code), tracing.CodeChangeUnspecified)
statedb.Finalise(true) statedb.Finalise(true)
vmctx := BlockContext{ vmctx := BlockContext{
CanTransfer: func(StateDB, common.Address, *uint256.Int) bool { return true }, CanTransfer: func(StateDB, common.Address, *uint256.Int) bool { return true },

View file

@ -43,7 +43,7 @@ type StateDB interface {
GetCode(common.Address) []byte GetCode(common.Address) []byte
// SetCode sets the new code for the address, and returns the previous code, if any. // SetCode sets the new code for the address, and returns the previous code, if any.
SetCode(common.Address, []byte) []byte SetCode(common.Address, []byte, tracing.CodeChangeReason) []byte
GetCodeSize(common.Address) int GetCodeSize(common.Address) int
AddRefund(uint64) AddRefund(uint64)

View file

@ -33,6 +33,7 @@ type Config struct {
ExtraEips []int // Additional EIPS that are to be enabled ExtraEips []int // Additional EIPS that are to be enabled
StatelessSelfValidation bool // Generate execution witnesses and self-check against them (testing purpose) StatelessSelfValidation bool // Generate execution witnesses and self-check against them (testing purpose)
EnableWitnessStats bool // Whether trie access statistics collection is enabled
} }
// ScopeContext contains the things that are per-call, such as stack and memory, // ScopeContext contains the things that are per-call, such as stack and memory,

View file

@ -24,6 +24,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256" "github.com/holiman/uint256"
@ -45,7 +46,7 @@ func TestLoopInterrupt(t *testing.T) {
for i, tt := range loopInterruptTests { for i, tt := range loopInterruptTests {
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
statedb.CreateAccount(address) statedb.CreateAccount(address)
statedb.SetCode(address, common.Hex2Bytes(tt)) statedb.SetCode(address, common.Hex2Bytes(tt), tracing.CodeChangeUnspecified)
statedb.Finalise(true) statedb.Finalise(true)
evm := NewEVM(vmctx, statedb, params.AllEthashProtocolChanges, Config{}) evm := NewEVM(vmctx, statedb, params.AllEthashProtocolChanges, Config{})

View file

@ -53,7 +53,7 @@ func (p *Program) add(op byte) *Program {
return p return p
} }
// pushBig creates a PUSHX instruction and pushes the given val. // doPush creates a PUSHX instruction and pushes the given val.
// - If the val is nil, it pushes zero // - If the val is nil, it pushes zero
// - If the val is bigger than 32 bytes, it panics // - If the val is bigger than 32 bytes, it panics
func (p *Program) doPush(val *uint256.Int) { func (p *Program) doPush(val *uint256.Int) {

View file

@ -22,6 +22,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
@ -139,7 +140,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil) cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil)
cfg.State.CreateAccount(address) cfg.State.CreateAccount(address)
// set the receiver's (the executing contract) code for execution. // set the receiver's (the executing contract) code for execution.
cfg.State.SetCode(address, code) cfg.State.SetCode(address, code, tracing.CodeChangeUnspecified)
// Call the code with the given configuration. // Call the code with the given configuration.
ret, leftOverGas, err := vmenv.Call( ret, leftOverGas, err := vmenv.Call(
cfg.Origin, cfg.Origin,

View file

@ -114,7 +114,7 @@ func TestCall(t *testing.T) {
byte(vm.PUSH1), 32, byte(vm.PUSH1), 32,
byte(vm.PUSH1), 0, byte(vm.PUSH1), 0,
byte(vm.RETURN), byte(vm.RETURN),
}) }, tracing.CodeChangeUnspecified)
ret, _, err := Call(address, nil, &Config{State: state}) ret, _, err := Call(address, nil, &Config{State: state})
if err != nil { if err != nil {
@ -167,7 +167,7 @@ func benchmarkEVM_Create(bench *testing.B, code string) {
) )
statedb.CreateAccount(sender) statedb.CreateAccount(sender)
statedb.SetCode(receiver, common.FromHex(code)) statedb.SetCode(receiver, common.FromHex(code), tracing.CodeChangeUnspecified)
runtimeConfig := Config{ runtimeConfig := Config{
Origin: sender, Origin: sender,
State: statedb, State: statedb,
@ -232,7 +232,7 @@ func BenchmarkEVM_SWAP1(b *testing.B) {
b.Run("10k", func(b *testing.B) { b.Run("10k", func(b *testing.B) {
contractCode := swapContract(10_000) contractCode := swapContract(10_000)
state.SetCode(contractAddr, contractCode) state.SetCode(contractAddr, contractCode, tracing.CodeChangeUnspecified)
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, _, err := Call(contractAddr, []byte{}, &Config{State: state}) _, _, err := Call(contractAddr, []byte{}, &Config{State: state})
@ -263,7 +263,7 @@ func BenchmarkEVM_RETURN(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
contractCode := returnContract(n) contractCode := returnContract(n)
state.SetCode(contractAddr, contractCode) state.SetCode(contractAddr, contractCode, tracing.CodeChangeUnspecified)
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
ret, _, err := Call(contractAddr, []byte{}, &Config{State: state}) ret, _, err := Call(contractAddr, []byte{}, &Config{State: state})
@ -422,12 +422,12 @@ func benchmarkNonModifyingCode(gas uint64, code []byte, name string, tracerCode
byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00,
byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00,
byte(vm.REVERT), byte(vm.REVERT),
}) }, tracing.CodeChangeUnspecified)
} }
//cfg.State.CreateAccount(cfg.Origin) //cfg.State.CreateAccount(cfg.Origin)
// set the receiver's (the executing contract) code for execution. // set the receiver's (the executing contract) code for execution.
cfg.State.SetCode(destination, code) cfg.State.SetCode(destination, code, tracing.CodeChangeUnspecified)
Call(destination, nil, cfg) Call(destination, nil, cfg)
b.Run(name, func(b *testing.B) { b.Run(name, func(b *testing.B) {
@ -772,12 +772,12 @@ func TestRuntimeJSTracer(t *testing.T) {
for i, jsTracer := range jsTracers { for i, jsTracer := range jsTracers {
for j, tc := range tests { for j, tc := range tests {
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
statedb.SetCode(main, tc.code) statedb.SetCode(main, tc.code, tracing.CodeChangeUnspecified)
statedb.SetCode(common.HexToAddress("0xbb"), calleeCode) statedb.SetCode(common.HexToAddress("0xbb"), calleeCode, tracing.CodeChangeUnspecified)
statedb.SetCode(common.HexToAddress("0xcc"), calleeCode) statedb.SetCode(common.HexToAddress("0xcc"), calleeCode, tracing.CodeChangeUnspecified)
statedb.SetCode(common.HexToAddress("0xdd"), calleeCode) statedb.SetCode(common.HexToAddress("0xdd"), calleeCode, tracing.CodeChangeUnspecified)
statedb.SetCode(common.HexToAddress("0xee"), calleeCode) statedb.SetCode(common.HexToAddress("0xee"), calleeCode, tracing.CodeChangeUnspecified)
statedb.SetCode(common.HexToAddress("0xff"), suicideCode) statedb.SetCode(common.HexToAddress("0xff"), suicideCode, tracing.CodeChangeUnspecified)
tracer, err := tracers.DefaultDirectory.New(jsTracer, new(tracers.Context), nil, params.MergedTestChainConfig) tracer, err := tracers.DefaultDirectory.New(jsTracer, new(tracers.Context), nil, params.MergedTestChainConfig)
if err != nil { if err != nil {
@ -862,8 +862,8 @@ func BenchmarkTracerStepVsCallFrame(b *testing.B) {
// delegation designator incurs the correct amount of gas based on the tracer. // delegation designator incurs the correct amount of gas based on the tracer.
func TestDelegatedAccountAccessCost(t *testing.T) { func TestDelegatedAccountAccessCost(t *testing.T) {
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
statedb.SetCode(common.HexToAddress("0xff"), types.AddressToDelegation(common.HexToAddress("0xaa"))) statedb.SetCode(common.HexToAddress("0xff"), types.AddressToDelegation(common.HexToAddress("0xaa")), tracing.CodeChangeUnspecified)
statedb.SetCode(common.HexToAddress("0xaa"), program.New().Return(0, 0).Bytes()) statedb.SetCode(common.HexToAddress("0xaa"), program.New().Return(0, 0).Bytes(), tracing.CodeChangeUnspecified)
for i, tc := range []struct { for i, tc := range []struct {
code []byte code []byte

View file

@ -117,5 +117,215 @@
"Name": "nagydani-5-pow0x10001", "Name": "nagydani-5-pow0x10001",
"Gas": 87381, "Gas": 87381,
"NoBenchmark": false "NoBenchmark": false
},
{
"Input": "000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000c1000000000000000000000000000000000000000000000000000000000000000cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe000007d7d7d83828282348286877d7d827d407d797d7d7d7d7d7d7d7d7d7d7d5b00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000021000000000000000000000000000000000000000000000000000000000000000cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4000007d7d7d83828282348286877d7d82",
"Expected": "36a385a417859b5e178d3ab9",
"Name": "marius-1-even",
"Gas": 2057,
"NoBenchmark": false
},
{
"Input": "000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000d80000000000000000000000000000000000000000000000000000000000000010ffffffffffffffff76ffffffffffffff1cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c76ec7c7c7c7ffffffffffffffc7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7ffffffffffffc7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c76ec7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7ffffffffff3f000000000000000000000000",
"Expected": "c3745de81615f80088ffffffffffffff",
"Name": "guido-1-even",
"Gas": 2298,
"NoBenchmark": false
},
{
"Input": "000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000d80000000000000000000000000000000000000000000000000000000000000010e0060000a921212121212121ff0000212b212121ffff1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f00feffff212121212121ffffffff1fe1e0e0e01e1f1f169f1f1f1f490afcefffffffffffffffff82828282828282828282828282828282828282828200ffff28ff2b212121ffff1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1fffffffffff0afceffffff7ffffffffff7c8282828282a1828282828282828282828282828200ffff28ff2b212121ffff1f1f1f1f1f1fd11f1f1f1f1f1f1f1f1f1f1fffffffffffffffff21212121212121fb2121212121ffff1f1f1f1f1f1f1f1fffaf82828282828200ffff28ff2b21828200",
"Expected": "458ef0af2549d46d24c89079499479e1",
"Name": "guido-2-even",
"Gas": 2300,
"NoBenchmark": false
},
{
"Input": "00000000000000000000000000000000000000000000000000000000000001e7000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000002cb0193585a48e18aad777e9c1b54221a0f58140392e4f091cd5f42b2e8644a9384fbd58ae1edec2477ebf7edbf7c0a3f8bd21d1890ee87646feab3c47be716f842cc3da9b940af312dc54450a960e3fc0b86e56abddd154068e10571a96fff6259431632bc15695c6c8679057e66c2c25c127e97e64ee5de6ea1fc0a4a0e431343fed1daafa072c238a45841da86a9806680bc9f298411173210790359209cd454b5af7b4d5688b4403924e5f863d97e2c5349e1a04b54fcf385b1e9d7714bab8fbf5835f6ff9ed575e77dff7af5cbb641db5d537933bae1fa6555d6c12d6fb31ca27b57771f4aebfbe0bf95e8990c0108ffe7cbdaf370be52cf3ade594543af75ad9329d2d11a402270b5b9a6bf4b83307506e118fca4862749d04e916fc7a039f0d13f2a02e0eedb800199ec95df15b4ccd8669b52586879624d51219e72102fad810b5909b1e372ddf33888fb9beb09b416e4164966edbabd89e4a286be36277fc576ed519a15643dac602e92b63d0b9121f0491da5b16ef793a967f096d80b6c81ecaaffad7e3f06a4a5ac2796f1ed9f68e6a0fd5cf191f0c5c2eec338952ff8d31abc68bf760febeb57e088995ba1d7726a2fdd6d8ca28a181378b8b4ab699bfd4b696739bbf17a9eb2df6251143046137fdbbfacac312ebf67a67da9741b596000000000000419a2917c61722b0713d3b00a2f0e1dd5aebbbe09615de424700eea3c3020fe6e9ea5de9fa1ace781df28b21f746d2ab61d0da496e08473c90ff7dfe25b43bcde76f4bafb82e0975bea75f5a0591dba80ba2fff80a07d8853bea5be13ab326ba70c57b153acc646151948d1cf061ca31b02d4719fac710e7c723ca44f5b1737824b7ccc74ba5bff980aabdbf267621cafc3d6dcc29d0ca9c16839a92ed34de136da7900aa3ee43d21aa57498981124357cf0ca9b86f9a8d3f9c604ca00c726e48f7a9945021ea6dfff92d6b2d6514693169ca133e993541bfa4c4c191de806aa80c48109bcfc9901eccfdeb2395ab75fe63c67de900829d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"Expected": "1194e971c875bb45f030316793bc6916343335b18670dfad7a4646fba99749b30283b78b818836de7400ff1a68ddad1a2dd850ec0f227441e2d4d13f5ee4b5d7a856db0a9ad1f86987e1f117d70f9e6a1a2b8d083fa82653aa16f1773b6deb2ed8a1f9e7f3a5db121c4a0c91cb954e2ec53e63422efe86c7984d79cd0e7b5e3eb8ca4980551d63f302c7d72500a84baf12c82fc7bd9b5c2ab8b9c33baf1df28b2031c58a8b2928a42c9f456e98874e22fe13cf17aa5915b11bb108b6ae40842d434604ccddcb4f64324c67b2dde32e6cd759d964f17d9cdf0046cd0ed3588e1fc4b88f67a5d4f3a870aad1cba89ead265d6ad327c8ea7ff54fe4b5e7dbe87c5c59c468543eab3675751111bfa1d6c51daf789d41dc21fd8ba9e05490f881a973a3c1567ff3129a49aa6658cf06f0a79530a7256ce5a07c2a77b4306383d538866bba376d90621c4f82d1f5f32304ee2b7170805d42418fc6967642e5648d8c64fe9c0fdff2d7c114a47add7767c8fccb8808de8c3c6e1a8880c05e16fafc1513fded8eba222dcaaa809bdb36999cc27ab8d0055009e9690e8a35b859df865dc510d25c7812d8eebbb35607ad595573f0fabd1b57970a2bc113ac6f0ca01e985032b9c2c139316ac099ed1632d2bc0fcc341343d303db2a9c3cf2ad572c6c43084b08d458bf822e92da16079f39cdb0dd10ef47f87ecae404117fc72660372cee9ea42266e7f8d973e7f6b09930ca5f96e04976bf23b9d356bbd2429597b04b7663e0e1a1228f4dfda3b854233e4888dc60c6886c1e0e8aec1705f681027b1e0b3017337557f107ef5cd272df5fd31dfec2bdffe163a8369895ffe124c0aa0ee00ca0fe1db4d5cf37b4af0e49bd73a89d88ced3d88f8e6f00d8e61ad09946d0e72cd3e25bc688a021a83758b5023daae7c269a6cbbd447aba5da7629b75801e1654ce85b8e21ccb9865654f8662e538625d75fea31000000000000000000000000000000000000000000000",
"Name": "guido-3-even",
"Gas": 5400,
"NoBenchmark": false
},
{
"Input": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000181000000000000000000000000000000000000000000000000000000000000000801ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2cffffffffffffffffffffffffffffffffffffffffffffffffffffffff3b10000000006c01ffffffffffffffffffffffffffffffffffffffffffffffdffffb97ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3bffffffffffffffffffffffffffffffffffffffffffffffffffffffffebafd93b37ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc5bb6affffffff3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2a",
"Expected": "0000000000000001",
"Name": "guido-4-even",
"Gas": 1026,
"NoBenchmark": false
},
{
"Input": "00000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c0e8e77626586f73b955364c7b4bbf0bb7f7685ebd40e852b164633a4acbd3244c51f81bcdfc324a0dff2b5bec9d92e21cbebc4d5e29d3a3d30de3e03fbeab8d7f2ee5f854d076701c8753d72779187e404f9b2fb705c495137d78551250314a463ef5a213fe22de1cea28d60f518364ff95fe0b73660793e3efcfbe31bda68aeccc21cc9477a6aea5df8cae73422b700c47e54d892691e099167e77befc94780a920ae4155769cd69c30626f054134b5f003772473f57f84837402df6d166e66303f01681d2220bfea4bb888a5543db8c0916274ddb1ea93b144c042c01d8164c954b27ac388a17c8e9a7ba12a968f288f3308d6fe7bcdf28e685e9a2e00d8be1af19726b7662016d6404f9336493ad633777feb88c9d02a1d2428e566ac38f42c0bb66d2962ec349088ce0d03b35bf27f6114414ef558c87ad8e543754a352f7dffcaca429690688595ab1d1b349d9295b480a82f43ac5c9112fe40720545cc78501cd8b42f3605212fe06a835c9cbc0328e07e94aedb2ac11f6d6649e7fcd8c43",
"Expected": "120cf297dbe810911c7d060e109e03699ccefa00a257d296c5a14b4180776c5f7c0d7f1cd789c694807689729af267b53f00373f395dee264a3daba11fcac1fa8875aee0950acd8fa656f1fc58077a7549d794dd160506ecea1acc9c0cda13795749c94f9973b683ce2162866e8d6b5b1165a4c7fa4234964d394d6ec4e0113698b89d173e24a962dd7a41a1819b0fef188ef64e7ee264595dce0d76fbc3ba42d5de833b143c8744366effede8bc8197e8f747ff8cdbc0bf1a93560bec960ca9",
"Name": "marcin-1-base-heavy",
"Gas": 200,
"NoBenchmark": false
},
{
"Input": "0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000080001020304050607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0001020304050607",
"Expected": "0000000000000000",
"Name": "marcin-1-exp-heavy",
"Gas": 215,
"NoBenchmark": false
},
{
"Input": "000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000028e8e77626586f73b955364c7b4bbf0bb7f7685ebd40e852b164633a4acbd3244c000102030405060701fffffff01681d2220bfea4bb888a5543db8c0916274ddb1ea93b144c042c01d8164c950001020304050607",
"Expected": "1abce71dc2205cce4eb6934397a88136f94641342e283cbcd30e929e85605c6718ed67f475192ffd",
"Name": "marcin-1-balanced",
"Gas": 200,
"NoBenchmark": false
},
{
"Input": "000000000000000000000000000000000000000000000000000000000000019800000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000198e8e77626586f73b955364c7b4bbf0bb7f7685ebd40e852b164633a4acbd3244c51f81bcdfc324a0dff2b5bec9d92e21cbebc4d5e29d3a3d30de3e03fbeab8d7f2ee5f854d076701c8753d72779187e404f9b2fb705c495137d78551250314a463ef5a213fe22de1cea28d60f518364ff95fe0b73660793e3efcfbe31bda68aeccc21cc9477a6aea5df8cae73422b700c47e54d892691e099167e77befc94780a920ae4155769cd69c30626f054134b5f003772473f57f84837402df6d166e663c29021b0e084f7dc16f6ec88cc597f1aea9f8e0b9501e0f7a546805d2a20eeda0bf080aeb3ed7ea6f9174d804bd242f0b31ff1ea24800344abb580cd87f61ca75a013a87733553966400242399dee3760877fead2cd87287747155e47a854acb50fe07922f57ae3b4553201bfd7c11aca85e1541f91db8e62dca9c418dc5feae086c9487350539c884510044efce5e3f2aaffca4215c12b9044506375097fecd9b22e2ef46f01f1af8aff742aebf96bdcaf55a341600971dc62555376b9e98a8000102030405060708090a0b0c0d0e0f101112131415161703f01681d2220bfea4bb888a5543db8c0916274ddb1ea93b144c042c01d8164c954b27ac388a17c8e9a7ba12a968f288f3308d6fe7bcdf28e685e9a2e00d8be1af19726b7662016d6404f9336493ad633777feb88c9d02a1d2428e566ac38f42c0bb66d2962ec349088ce0d03b35bf27f6114414ef558c87ad8e543754a352f7dffcaca429690688595ab1d1b349d9295b480a82f43ac5c9112fe40720545cc78501cd8b42f3605212fe06a835c9cbc0328e07e94aedb2ac11f6d6649e7fcd8c43ddce2bd0cdc6c22c4dcd345735040d5bfe3f09b7c61362089f728e2222db96cab2f2c2ccf43574f9e119f4860fd0f1b6036a43ad9db8a428ea09a4ee385112f3fe9c6656ea2cec604cbb5a9227526653bfa7035e4ae80010b1ba16a76608d5dde0a62bc019e9047b5ec05b1005fd017366130a4ba555e7be654561ee3f539c93cb2c9988fca71bf0ad9c4a426b924641a28e1e4adb93609bfa5b2bc81714cbba1110208b86d7b87be28bdf63a62e33ae81dbcc43de9192bd192c40e85faab539000102030405060708090a0b0c0d0e0f1011121314151617",
"Expected": "817d05c4d540ace3f250fd082e2deb8c2097410fcfe4ce40862cfa015b7f62a7bb72ec1af2915cad66294447b45d177fe759eb80370b0dbd3c0e1c448d54db81eadc11e40c19e394d066a5c019c443798a98d4afd116fc220593d42fbf191b6af0ae75410badb641187ba24a0b968f742a75e2822853f137151d9ea972fd1f36b7c7cc4e71355ecf50648aec094b864cfae9316c0a7be3ddb8ab2d0050b2a029ee956c2366d49430c8f889f29ab514aea8e5b8dec40ba1b49432e30aee32ec45e96dd548205a79d8f8f918eed46acb2115c59086b1011b1d2b093cea723535c3d95efc8e51a7da43b80586d69eb7f213dfb06f7a8e789a9472392bd224411b50f8ca6f2862bcd63431912d1ff99c8d76408245da9e4bea649f0eb930b32922f2d0a345a206160be44d418f1a6c74bd49c4618392ef9350b264a461dfc684c7343211e27675b027054f1cb3d4a5b1a066d3a3ea2eed9caf13251d8be936818f15274e8e3d7539b7c5f216cef327a270fd2a886fbf679c163fb5806249f2c74da5ee0e3ffe9ad1fde2634b29b35da6da6d184ab6ae70199229",
"Name": "marcin-2-base-heavy",
"Gas": 867,
"NoBenchmark": false
},
{
"Input": "000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000010000102030405060708090a0b0c0d0e0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000102030405060708090a0b0c0d0e0f",
"Expected": "00000000000000000000000000000000",
"Name": "marcin-2-exp-heavy",
"Gas": 852,
"NoBenchmark": false
},
{
"Input": "000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000038e8e77626586f73b955364c7b4bbf0bb7f7685ebd40e852b164633a4acbd3244c000102030405060708090a0b0c0d0e0f10111213141516172bfffffffffffffff01681d2220bfea4bb888a5543db8c0916274ddb1ea93b144c042c01d8164c95000102030405060708090a0b0c0d0e0f1011121314151617",
"Expected": "86bef3367fc7117c8a6b825cadebe80f3e94c321dda73e9e240b98188a1d5c071c60a195097c8d1fb85ce03a2e1b6964846edee5aa2c3f46",
"Name": "marcin-2-balanced",
"Gas": 996,
"NoBenchmark": false
},
{
"Input": "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020e8e77626586f73b955364c7b4bbf0bb7f7685ebd40e852b164633a4acbd3244cfffffffffffffffffffffffffffffffff01681d2220bfea4bb888a5543db8c0916274ddb1ea93b144c042c01d8164c95",
"Expected": "4004762c491606a5132134da6086284f74cc8e14b08f18b90fc09f31bca3d78f",
"Name": "marcin-3-base-heavy",
"Gas": 677,
"NoBenchmark": false
},
{
"Input": "000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000018000102030405060708090a0b0c0d0e0f1011121314151617ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000102030405060708090a0b0c0d0e0f1011121314151617",
"Expected": "000000000000000000000000000000000000000000000000",
"Name": "marcin-3-exp-heavy",
"Gas": 765,
"NoBenchmark": false
},
{
"Input": "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020e8e77626586f73b955364c7b4bbf0bb7f7685ebd40e852b164633a4acbd3244cfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01681d2220bfea4bb888a5543db8c0916274ddb1ea93b144c042c01d8164c95",
"Expected": "2d3feee20d394af68dd6744b86a8aca6a4a0b7f01bbcd3c3eec768245ca6acee",
"Name": "marcin-3-balanced",
"Gas": 1360,
"NoBenchmark": false
},
{
"Input": "00000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000000800ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00",
"Expected": "00ffffffffffffff",
"Name": "mod-8-exp-648",
"Gas": 215,
"NoBenchmark": false
},
{
"Input": "00000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000000800ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00",
"Expected": "00ffffffffffffff",
"Name": "mod-8-exp-896",
"Gas": 298,
"NoBenchmark": false
},
{
"Input": "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000002000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00",
"Expected": "00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"Name": "mod-32-exp-32",
"Gas": 200,
"NoBenchmark": false
},
{
"Input": "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000002000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00",
"Expected": "00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"Name": "mod-32-exp-36",
"Gas": 200,
"NoBenchmark": false
},
{
"Input": "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000002000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00",
"Expected": "00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"Name": "mod-32-exp-40",
"Gas": 208,
"NoBenchmark": false
},
{
"Input": "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000002000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00",
"Expected": "00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"Name": "mod-32-exp-64",
"Gas": 336,
"NoBenchmark": false
},
{
"Input": "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000002000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00",
"Expected": "00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"Name": "mod-32-exp-65",
"Gas": 341,
"NoBenchmark": false
},
{
"Input": "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00",
"Expected": "00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"Name": "mod-32-exp-128",
"Gas": 677,
"NoBenchmark": false
},
{
"Input": "00000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000010000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00",
"Expected": "02fd01000000000000000000000000000000fefffffffffffd03ff0000000000000000000000000000000000000000000000fefffffffffffd03ff0000000000000000000000000000000000000000000000fefffffffffffd03ff0000000000000000000000000000000000000000000000fefffffffffffd03ff0000000000000000000000000000000000000000000000fefffffffffffd03ff0000000000000000000000000000000000000000000000fefffffffffffd03ff0000000000000000000000000000000000000000000000fefffffffffffd03ff0000000000000000000000000000000000000000000000fefffffffffffd03feffffffffff",
"Name": "mod-256-exp-2",
"Gas": 341,
"NoBenchmark": false
},
{
"Input": "00000000000000000000000000000000000000000000000000000000000001080000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000010800ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffff00",
"Expected": "0100fefffffffffffff710f80000000006f108000000000000feffffffffffff0100fefffffffffffef90ff80000000105f008ffffffffff02fdffffffffffff0100feffffffffff00f80ff80000010101f407fffffffd06fc0000000000fd020000feffffffff00fff80ff8000101fa08f207fffffb0afa0000000001fb03000000feffffff00fffff80ff80102f80405f207fff90ef80000000002f90400000000feffff00fffffff80ff903f6050005f207f712f60000000003f7050000000000feff00fffffffff810fcf406000005f1fd16f40000000004f506000000000000fe00fffffffffff915ea0700000005e522f20000000005f306ffffffffffffffffffffffffff",
"Name": "mod-264-exp-2",
"Gas": 363,
"NoBenchmark": false
},
{
"Input": "00000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000040000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00",
"Expected": "00fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02feffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"Name": "mod-1024-exp-2",
"Gas": 5461,
"NoBenchmark": false
},
{
"Input": "000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000008ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"Expected": "2a02d5f86c2375ff",
"Name": "pawel-1-exp-heavy",
"Gas": 298,
"NoBenchmark": false
},
{
"Input": "000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000010ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"Expected": "823ef7dc60d6d9616756c48f69b7c4ff",
"Name": "pawel-2-exp-heavy",
"Gas": 425,
"NoBenchmark": false
},
{
"Input": "000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000150000000000000000000000000000000000000000000000000000000000000018ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"Expected": "c817dd5aa60a41948eed409706c2aa97be3000d4da0261ff",
"Name": "pawel-3-exp-heavy",
"Gas": 501,
"NoBenchmark": false
},
{
"Input": "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000020ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"Expected": "2defaca0137d6edacbbd5d36d6ed70cbf8a998ffb19fc270d45a18d37e0f35ff",
"Name": "pawel-4-exp-heavy",
"Gas": 506,
"NoBenchmark": false
},
{
"Input": "000000000000000000000000000000000000000000000000000000000000001700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000017bffffffffffffffffffffffffffffffffffffffffffffbffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffe",
"Expected": "200f14de1d474710c1c979920452e0ffc2ac6f618afba5",
"Name": "mod_vul_pawel_3_exp_8",
"Gas": 200,
"NoBenchmark": false
} }
] ]

View file

@ -103,5 +103,215 @@
"Name": "nagydani-5-pow0x10001", "Name": "nagydani-5-pow0x10001",
"Gas": 524288, "Gas": 524288,
"NoBenchmark": false "NoBenchmark": false
},
{
"Input": "000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000c1000000000000000000000000000000000000000000000000000000000000000cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe000007d7d7d83828282348286877d7d827d407d797d7d7d7d7d7d7d7d7d7d7d5b00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000021000000000000000000000000000000000000000000000000000000000000000cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4000007d7d7d83828282348286877d7d82",
"Expected": "36a385a417859b5e178d3ab9",
"Name": "marius-1-even",
"Gas": 45296,
"NoBenchmark": false
},
{
"Input": "000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000d80000000000000000000000000000000000000000000000000000000000000010ffffffffffffffff76ffffffffffffff1cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c76ec7c7c7c7ffffffffffffffc7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7ffffffffffffc7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c76ec7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7c7ffffffffff3f000000000000000000000000",
"Expected": "c3745de81615f80088ffffffffffffff",
"Name": "guido-1-even",
"Gas": 51136,
"NoBenchmark": false
},
{
"Input": "000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000d80000000000000000000000000000000000000000000000000000000000000010e0060000a921212121212121ff0000212b212121ffff1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f00feffff212121212121ffffffff1fe1e0e0e01e1f1f169f1f1f1f490afcefffffffffffffffff82828282828282828282828282828282828282828200ffff28ff2b212121ffff1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1fffffffffff0afceffffff7ffffffffff7c8282828282a1828282828282828282828282828200ffff28ff2b212121ffff1f1f1f1f1f1fd11f1f1f1f1f1f1f1f1f1f1fffffffffffffffff21212121212121fb2121212121ffff1f1f1f1f1f1f1f1fffaf82828282828200ffff28ff2b21828200",
"Expected": "458ef0af2549d46d24c89079499479e1",
"Name": "guido-2-even",
"Gas": 51152,
"NoBenchmark": false
},
{
"Input": "00000000000000000000000000000000000000000000000000000000000001e7000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000002cb0193585a48e18aad777e9c1b54221a0f58140392e4f091cd5f42b2e8644a9384fbd58ae1edec2477ebf7edbf7c0a3f8bd21d1890ee87646feab3c47be716f842cc3da9b940af312dc54450a960e3fc0b86e56abddd154068e10571a96fff6259431632bc15695c6c8679057e66c2c25c127e97e64ee5de6ea1fc0a4a0e431343fed1daafa072c238a45841da86a9806680bc9f298411173210790359209cd454b5af7b4d5688b4403924e5f863d97e2c5349e1a04b54fcf385b1e9d7714bab8fbf5835f6ff9ed575e77dff7af5cbb641db5d537933bae1fa6555d6c12d6fb31ca27b57771f4aebfbe0bf95e8990c0108ffe7cbdaf370be52cf3ade594543af75ad9329d2d11a402270b5b9a6bf4b83307506e118fca4862749d04e916fc7a039f0d13f2a02e0eedb800199ec95df15b4ccd8669b52586879624d51219e72102fad810b5909b1e372ddf33888fb9beb09b416e4164966edbabd89e4a286be36277fc576ed519a15643dac602e92b63d0b9121f0491da5b16ef793a967f096d80b6c81ecaaffad7e3f06a4a5ac2796f1ed9f68e6a0fd5cf191f0c5c2eec338952ff8d31abc68bf760febeb57e088995ba1d7726a2fdd6d8ca28a181378b8b4ab699bfd4b696739bbf17a9eb2df6251143046137fdbbfacac312ebf67a67da9741b596000000000000419a2917c61722b0713d3b00a2f0e1dd5aebbbe09615de424700eea3c3020fe6e9ea5de9fa1ace781df28b21f746d2ab61d0da496e08473c90ff7dfe25b43bcde76f4bafb82e0975bea75f5a0591dba80ba2fff80a07d8853bea5be13ab326ba70c57b153acc646151948d1cf061ca31b02d4719fac710e7c723ca44f5b1737824b7ccc74ba5bff980aabdbf267621cafc3d6dcc29d0ca9c16839a92ed34de136da7900aa3ee43d21aa57498981124357cf0ca9b86f9a8d3f9c604ca00c726e48f7a9945021ea6dfff92d6b2d6514693169ca133e993541bfa4c4c191de806aa80c48109bcfc9901eccfdeb2395ab75fe63c67de900829d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"Expected": "1194e971c875bb45f030316793bc6916343335b18670dfad7a4646fba99749b30283b78b818836de7400ff1a68ddad1a2dd850ec0f227441e2d4d13f5ee4b5d7a856db0a9ad1f86987e1f117d70f9e6a1a2b8d083fa82653aa16f1773b6deb2ed8a1f9e7f3a5db121c4a0c91cb954e2ec53e63422efe86c7984d79cd0e7b5e3eb8ca4980551d63f302c7d72500a84baf12c82fc7bd9b5c2ab8b9c33baf1df28b2031c58a8b2928a42c9f456e98874e22fe13cf17aa5915b11bb108b6ae40842d434604ccddcb4f64324c67b2dde32e6cd759d964f17d9cdf0046cd0ed3588e1fc4b88f67a5d4f3a870aad1cba89ead265d6ad327c8ea7ff54fe4b5e7dbe87c5c59c468543eab3675751111bfa1d6c51daf789d41dc21fd8ba9e05490f881a973a3c1567ff3129a49aa6658cf06f0a79530a7256ce5a07c2a77b4306383d538866bba376d90621c4f82d1f5f32304ee2b7170805d42418fc6967642e5648d8c64fe9c0fdff2d7c114a47add7767c8fccb8808de8c3c6e1a8880c05e16fafc1513fded8eba222dcaaa809bdb36999cc27ab8d0055009e9690e8a35b859df865dc510d25c7812d8eebbb35607ad595573f0fabd1b57970a2bc113ac6f0ca01e985032b9c2c139316ac099ed1632d2bc0fcc341343d303db2a9c3cf2ad572c6c43084b08d458bf822e92da16079f39cdb0dd10ef47f87ecae404117fc72660372cee9ea42266e7f8d973e7f6b09930ca5f96e04976bf23b9d356bbd2429597b04b7663e0e1a1228f4dfda3b854233e4888dc60c6886c1e0e8aec1705f681027b1e0b3017337557f107ef5cd272df5fd31dfec2bdffe163a8369895ffe124c0aa0ee00ca0fe1db4d5cf37b4af0e49bd73a89d88ced3d88f8e6f00d8e61ad09946d0e72cd3e25bc688a021a83758b5023daae7c269a6cbbd447aba5da7629b75801e1654ce85b8e21ccb9865654f8662e538625d75fea31000000000000000000000000000000000000000000000",
"Name": "guido-3-even",
"Gas": 32400,
"NoBenchmark": false
},
{
"Input": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000181000000000000000000000000000000000000000000000000000000000000000801ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2cffffffffffffffffffffffffffffffffffffffffffffffffffffffff3b10000000006c01ffffffffffffffffffffffffffffffffffffffffffffffdffffb97ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3bffffffffffffffffffffffffffffffffffffffffffffffffffffffffebafd93b37ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc5bb6affffffff3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2a",
"Expected": "0000000000000001",
"Name": "guido-4-even",
"Gas": 94448,
"NoBenchmark": false
},
{
"Input": "00000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c0e8e77626586f73b955364c7b4bbf0bb7f7685ebd40e852b164633a4acbd3244c51f81bcdfc324a0dff2b5bec9d92e21cbebc4d5e29d3a3d30de3e03fbeab8d7f2ee5f854d076701c8753d72779187e404f9b2fb705c495137d78551250314a463ef5a213fe22de1cea28d60f518364ff95fe0b73660793e3efcfbe31bda68aeccc21cc9477a6aea5df8cae73422b700c47e54d892691e099167e77befc94780a920ae4155769cd69c30626f054134b5f003772473f57f84837402df6d166e66303f01681d2220bfea4bb888a5543db8c0916274ddb1ea93b144c042c01d8164c954b27ac388a17c8e9a7ba12a968f288f3308d6fe7bcdf28e685e9a2e00d8be1af19726b7662016d6404f9336493ad633777feb88c9d02a1d2428e566ac38f42c0bb66d2962ec349088ce0d03b35bf27f6114414ef558c87ad8e543754a352f7dffcaca429690688595ab1d1b349d9295b480a82f43ac5c9112fe40720545cc78501cd8b42f3605212fe06a835c9cbc0328e07e94aedb2ac11f6d6649e7fcd8c43",
"Expected": "120cf297dbe810911c7d060e109e03699ccefa00a257d296c5a14b4180776c5f7c0d7f1cd789c694807689729af267b53f00373f395dee264a3daba11fcac1fa8875aee0950acd8fa656f1fc58077a7549d794dd160506ecea1acc9c0cda13795749c94f9973b683ce2162866e8d6b5b1165a4c7fa4234964d394d6ec4e0113698b89d173e24a962dd7a41a1819b0fef188ef64e7ee264595dce0d76fbc3ba42d5de833b143c8744366effede8bc8197e8f747ff8cdbc0bf1a93560bec960ca9",
"Name": "marcin-1-base-heavy",
"Gas": 1152,
"NoBenchmark": false
},
{
"Input": "0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000080001020304050607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0001020304050607",
"Expected": "0000000000000000",
"Name": "marcin-1-exp-heavy",
"Gas": 16624,
"NoBenchmark": false
},
{
"Input": "000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000028e8e77626586f73b955364c7b4bbf0bb7f7685ebd40e852b164633a4acbd3244c000102030405060701fffffff01681d2220bfea4bb888a5543db8c0916274ddb1ea93b144c042c01d8164c950001020304050607",
"Expected": "1abce71dc2205cce4eb6934397a88136f94641342e283cbcd30e929e85605c6718ed67f475192ffd",
"Name": "marcin-1-balanced",
"Gas": 1200,
"NoBenchmark": false
},
{
"Input": "000000000000000000000000000000000000000000000000000000000000019800000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000198e8e77626586f73b955364c7b4bbf0bb7f7685ebd40e852b164633a4acbd3244c51f81bcdfc324a0dff2b5bec9d92e21cbebc4d5e29d3a3d30de3e03fbeab8d7f2ee5f854d076701c8753d72779187e404f9b2fb705c495137d78551250314a463ef5a213fe22de1cea28d60f518364ff95fe0b73660793e3efcfbe31bda68aeccc21cc9477a6aea5df8cae73422b700c47e54d892691e099167e77befc94780a920ae4155769cd69c30626f054134b5f003772473f57f84837402df6d166e663c29021b0e084f7dc16f6ec88cc597f1aea9f8e0b9501e0f7a546805d2a20eeda0bf080aeb3ed7ea6f9174d804bd242f0b31ff1ea24800344abb580cd87f61ca75a013a87733553966400242399dee3760877fead2cd87287747155e47a854acb50fe07922f57ae3b4553201bfd7c11aca85e1541f91db8e62dca9c418dc5feae086c9487350539c884510044efce5e3f2aaffca4215c12b9044506375097fecd9b22e2ef46f01f1af8aff742aebf96bdcaf55a341600971dc62555376b9e98a8000102030405060708090a0b0c0d0e0f101112131415161703f01681d2220bfea4bb888a5543db8c0916274ddb1ea93b144c042c01d8164c954b27ac388a17c8e9a7ba12a968f288f3308d6fe7bcdf28e685e9a2e00d8be1af19726b7662016d6404f9336493ad633777feb88c9d02a1d2428e566ac38f42c0bb66d2962ec349088ce0d03b35bf27f6114414ef558c87ad8e543754a352f7dffcaca429690688595ab1d1b349d9295b480a82f43ac5c9112fe40720545cc78501cd8b42f3605212fe06a835c9cbc0328e07e94aedb2ac11f6d6649e7fcd8c43ddce2bd0cdc6c22c4dcd345735040d5bfe3f09b7c61362089f728e2222db96cab2f2c2ccf43574f9e119f4860fd0f1b6036a43ad9db8a428ea09a4ee385112f3fe9c6656ea2cec604cbb5a9227526653bfa7035e4ae80010b1ba16a76608d5dde0a62bc019e9047b5ec05b1005fd017366130a4ba555e7be654561ee3f539c93cb2c9988fca71bf0ad9c4a426b924641a28e1e4adb93609bfa5b2bc81714cbba1110208b86d7b87be28bdf63a62e33ae81dbcc43de9192bd192c40e85faab539000102030405060708090a0b0c0d0e0f1011121314151617",
"Expected": "817d05c4d540ace3f250fd082e2deb8c2097410fcfe4ce40862cfa015b7f62a7bb72ec1af2915cad66294447b45d177fe759eb80370b0dbd3c0e1c448d54db81eadc11e40c19e394d066a5c019c443798a98d4afd116fc220593d42fbf191b6af0ae75410badb641187ba24a0b968f742a75e2822853f137151d9ea972fd1f36b7c7cc4e71355ecf50648aec094b864cfae9316c0a7be3ddb8ab2d0050b2a029ee956c2366d49430c8f889f29ab514aea8e5b8dec40ba1b49432e30aee32ec45e96dd548205a79d8f8f918eed46acb2115c59086b1011b1d2b093cea723535c3d95efc8e51a7da43b80586d69eb7f213dfb06f7a8e789a9472392bd224411b50f8ca6f2862bcd63431912d1ff99c8d76408245da9e4bea649f0eb930b32922f2d0a345a206160be44d418f1a6c74bd49c4618392ef9350b264a461dfc684c7343211e27675b027054f1cb3d4a5b1a066d3a3ea2eed9caf13251d8be936818f15274e8e3d7539b7c5f216cef327a270fd2a886fbf679c163fb5806249f2c74da5ee0e3ffe9ad1fde2634b29b35da6da6d184ab6ae70199229",
"Name": "marcin-2-base-heavy",
"Gas": 5202,
"NoBenchmark": false
},
{
"Input": "000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000010000102030405060708090a0b0c0d0e0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000102030405060708090a0b0c0d0e0f",
"Expected": "00000000000000000000000000000000",
"Name": "marcin-2-exp-heavy",
"Gas": 16368,
"NoBenchmark": false
},
{
"Input": "000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000038e8e77626586f73b955364c7b4bbf0bb7f7685ebd40e852b164633a4acbd3244c000102030405060708090a0b0c0d0e0f10111213141516172bfffffffffffffff01681d2220bfea4bb888a5543db8c0916274ddb1ea93b144c042c01d8164c95000102030405060708090a0b0c0d0e0f1011121314151617",
"Expected": "86bef3367fc7117c8a6b825cadebe80f3e94c321dda73e9e240b98188a1d5c071c60a195097c8d1fb85ce03a2e1b6964846edee5aa2c3f46",
"Name": "marcin-2-balanced",
"Gas": 5978,
"NoBenchmark": false
},
{
"Input": "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020e8e77626586f73b955364c7b4bbf0bb7f7685ebd40e852b164633a4acbd3244cfffffffffffffffffffffffffffffffff01681d2220bfea4bb888a5543db8c0916274ddb1ea93b144c042c01d8164c95",
"Expected": "4004762c491606a5132134da6086284f74cc8e14b08f18b90fc09f31bca3d78f",
"Name": "marcin-3-base-heavy",
"Gas": 2032,
"NoBenchmark": false
},
{
"Input": "000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000018000102030405060708090a0b0c0d0e0f1011121314151617ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000102030405060708090a0b0c0d0e0f1011121314151617",
"Expected": "000000000000000000000000000000000000000000000000",
"Name": "marcin-3-exp-heavy",
"Gas": 4080,
"NoBenchmark": false
},
{
"Input": "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020e8e77626586f73b955364c7b4bbf0bb7f7685ebd40e852b164633a4acbd3244cfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01681d2220bfea4bb888a5543db8c0916274ddb1ea93b144c042c01d8164c95",
"Expected": "2d3feee20d394af68dd6744b86a8aca6a4a0b7f01bbcd3c3eec768245ca6acee",
"Name": "marcin-3-balanced",
"Gas": 4080,
"NoBenchmark": false
},
{
"Input": "00000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000000800ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00",
"Expected": "00ffffffffffffff",
"Name": "mod-8-exp-648",
"Gas": 16624,
"NoBenchmark": false
},
{
"Input": "00000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000000800ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00",
"Expected": "00ffffffffffffff",
"Name": "mod-8-exp-896",
"Gas": 24560,
"NoBenchmark": false
},
{
"Input": "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000002000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00",
"Expected": "00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"Name": "mod-32-exp-32",
"Gas": 500,
"NoBenchmark": false
},
{
"Input": "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000002000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00",
"Expected": "00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"Name": "mod-32-exp-36",
"Gas": 560,
"NoBenchmark": false
},
{
"Input": "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000002000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00",
"Expected": "00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"Name": "mod-32-exp-40",
"Gas": 624,
"NoBenchmark": false
},
{
"Input": "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000002000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00",
"Expected": "00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"Name": "mod-32-exp-64",
"Gas": 1008,
"NoBenchmark": false
},
{
"Input": "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000002000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00",
"Expected": "00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"Name": "mod-32-exp-65",
"Gas": 1024,
"NoBenchmark": false
},
{
"Input": "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00",
"Expected": "00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"Name": "mod-32-exp-128",
"Gas": 2032,
"NoBenchmark": false
},
{
"Input": "00000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000010000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00",
"Expected": "02fd01000000000000000000000000000000fefffffffffffd03ff0000000000000000000000000000000000000000000000fefffffffffffd03ff0000000000000000000000000000000000000000000000fefffffffffffd03ff0000000000000000000000000000000000000000000000fefffffffffffd03ff0000000000000000000000000000000000000000000000fefffffffffffd03ff0000000000000000000000000000000000000000000000fefffffffffffd03ff0000000000000000000000000000000000000000000000fefffffffffffd03ff0000000000000000000000000000000000000000000000fefffffffffffd03feffffffffff",
"Name": "mod-256-exp-2",
"Gas": 2048,
"NoBenchmark": false
},
{
"Input": "00000000000000000000000000000000000000000000000000000000000001080000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000010800ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffff00",
"Expected": "0100fefffffffffffff710f80000000006f108000000000000feffffffffffff0100fefffffffffffef90ff80000000105f008ffffffffff02fdffffffffffff0100feffffffffff00f80ff80000010101f407fffffffd06fc0000000000fd020000feffffffff00fff80ff8000101fa08f207fffffb0afa0000000001fb03000000feffffff00fffff80ff80102f80405f207fff90ef80000000002f90400000000feffff00fffffff80ff903f6050005f207f712f60000000003f7050000000000feff00fffffffff810fcf406000005f1fd16f40000000004f506000000000000fe00fffffffffff915ea0700000005e522f20000000005f306ffffffffffffffffffffffffff",
"Name": "mod-264-exp-2",
"Gas": 2178,
"NoBenchmark": false
},
{
"Input": "00000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000040000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00",
"Expected": "00fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02fefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe02feffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"Name": "mod-1024-exp-2",
"Gas": 32768,
"NoBenchmark": false
},
{
"Input": "000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000008ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"Expected": "2a02d5f86c2375ff",
"Name": "pawel-1-exp-heavy",
"Gas": 24560,
"NoBenchmark": false
},
{
"Input": "000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000010ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"Expected": "823ef7dc60d6d9616756c48f69b7c4ff",
"Name": "pawel-2-exp-heavy",
"Gas": 6128,
"NoBenchmark": false
},
{
"Input": "000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000150000000000000000000000000000000000000000000000000000000000000018ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"Expected": "c817dd5aa60a41948eed409706c2aa97be3000d4da0261ff",
"Name": "pawel-3-exp-heavy",
"Gas": 2672,
"NoBenchmark": false
},
{
"Input": "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000020ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"Expected": "2defaca0137d6edacbbd5d36d6ed70cbf8a998ffb19fc270d45a18d37e0f35ff",
"Name": "pawel-4-exp-heavy",
"Gas": 1520,
"NoBenchmark": false
},
{
"Input": "000000000000000000000000000000000000000000000000000000000000001700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000017bffffffffffffffffffffffffffffffffffffffffffffbffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffe",
"Expected": "200f14de1d474710c1c979920452e0ffc2ac6f618afba5",
"Name": "mod_vul_pawel_3_exp_8",
"Gas": 1008,
"NoBenchmark": false
} }
] ]

View file

@ -150,7 +150,7 @@ func ckzgComputeCellProofs(blob *Blob) ([]Proof, error) {
return p, nil return p, nil
} }
// ckzgVerifyCellProofs verifies that the blob data corresponds to the provided commitment. // ckzgVerifyCellProofBatch verifies that the blob data corresponds to the provided commitment.
func ckzgVerifyCellProofBatch(blobs []Blob, commitments []Commitment, cellProofs []Proof) error { func ckzgVerifyCellProofBatch(blobs []Blob, commitments []Commitment, cellProofs []Proof) error {
ckzgIniter.Do(ckzgInit) ckzgIniter.Do(ckzgInit)
var ( var (

View file

@ -115,7 +115,7 @@ func gokzgComputeCellProofs(blob *Blob) ([]Proof, error) {
return p, nil return p, nil
} }
// gokzgVerifyCellProofs verifies that the blob data corresponds to the provided commitment. // gokzgVerifyCellProofBatch verifies that the blob data corresponds to the provided commitment.
func gokzgVerifyCellProofBatch(blobs []Blob, commitments []Commitment, cellProofs []Proof) error { func gokzgVerifyCellProofBatch(blobs []Blob, commitments []Commitment, cellProofs []Proof) error {
gokzgIniter.Do(gokzgInit) gokzgIniter.Do(gokzgInit)

View file

@ -443,3 +443,51 @@ func (api *DebugAPI) GetTrieFlushInterval() (string, error) {
} }
return api.eth.blockchain.GetTrieFlushInterval().String(), nil return api.eth.blockchain.GetTrieFlushInterval().String(), nil
} }
// StateSize returns the current state size statistics from the state size tracker.
// Returns an error if the state size tracker is not initialized or if stats are not ready.
func (api *DebugAPI) StateSize(blockHashOrNumber *rpc.BlockNumberOrHash) (interface{}, error) {
sizer := api.eth.blockchain.StateSizer()
if sizer == nil {
return nil, errors.New("state size tracker is not enabled")
}
var (
err error
stats *state.SizeStats
)
if blockHashOrNumber == nil {
stats, err = sizer.Query(nil)
} else {
header, herr := api.eth.APIBackend.HeaderByNumberOrHash(context.Background(), *blockHashOrNumber)
if herr != nil || header == nil {
return nil, fmt.Errorf("block %s is unknown", blockHashOrNumber)
}
stats, err = sizer.Query(&header.Root)
}
if err != nil {
return nil, err
}
if stats == nil {
var s string
if blockHashOrNumber == nil {
s = "chain head"
} else {
s = blockHashOrNumber.String()
}
return nil, fmt.Errorf("state size of %s is not available", s)
}
return map[string]interface{}{
"stateRoot": stats.StateRoot,
"blockNumber": hexutil.Uint64(stats.BlockNumber),
"accounts": hexutil.Uint64(stats.Accounts),
"accountBytes": hexutil.Uint64(stats.AccountBytes),
"storages": hexutil.Uint64(stats.Storages),
"storageBytes": hexutil.Uint64(stats.StorageBytes),
"accountTrienodes": hexutil.Uint64(stats.AccountTrienodes),
"accountTrienodeBytes": hexutil.Uint64(stats.AccountTrienodeBytes),
"storageTrienodes": hexutil.Uint64(stats.StorageTrienodes),
"storageTrienodeBytes": hexutil.Uint64(stats.StorageTrienodeBytes),
"contractCodes": hexutil.Uint64(stats.ContractCodes),
"contractCodeBytes": hexutil.Uint64(stats.ContractCodeBytes),
}, nil
}

View file

@ -240,6 +240,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
// - DATADIR/triedb/merkle.journal // - DATADIR/triedb/merkle.journal
// - DATADIR/triedb/verkle.journal // - DATADIR/triedb/verkle.journal
TrieJournalDirectory: stack.ResolvePath("triedb"), TrieJournalDirectory: stack.ResolvePath("triedb"),
StateSizeTracking: config.EnableStateSizeTracking,
} }
) )
if config.VMTrace != "" { if config.VMTrace != "" {

View file

@ -20,10 +20,12 @@ package catalyst
import ( import (
"errors" "errors"
"fmt" "fmt"
"reflect"
"strconv" "strconv"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
"unicode"
"github.com/ethereum/go-ethereum/beacon/engine" "github.com/ethereum/go-ethereum/beacon/engine"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -80,47 +82,6 @@ const (
beaconUpdateWarnFrequency = 5 * time.Minute beaconUpdateWarnFrequency = 5 * time.Minute
) )
// All methods provided over the engine endpoint.
var caps = []string{
"engine_forkchoiceUpdatedV1",
"engine_forkchoiceUpdatedV2",
"engine_forkchoiceUpdatedV3",
"engine_forkchoiceUpdatedV3P11",
"engine_forkchoiceUpdatedWithWitnessV1",
"engine_forkchoiceUpdatedWithWitnessV2",
"engine_forkchoiceUpdatedWithWitnessV3",
"engine_forkchoiceUpdatedWithWitnessV3P11",
"engine_exchangeTransitionConfigurationV1",
"engine_getPayloadV1",
"engine_getPayloadV2",
"engine_getPayloadV3",
"engine_getPayloadV4",
"engine_getPayloadV4P11",
"engine_getPayloadV5",
"engine_getBlobsV1",
"engine_getBlobsV2",
"engine_newPayloadV1",
"engine_newPayloadV2",
"engine_newPayloadV3",
"engine_newPayloadV4",
"engine_newPayloadV4P11",
"engine_newPayloadWithWitnessV1",
"engine_newPayloadWithWitnessV2",
"engine_newPayloadWithWitnessV3",
"engine_newPayloadWithWitnessV4",
"engine_newPayloadWithWitnessV4P11",
"engine_executeStatelessPayloadV1",
"engine_executeStatelessPayloadV2",
"engine_executeStatelessPayloadV3",
"engine_executeStatelessPayloadV4",
"engine_executeStatelessPayloadV4P11",
"engine_getPayloadBodiesByHashV1",
"engine_getPayloadBodiesByHashV2",
"engine_getPayloadBodiesByRangeV1",
"engine_getPayloadBodiesByRangeV2",
"engine_getClientVersionV1",
}
var ( var (
// Number of blobs requested via getBlobsV2 // Number of blobs requested via getBlobsV2
getBlobsRequestedCounter = metrics.NewRegisteredCounter("engine/getblobs/requested", nil) getBlobsRequestedCounter = metrics.NewRegisteredCounter("engine/getblobs/requested", nil)
@ -534,6 +495,26 @@ func (api *ConsensusAPI) getPayload(payloadID engine.PayloadID, full bool) (*eng
} }
// GetBlobsV1 returns a blob from the transaction pool. // GetBlobsV1 returns a blob from the transaction pool.
//
// Specification:
//
// Given an array of blob versioned hashes client software MUST respond with an
// array of BlobAndProofV1 objects with matching versioned hashes, respecting the
// order of versioned hashes in the input array.
//
// Client software MUST place responses in the order given in the request, using
// null for any missing blobs. For instance:
//
// if the request is [A_versioned_hash, B_versioned_hash, C_versioned_hash] and
// client software has data for blobs A and C, but doesn't have data for B, the
// response MUST be [A, null, C].
//
// Client software MUST support request sizes of at least 128 blob versioned hashes.
// The client MUST return -38004: Too large request error if the number of requested
// blobs is too large.
//
// Client software MAY return an array of all null entries if syncing or otherwise
// unable to serve blob pool data.
func (api *ConsensusAPI) GetBlobsV1(hashes []common.Hash) ([]*engine.BlobAndProofV1, error) { func (api *ConsensusAPI) GetBlobsV1(hashes []common.Hash) ([]*engine.BlobAndProofV1, error) {
if len(hashes) > 128 { if len(hashes) > 128 {
return nil, engine.TooLargeRequest.With(fmt.Errorf("requested blob count too large: %v", len(hashes))) return nil, engine.TooLargeRequest.With(fmt.Errorf("requested blob count too large: %v", len(hashes)))
@ -544,6 +525,10 @@ func (api *ConsensusAPI) GetBlobsV1(hashes []common.Hash) ([]*engine.BlobAndProo
} }
res := make([]*engine.BlobAndProofV1, len(hashes)) res := make([]*engine.BlobAndProofV1, len(hashes))
for i := 0; i < len(blobs); i++ { for i := 0; i < len(blobs); i++ {
// Skip the non-existing blob
if blobs[i] == nil {
continue
}
res[i] = &engine.BlobAndProofV1{ res[i] = &engine.BlobAndProofV1{
Blob: blobs[i][:], Blob: blobs[i][:],
Proof: proofs[i][0][:], Proof: proofs[i][0][:],
@ -553,6 +538,33 @@ func (api *ConsensusAPI) GetBlobsV1(hashes []common.Hash) ([]*engine.BlobAndProo
} }
// GetBlobsV2 returns a blob from the transaction pool. // GetBlobsV2 returns a blob from the transaction pool.
//
// Specification:
// Refer to the specification for engine_getBlobsV1 with changes of the following:
//
// Given an array of blob versioned hashes client software MUST respond with an
// array of BlobAndProofV2 objects with matching versioned hashes, respecting
// the order of versioned hashes in the input array.
//
// Client software MUST return null in case of any missing or older version blobs.
// For instance,
//
// - if the request is [A_versioned_hash, B_versioned_hash, C_versioned_hash] and
// client software has data for blobs A and C, but doesn't have data for B, the
// response MUST be null.
//
// - if the request is [A_versioned_hash_for_blob_with_blob_proof], the response
// MUST be null as well.
//
// Note, geth internally make the conversion from old version to new one, so the
// data will be returned normally.
//
// Client software MUST support request sizes of at least 128 blob versioned
// hashes. The client MUST return -38004: Too large request error if the number
// of requested blobs is too large.
//
// Client software MUST return null if syncing or otherwise unable to serve
// blob pool data.
func (api *ConsensusAPI) GetBlobsV2(hashes []common.Hash) ([]*engine.BlobAndProofV2, error) { func (api *ConsensusAPI) GetBlobsV2(hashes []common.Hash) ([]*engine.BlobAndProofV2, error) {
if len(hashes) > 128 { if len(hashes) > 128 {
return nil, engine.TooLargeRequest.With(fmt.Errorf("requested blob count too large: %v", len(hashes))) return nil, engine.TooLargeRequest.With(fmt.Errorf("requested blob count too large: %v", len(hashes)))
@ -566,12 +578,21 @@ func (api *ConsensusAPI) GetBlobsV2(hashes []common.Hash) ([]*engine.BlobAndProo
getBlobsV2RequestMiss.Inc(1) getBlobsV2RequestMiss.Inc(1)
return nil, nil return nil, nil
} }
getBlobsV2RequestHit.Inc(1)
blobs, _, proofs, err := api.eth.BlobTxPool().GetBlobs(hashes, types.BlobSidecarVersion1) blobs, _, proofs, err := api.eth.BlobTxPool().GetBlobs(hashes, types.BlobSidecarVersion1)
if err != nil { if err != nil {
return nil, engine.InvalidParams.With(err) return nil, engine.InvalidParams.With(err)
} }
// To comply with API spec, check again that we really got all data needed
for _, blob := range blobs {
if blob == nil {
getBlobsV2RequestMiss.Inc(1)
return nil, nil
}
}
getBlobsV2RequestHit.Inc(1)
res := make([]*engine.BlobAndProofV2, len(hashes)) res := make([]*engine.BlobAndProofV2, len(hashes))
for i := 0; i < len(blobs); i++ { for i := 0; i < len(blobs); i++ {
var cellProofs []hexutil.Bytes var cellProofs []hexutil.Bytes
@ -987,6 +1008,15 @@ func (api *ConsensusAPI) checkFork(timestamp uint64, forks ...forks.Fork) bool {
// ExchangeCapabilities returns the current methods provided by this node. // ExchangeCapabilities returns the current methods provided by this node.
func (api *ConsensusAPI) ExchangeCapabilities([]string) []string { func (api *ConsensusAPI) ExchangeCapabilities([]string) []string {
valueT := reflect.TypeOf(api)
caps := make([]string, 0, valueT.NumMethod())
for i := 0; i < valueT.NumMethod(); i++ {
name := []rune(valueT.Method(i).Name)
if string(name) == "ExchangeCapabilities" {
continue
}
caps = append(caps, "engine_"+string(unicode.ToLower(name[0]))+string(name[1:]))
}
return caps return caps
} }

View file

@ -19,7 +19,9 @@ package catalyst
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/ecdsa"
crand "crypto/rand" crand "crypto/rand"
"crypto/sha256"
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
@ -40,6 +42,7 @@ import (
"github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/internal/testrand"
"github.com/ethereum/go-ethereum/internal/version" "github.com/ethereum/go-ethereum/internal/version"
"github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/miner"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
@ -47,6 +50,7 @@ import (
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/holiman/uint256"
) )
var ( var (
@ -112,7 +116,7 @@ func TestEth2AssembleBlock(t *testing.T) {
n, ethservice := startEthService(t, genesis, blocks) n, ethservice := startEthService(t, genesis, blocks)
defer n.Close() defer n.Close()
api := NewConsensusAPI(ethservice) api := newConsensusAPIWithoutHeartbeat(ethservice)
signer := types.NewEIP155Signer(ethservice.BlockChain().Config().ChainID) signer := types.NewEIP155Signer(ethservice.BlockChain().Config().ChainID)
tx, err := types.SignTx(types.NewTransaction(uint64(10), blocks[9].Coinbase(), big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, testKey) tx, err := types.SignTx(types.NewTransaction(uint64(10), blocks[9].Coinbase(), big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, testKey)
if err != nil { if err != nil {
@ -151,7 +155,7 @@ func TestEth2AssembleBlockWithAnotherBlocksTxs(t *testing.T) {
n, ethservice := startEthService(t, genesis, blocks[:9]) n, ethservice := startEthService(t, genesis, blocks[:9])
defer n.Close() defer n.Close()
api := NewConsensusAPI(ethservice) api := newConsensusAPIWithoutHeartbeat(ethservice)
// Put the 10th block's tx in the pool and produce a new block // Put the 10th block's tx in the pool and produce a new block
txs := blocks[9].Transactions() txs := blocks[9].Transactions()
@ -173,7 +177,7 @@ func TestEth2PrepareAndGetPayload(t *testing.T) {
n, ethservice := startEthService(t, genesis, blocks[:9]) n, ethservice := startEthService(t, genesis, blocks[:9])
defer n.Close() defer n.Close()
api := NewConsensusAPI(ethservice) api := newConsensusAPIWithoutHeartbeat(ethservice)
// Put the 10th block's tx in the pool and produce a new block // Put the 10th block's tx in the pool and produce a new block
txs := blocks[9].Transactions() txs := blocks[9].Transactions()
@ -238,8 +242,9 @@ func TestInvalidPayloadTimestamp(t *testing.T) {
genesis, preMergeBlocks := generateMergeChain(10, false) genesis, preMergeBlocks := generateMergeChain(10, false)
n, ethservice := startEthService(t, genesis, preMergeBlocks) n, ethservice := startEthService(t, genesis, preMergeBlocks)
defer n.Close() defer n.Close()
var ( var (
api = NewConsensusAPI(ethservice) api = newConsensusAPIWithoutHeartbeat(ethservice)
parent = ethservice.BlockChain().CurrentBlock() parent = ethservice.BlockChain().CurrentBlock()
) )
tests := []struct { tests := []struct {
@ -281,7 +286,7 @@ func TestEth2NewBlock(t *testing.T) {
defer n.Close() defer n.Close()
var ( var (
api = NewConsensusAPI(ethservice) api = newConsensusAPIWithoutHeartbeat(ethservice)
parent = preMergeBlocks[len(preMergeBlocks)-1] parent = preMergeBlocks[len(preMergeBlocks)-1]
// This EVM code generates a log when the contract is created. // This EVM code generates a log when the contract is created.
@ -434,8 +439,14 @@ func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block)
t.Fatal("can't create node:", err) t.Fatal("can't create node:", err)
} }
mcfg := miner.DefaultConfig ethcfg := &ethconfig.Config{
ethcfg := &ethconfig.Config{Genesis: genesis, SyncMode: ethconfig.FullSync, TrieTimeout: time.Minute, TrieDirtyCache: 256, TrieCleanCache: 256, Miner: mcfg} Genesis: genesis,
SyncMode: ethconfig.FullSync,
TrieTimeout: time.Minute,
TrieDirtyCache: 256,
TrieCleanCache: 256,
Miner: miner.DefaultConfig,
}
ethservice, err := eth.New(n, ethcfg) ethservice, err := eth.New(n, ethcfg)
if err != nil { if err != nil {
t.Fatal("can't create eth service:", err) t.Fatal("can't create eth service:", err)
@ -459,6 +470,7 @@ func TestFullAPI(t *testing.T) {
genesis, preMergeBlocks := generateMergeChain(10, false) genesis, preMergeBlocks := generateMergeChain(10, false)
n, ethservice := startEthService(t, genesis, preMergeBlocks) n, ethservice := startEthService(t, genesis, preMergeBlocks)
defer n.Close() defer n.Close()
var ( var (
parent = ethservice.BlockChain().CurrentBlock() parent = ethservice.BlockChain().CurrentBlock()
// This EVM code generates a log when the contract is created. // This EVM code generates a log when the contract is created.
@ -476,7 +488,7 @@ func TestFullAPI(t *testing.T) {
} }
func setupBlocks(t *testing.T, ethservice *eth.Ethereum, n int, parent *types.Header, callback func(parent *types.Header), withdrawals [][]*types.Withdrawal, beaconRoots []common.Hash, proposerPubkeys []common.Pubkey) []*types.Header { func setupBlocks(t *testing.T, ethservice *eth.Ethereum, n int, parent *types.Header, callback func(parent *types.Header), withdrawals [][]*types.Withdrawal, beaconRoots []common.Hash, proposerPubkeys []common.Pubkey) []*types.Header {
api := NewConsensusAPI(ethservice) api := newConsensusAPIWithoutHeartbeat(ethservice)
var blocks []*types.Header var blocks []*types.Header
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
callback(parent) callback(parent)
@ -528,7 +540,7 @@ func TestExchangeTransitionConfig(t *testing.T) {
defer n.Close() defer n.Close()
// invalid ttd // invalid ttd
api := NewConsensusAPI(ethservice) api := newConsensusAPIWithoutHeartbeat(ethservice)
config := engine.TransitionConfigurationV1{ config := engine.TransitionConfigurationV1{
TerminalTotalDifficulty: (*hexutil.Big)(big.NewInt(0)), TerminalTotalDifficulty: (*hexutil.Big)(big.NewInt(0)),
TerminalBlockHash: common.Hash{}, TerminalBlockHash: common.Hash{},
@ -589,7 +601,7 @@ func TestNewPayloadOnInvalidChain(t *testing.T) {
defer n.Close() defer n.Close()
var ( var (
api = NewConsensusAPI(ethservice) api = newConsensusAPIWithoutHeartbeat(ethservice)
parent = ethservice.BlockChain().CurrentBlock() parent = ethservice.BlockChain().CurrentBlock()
signer = types.LatestSigner(ethservice.BlockChain().Config()) signer = types.LatestSigner(ethservice.BlockChain().Config())
// This EVM code generates a log when the contract is created. // This EVM code generates a log when the contract is created.
@ -693,7 +705,7 @@ func TestEmptyBlocks(t *testing.T) {
defer n.Close() defer n.Close()
commonAncestor := ethservice.BlockChain().CurrentBlock() commonAncestor := ethservice.BlockChain().CurrentBlock()
api := NewConsensusAPI(ethservice) api := newConsensusAPIWithoutHeartbeat(ethservice)
// Setup 10 blocks on the canonical chain // Setup 10 blocks on the canonical chain
setupBlocks(t, ethservice, 10, commonAncestor, func(parent *types.Header) {}, nil, nil, nil) setupBlocks(t, ethservice, 10, commonAncestor, func(parent *types.Header) {}, nil, nil, nil)
@ -820,8 +832,8 @@ func TestTrickRemoteBlockCache(t *testing.T) {
} }
nodeA.Server().AddPeer(nodeB.Server().Self()) nodeA.Server().AddPeer(nodeB.Server().Self())
nodeB.Server().AddPeer(nodeA.Server().Self()) nodeB.Server().AddPeer(nodeA.Server().Self())
apiA := NewConsensusAPI(ethserviceA) apiA := newConsensusAPIWithoutHeartbeat(ethserviceA)
apiB := NewConsensusAPI(ethserviceB) apiB := newConsensusAPIWithoutHeartbeat(ethserviceB)
commonAncestor := ethserviceA.BlockChain().CurrentBlock() commonAncestor := ethserviceA.BlockChain().CurrentBlock()
@ -878,7 +890,7 @@ func TestInvalidBloom(t *testing.T) {
defer n.Close() defer n.Close()
commonAncestor := ethservice.BlockChain().CurrentBlock() commonAncestor := ethservice.BlockChain().CurrentBlock()
api := NewConsensusAPI(ethservice) api := newConsensusAPIWithoutHeartbeat(ethservice)
// Setup 10 blocks on the canonical chain // Setup 10 blocks on the canonical chain
setupBlocks(t, ethservice, 10, commonAncestor, func(parent *types.Header) {}, nil, nil, nil) setupBlocks(t, ethservice, 10, commonAncestor, func(parent *types.Header) {}, nil, nil, nil)
@ -904,7 +916,7 @@ func TestSimultaneousNewBlock(t *testing.T) {
defer n.Close() defer n.Close()
var ( var (
api = NewConsensusAPI(ethservice) api = newConsensusAPIWithoutHeartbeat(ethservice)
parent = preMergeBlocks[len(preMergeBlocks)-1] parent = preMergeBlocks[len(preMergeBlocks)-1]
) )
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
@ -994,7 +1006,7 @@ func TestWithdrawals(t *testing.T) {
n, ethservice := startEthService(t, genesis, blocks) n, ethservice := startEthService(t, genesis, blocks)
defer n.Close() defer n.Close()
api := NewConsensusAPI(ethservice) api := newConsensusAPIWithoutHeartbeat(ethservice)
// 10: Build Shanghai block with no withdrawals. // 10: Build Shanghai block with no withdrawals.
parent := ethservice.BlockChain().CurrentHeader() parent := ethservice.BlockChain().CurrentHeader()
@ -1111,7 +1123,7 @@ func TestNilWithdrawals(t *testing.T) {
n, ethservice := startEthService(t, genesis, blocks) n, ethservice := startEthService(t, genesis, blocks)
defer n.Close() defer n.Close()
api := NewConsensusAPI(ethservice) api := newConsensusAPIWithoutHeartbeat(ethservice)
parent := ethservice.BlockChain().CurrentHeader() parent := ethservice.BlockChain().CurrentHeader()
aa := common.Address{0xaa} aa := common.Address{0xaa}
@ -1307,7 +1319,7 @@ func allBodies(blocks []*types.Block) []*types.Body {
func TestGetBlockBodiesByHash(t *testing.T) { func TestGetBlockBodiesByHash(t *testing.T) {
node, eth, blocks := setupBodies(t) node, eth, blocks := setupBodies(t)
api := NewConsensusAPI(eth) api := newConsensusAPIWithoutHeartbeat(eth)
defer node.Close() defer node.Close()
tests := []struct { tests := []struct {
@ -1363,7 +1375,7 @@ func TestGetBlockBodiesByHash(t *testing.T) {
func TestGetBlockBodiesByRange(t *testing.T) { func TestGetBlockBodiesByRange(t *testing.T) {
node, eth, blocks := setupBodies(t) node, eth, blocks := setupBodies(t)
api := NewConsensusAPI(eth) api := newConsensusAPIWithoutHeartbeat(eth)
defer node.Close() defer node.Close()
tests := []struct { tests := []struct {
@ -1444,7 +1456,7 @@ func TestGetBlockBodiesByRange(t *testing.T) {
func TestGetBlockBodiesByRangeInvalidParams(t *testing.T) { func TestGetBlockBodiesByRangeInvalidParams(t *testing.T) {
node, eth, _ := setupBodies(t) node, eth, _ := setupBodies(t)
api := NewConsensusAPI(eth) api := newConsensusAPIWithoutHeartbeat(eth)
defer node.Close() defer node.Close()
tests := []struct { tests := []struct {
start hexutil.Uint64 start hexutil.Uint64
@ -1556,7 +1568,7 @@ func TestParentBeaconBlockRoot(t *testing.T) {
n, ethservice := startEthService(t, genesis, blocks) n, ethservice := startEthService(t, genesis, blocks)
defer n.Close() defer n.Close()
api := NewConsensusAPI(ethservice) api := newConsensusAPIWithoutHeartbeat(ethservice)
// 11: Build Shanghai block with no withdrawals. // 11: Build Shanghai block with no withdrawals.
parent := ethservice.BlockChain().CurrentHeader() parent := ethservice.BlockChain().CurrentHeader()
@ -1639,7 +1651,7 @@ func TestWitnessCreationAndConsumption(t *testing.T) {
n, ethservice := startEthService(t, genesis, blocks[:9]) n, ethservice := startEthService(t, genesis, blocks[:9])
defer n.Close() defer n.Close()
api := NewConsensusAPI(ethservice) api := newConsensusAPIWithoutHeartbeat(ethservice)
// Put the 10th block's tx in the pool and produce a new block // Put the 10th block's tx in the pool and produce a new block
txs := blocks[9].Transactions() txs := blocks[9].Transactions()
@ -1731,7 +1743,7 @@ func TestGetClientVersion(t *testing.T) {
n, ethservice := startEthService(t, genesis, preMergeBlocks) n, ethservice := startEthService(t, genesis, preMergeBlocks)
defer n.Close() defer n.Close()
api := NewConsensusAPI(ethservice) api := newConsensusAPIWithoutHeartbeat(ethservice)
info := engine.ClientVersionV1{ info := engine.ClientVersionV1{
Code: "TT", Code: "TT",
Name: "test", Name: "test",
@ -1805,3 +1817,245 @@ func TestValidateRequests(t *testing.T) {
}) })
} }
} }
var (
testBlobs []*kzg4844.Blob
testBlobCommits []kzg4844.Commitment
testBlobProofs []kzg4844.Proof
testBlobCellProofs [][]kzg4844.Proof
testBlobVHashes [][32]byte
)
func init() {
for i := 0; i < 6; i++ {
testBlob := &kzg4844.Blob{byte(i)}
testBlobs = append(testBlobs, testBlob)
testBlobCommit, _ := kzg4844.BlobToCommitment(testBlob)
testBlobCommits = append(testBlobCommits, testBlobCommit)
testBlobProof, _ := kzg4844.ComputeBlobProof(testBlob, testBlobCommit)
testBlobProofs = append(testBlobProofs, testBlobProof)
testBlobCellProof, _ := kzg4844.ComputeCellProofs(testBlob)
testBlobCellProofs = append(testBlobCellProofs, testBlobCellProof)
testBlobVHash := kzg4844.CalcBlobHashV1(sha256.New(), &testBlobCommit)
testBlobVHashes = append(testBlobVHashes, testBlobVHash)
}
}
// makeMultiBlobTx is a utility method to construct a random blob tx with
// certain number of blobs in its sidecar.
func makeMultiBlobTx(chainConfig *params.ChainConfig, nonce uint64, blobCount int, blobOffset int, key *ecdsa.PrivateKey, version byte) *types.Transaction {
var (
blobs []kzg4844.Blob
blobHashes []common.Hash
commitments []kzg4844.Commitment
proofs []kzg4844.Proof
)
for i := 0; i < blobCount; i++ {
blobs = append(blobs, *testBlobs[blobOffset+i])
commitments = append(commitments, testBlobCommits[blobOffset+i])
if version == types.BlobSidecarVersion0 {
proofs = append(proofs, testBlobProofs[blobOffset+i])
} else {
cellProofs, _ := kzg4844.ComputeCellProofs(testBlobs[blobOffset+i])
proofs = append(proofs, cellProofs...)
}
blobHashes = append(blobHashes, testBlobVHashes[blobOffset+i])
}
blobtx := &types.BlobTx{
ChainID: uint256.MustFromBig(chainConfig.ChainID),
Nonce: nonce,
GasTipCap: uint256.NewInt(1),
GasFeeCap: uint256.NewInt(1000),
Gas: 21000,
BlobFeeCap: uint256.NewInt(1000),
BlobHashes: blobHashes,
Value: uint256.NewInt(100),
Sidecar: types.NewBlobTxSidecar(version, blobs, commitments, proofs),
}
return types.MustSignNewTx(key, types.LatestSigner(chainConfig), blobtx)
}
func newGetBlobEnv(t *testing.T, version byte) (*node.Node, *ConsensusAPI) {
var (
// Create a database pre-initialize with a genesis block
config = *params.MergedTestChainConfig
key1, _ = crypto.GenerateKey()
key2, _ = crypto.GenerateKey()
key3, _ = crypto.GenerateKey()
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
addr2 = crypto.PubkeyToAddress(key2.PublicKey)
addr3 = crypto.PubkeyToAddress(key3.PublicKey)
)
// Disable Osaka fork for GetBlobsV1
if version == 0 {
config.OsakaTime = nil
}
gspec := &core.Genesis{
Config: &config,
Alloc: types.GenesisAlloc{
testAddr: {Balance: testBalance},
addr1: {Balance: testBalance},
addr2: {Balance: testBalance},
addr3: {Balance: testBalance},
},
Difficulty: common.Big0,
}
n, ethServ := startEthService(t, gspec, nil)
// fill blob txs into the pool
tx1 := makeMultiBlobTx(&config, 0, 2, 0, key1, version) // blob[0, 2)
tx2 := makeMultiBlobTx(&config, 0, 2, 2, key2, version) // blob[2, 4)
tx3 := makeMultiBlobTx(&config, 0, 2, 4, key3, version) // blob[4, 6)
ethServ.TxPool().Add([]*types.Transaction{tx1, tx2, tx3}, true)
api := newConsensusAPIWithoutHeartbeat(ethServ)
return n, api
}
func TestGetBlobsV1(t *testing.T) {
n, api := newGetBlobEnv(t, 0)
defer n.Close()
suites := []struct {
start int
limit int
fillRandom bool
}{
{
start: 0, limit: 1,
},
{
start: 0, limit: 1, fillRandom: true,
},
{
start: 0, limit: 2,
},
{
start: 0, limit: 2, fillRandom: true,
},
{
start: 1, limit: 3,
},
{
start: 1, limit: 3, fillRandom: true,
},
{
start: 0, limit: 6,
},
{
start: 0, limit: 6, fillRandom: true,
},
{
start: 1, limit: 5,
},
{
start: 1, limit: 5, fillRandom: true,
},
}
for i, suite := range suites {
// Fill the request for retrieving blobs
var (
vhashes []common.Hash
expect []*engine.BlobAndProofV1
)
// fill missing blob at the beginning
if suite.fillRandom {
vhashes = append(vhashes, testrand.Hash())
expect = append(expect, nil)
}
for j := suite.start; j < suite.limit; j++ {
vhashes = append(vhashes, testBlobVHashes[j])
expect = append(expect, &engine.BlobAndProofV1{
Blob: testBlobs[j][:],
Proof: testBlobProofs[j][:],
})
// fill missing blobs in the middle
if suite.fillRandom && rand.Intn(2) == 0 {
vhashes = append(vhashes, testrand.Hash())
expect = append(expect, nil)
}
}
// fill missing blobs at the end
if suite.fillRandom {
vhashes = append(vhashes, testrand.Hash())
expect = append(expect, nil)
}
result, err := api.GetBlobsV1(vhashes)
if err != nil {
t.Errorf("Unexpected error for case %d, %v", i, err)
}
if !reflect.DeepEqual(result, expect) {
t.Fatalf("Unexpected result for case %d", i)
}
}
}
func TestGetBlobsV2(t *testing.T) {
n, api := newGetBlobEnv(t, 1)
defer n.Close()
suites := []struct {
start int
limit int
fillRandom bool
}{
{
start: 0, limit: 1,
},
{
start: 0, limit: 2,
},
{
start: 1, limit: 3,
},
{
start: 0, limit: 6,
},
{
start: 1, limit: 5,
},
{
start: 0, limit: 6, fillRandom: true,
},
}
for i, suite := range suites {
// Fill the request for retrieving blobs
var (
vhashes []common.Hash
expect []*engine.BlobAndProofV2
)
// fill missing blob
if suite.fillRandom {
vhashes = append(vhashes, testrand.Hash())
}
for j := suite.start; j < suite.limit; j++ {
vhashes = append(vhashes, testBlobVHashes[j])
var cellProofs []hexutil.Bytes
for _, proof := range testBlobCellProofs[j] {
cellProofs = append(cellProofs, proof[:])
}
expect = append(expect, &engine.BlobAndProofV2{
Blob: testBlobs[j][:],
CellProofs: cellProofs,
})
}
result, err := api.GetBlobsV2(vhashes)
if err != nil {
t.Errorf("Unexpected error for case %d, %v", i, err)
}
// null is responded if any blob is missing
if suite.fillRandom {
expect = nil
}
if !reflect.DeepEqual(result, expect) {
t.Fatalf("Unexpected result for case %d", i)
}
}
}

View file

@ -144,6 +144,9 @@ type Config struct {
// Enables tracking of SHA3 preimages in the VM // Enables tracking of SHA3 preimages in the VM
EnablePreimageRecording bool EnablePreimageRecording bool
// Enables tracking of state size
EnableStateSizeTracking bool
// Enables VM tracing // Enables VM tracing
VMTrace string VMTrace string
VMTraceJsonConfig string VMTraceJsonConfig string

View file

@ -49,6 +49,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
BlobPool blobpool.Config BlobPool blobpool.Config
GPO gasprice.Config GPO gasprice.Config
EnablePreimageRecording bool EnablePreimageRecording bool
EnableStateSizeTracking bool
VMTrace string VMTrace string
VMTraceJsonConfig string VMTraceJsonConfig string
RPCGasCap uint64 RPCGasCap uint64
@ -90,6 +91,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
enc.BlobPool = c.BlobPool enc.BlobPool = c.BlobPool
enc.GPO = c.GPO enc.GPO = c.GPO
enc.EnablePreimageRecording = c.EnablePreimageRecording enc.EnablePreimageRecording = c.EnablePreimageRecording
enc.EnableStateSizeTracking = c.EnableStateSizeTracking
enc.VMTrace = c.VMTrace enc.VMTrace = c.VMTrace
enc.VMTraceJsonConfig = c.VMTraceJsonConfig enc.VMTraceJsonConfig = c.VMTraceJsonConfig
enc.RPCGasCap = c.RPCGasCap enc.RPCGasCap = c.RPCGasCap
@ -135,6 +137,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
BlobPool *blobpool.Config BlobPool *blobpool.Config
GPO *gasprice.Config GPO *gasprice.Config
EnablePreimageRecording *bool EnablePreimageRecording *bool
EnableStateSizeTracking *bool
VMTrace *string VMTrace *string
VMTraceJsonConfig *string VMTraceJsonConfig *string
RPCGasCap *uint64 RPCGasCap *uint64
@ -243,6 +246,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
if dec.EnablePreimageRecording != nil { if dec.EnablePreimageRecording != nil {
c.EnablePreimageRecording = *dec.EnablePreimageRecording c.EnablePreimageRecording = *dec.EnablePreimageRecording
} }
if dec.EnableStateSizeTracking != nil {
c.EnableStateSizeTracking = *dec.EnableStateSizeTracking
}
if dec.VMTrace != nil { if dec.VMTrace != nil {
c.VMTrace = *dec.VMTrace c.VMTrace = *dec.VMTrace
} }

View file

@ -17,21 +17,22 @@
package eth package eth
import ( import (
"cmp"
crand "crypto/rand"
"errors" "errors"
"maps" "maps"
"math" "math"
"math/big"
"slices" "slices"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/dchest/siphash"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/eth/fetcher" "github.com/ethereum/go-ethereum/eth/fetcher"
@ -122,6 +123,7 @@ type handler struct {
downloader *downloader.Downloader downloader *downloader.Downloader
txFetcher *fetcher.TxFetcher txFetcher *fetcher.TxFetcher
peers *peerSet peers *peerSet
txBroadcastKey [16]byte
eventMux *event.TypeMux eventMux *event.TypeMux
txsCh chan core.NewTxsEvent txsCh chan core.NewTxsEvent
@ -153,6 +155,7 @@ func newHandler(config *handlerConfig) (*handler, error) {
txpool: config.TxPool, txpool: config.TxPool,
chain: config.Chain, chain: config.Chain,
peers: newPeerSet(), peers: newPeerSet(),
txBroadcastKey: newBroadcastChoiceKey(),
requiredBlocks: config.RequiredBlocks, requiredBlocks: config.RequiredBlocks,
quitSync: make(chan struct{}), quitSync: make(chan struct{}),
handlerDoneCh: make(chan struct{}), handlerDoneCh: make(chan struct{}),
@ -480,58 +483,40 @@ func (h *handler) BroadcastTransactions(txs types.Transactions) {
txset = make(map[*ethPeer][]common.Hash) // Set peer->hash to transfer directly txset = make(map[*ethPeer][]common.Hash) // Set peer->hash to transfer directly
annos = make(map[*ethPeer][]common.Hash) // Set peer->hash to announce annos = make(map[*ethPeer][]common.Hash) // Set peer->hash to announce
)
// Broadcast transactions to a batch of peers not knowing about it
direct := big.NewInt(int64(math.Sqrt(float64(h.peers.len())))) // Approximate number of peers to broadcast to
if direct.BitLen() == 0 {
direct = big.NewInt(1)
}
total := new(big.Int).Exp(direct, big.NewInt(2), nil) // Stabilise total peer count a bit based on sqrt peers
var ( signer = types.LatestSigner(h.chain.Config())
signer = types.LatestSigner(h.chain.Config()) // Don't care about chain status, we just need *a* sender choice = newBroadcastChoice(h.nodeID, h.txBroadcastKey)
hasher = crypto.NewKeccakState() peers = h.peers.all()
hash = make([]byte, 32)
) )
for _, tx := range txs { for _, tx := range txs {
var maybeDirect bool var directSet map[*ethPeer]struct{}
switch { switch {
case tx.Type() == types.BlobTxType: case tx.Type() == types.BlobTxType:
blobTxs++ blobTxs++
case tx.Size() > txMaxBroadcastSize: case tx.Size() > txMaxBroadcastSize:
largeTxs++ largeTxs++
default: default:
maybeDirect = true // Get transaction sender address. Here we can ignore any error
// since we're just interested in any value.
txSender, _ := types.Sender(signer, tx)
directSet = choice.choosePeers(peers, txSender)
} }
// Send the transaction (if it's small enough) directly to a subset of
// the peers that have not received it yet, ensuring that the flow of
// transactions is grouped by account to (try and) avoid nonce gaps.
//
// To do this, we hash the local enode IW with together with a peer's
// enode ID together with the transaction sender and broadcast if
// `sha(self, peer, sender) mod peers < sqrt(peers)`.
for _, peer := range h.peers.peersWithoutTransaction(tx.Hash()) {
var broadcast bool
if maybeDirect {
hasher.Reset()
hasher.Write(h.nodeID.Bytes())
hasher.Write(peer.Node().ID().Bytes())
from, _ := types.Sender(signer, tx) // Ignore error, we only use the addr as a propagation target splitter for _, peer := range peers {
hasher.Write(from.Bytes()) if peer.KnownTransaction(tx.Hash()) {
continue
hasher.Read(hash)
if new(big.Int).Mod(new(big.Int).SetBytes(hash), total).Cmp(direct) < 0 {
broadcast = true
} }
} if _, ok := directSet[peer]; ok {
if broadcast { // Send direct.
txset[peer] = append(txset[peer], tx.Hash()) txset[peer] = append(txset[peer], tx.Hash())
} else { } else {
// Send announcement.
annos[peer] = append(annos[peer], tx.Hash()) annos[peer] = append(annos[peer], tx.Hash())
} }
} }
} }
for peer, hashes := range txset { for peer, hashes := range txset {
directCount += len(hashes) directCount += len(hashes)
peer.AsyncSendTransactions(hashes) peer.AsyncSendTransactions(hashes)
@ -594,7 +579,7 @@ func newBlockRangeState(chain *core.BlockChain, typeMux *event.TypeMux) *blockRa
return st return st
} }
// blockRangeBroadcastLoop announces changes in locally-available block range to peers. // blockRangeLoop announces changes in locally-available block range to peers.
// The range to announce is the range that is available in the store, so it's not just // The range to announce is the range that is available in the store, so it's not just
// about imported blocks. // about imported blocks.
func (h *handler) blockRangeLoop(st *blockRangeState) { func (h *handler) blockRangeLoop(st *blockRangeState) {
@ -696,3 +681,62 @@ func (st *blockRangeState) stop() {
func (st *blockRangeState) currentRange() eth.BlockRangeUpdatePacket { func (st *blockRangeState) currentRange() eth.BlockRangeUpdatePacket {
return *st.next.Load() return *st.next.Load()
} }
// broadcastChoice implements a deterministic random choice of peers. This is designed
// specifically for choosing which peer receives a direct broadcast of a transaction.
//
// The choice is made based on the involved p2p node IDs and the transaction sender,
// ensuring that the flow of transactions is grouped by account to (try and) avoid nonce
// gaps.
type broadcastChoice struct {
self enode.ID
key [16]byte
buffer map[*ethPeer]struct{}
tmp []broadcastPeer
}
type broadcastPeer struct {
p *ethPeer
score uint64
}
func newBroadcastChoiceKey() (k [16]byte) {
crand.Read(k[:])
return k
}
func newBroadcastChoice(self enode.ID, key [16]byte) *broadcastChoice {
return &broadcastChoice{
self: self,
key: key,
buffer: make(map[*ethPeer]struct{}),
}
}
// choosePeers selects the peers that will receive a direct transaction broadcast message.
// Note the return value will only stay valid until the next call to choosePeers.
func (bc *broadcastChoice) choosePeers(peers []*ethPeer, txSender common.Address) map[*ethPeer]struct{} {
// Compute randomized scores.
bc.tmp = slices.Grow(bc.tmp[:0], len(peers))[:len(peers)]
hash := siphash.New(bc.key[:])
for i, peer := range peers {
hash.Reset()
hash.Write(bc.self[:])
hash.Write(peer.Peer.Peer.ID().Bytes())
hash.Write(txSender[:])
bc.tmp[i] = broadcastPeer{peer, hash.Sum64()}
}
// Sort by score.
slices.SortFunc(bc.tmp, func(a, b broadcastPeer) int {
return cmp.Compare(a.score, b.score)
})
// Take top n.
clear(bc.buffer)
n := int(math.Ceil(math.Sqrt(float64(len(bc.tmp)))))
for i := range n {
bc.buffer[bc.tmp[i].p] = struct{}{}
}
return bc.buffer
}

View file

@ -17,9 +17,12 @@
package eth package eth
import ( import (
"maps"
"math/big" "math/big"
"math/rand"
"sort" "sort"
"sync" "sync"
"testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/consensus/ethash"
@ -29,8 +32,11 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/eth/protocols/eth"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/holiman/uint256" "github.com/holiman/uint256"
@ -212,3 +218,102 @@ func (b *testHandler) close() {
b.handler.Stop() b.handler.Stop()
b.chain.Stop() b.chain.Stop()
} }
func TestBroadcastChoice(t *testing.T) {
self := enode.HexID("1111111111111111111111111111111111111111111111111111111111111111")
choice49 := newBroadcastChoice(self, [16]byte{1})
choice50 := newBroadcastChoice(self, [16]byte{1})
// Create test peers and random tx sender addresses.
rand := rand.New(rand.NewSource(33))
txsenders := make([]common.Address, 400)
for i := range txsenders {
rand.Read(txsenders[i][:])
}
peers := createTestPeers(rand, 50)
defer closePeers(peers)
// Evaluate choice49 first.
expectedCount := 7 // sqrt(49)
var chosen49 = make([]map[*ethPeer]struct{}, len(txsenders))
for i, txSender := range txsenders {
set := choice49.choosePeers(peers[:49], txSender)
chosen49[i] = maps.Clone(set)
// Sanity check choices. Here we check that the function selects different peers
// for different transaction senders.
if len(set) != expectedCount {
t.Fatalf("choice49 produced wrong count %d, want %d", len(set), expectedCount)
}
if i > 0 && maps.Equal(set, chosen49[i-1]) {
t.Errorf("choice49 for tx %d is equal to tx %d", i, i-1)
}
}
// Evaluate choice50 for the same peers and transactions. It should always yield more
// peers than choice49, and the chosen set should be a superset of choice49's.
for i, txSender := range txsenders {
set := choice50.choosePeers(peers[:50], txSender)
if len(set) < len(chosen49[i]) {
t.Errorf("for tx %d, choice50 has less peers than choice49", i)
}
for p := range chosen49[i] {
if _, ok := set[p]; !ok {
t.Errorf("for tx %d, choice50 did not choose peer %v, but choice49 did", i, p.ID())
}
}
}
}
func BenchmarkBroadcastChoice(b *testing.B) {
b.Run("50", func(b *testing.B) {
benchmarkBroadcastChoice(b, 50)
})
b.Run("200", func(b *testing.B) {
benchmarkBroadcastChoice(b, 200)
})
b.Run("500", func(b *testing.B) {
benchmarkBroadcastChoice(b, 500)
})
}
// This measures the overhead of sending one transaction to N peers.
func benchmarkBroadcastChoice(b *testing.B, npeers int) {
rand := rand.New(rand.NewSource(33))
peers := createTestPeers(rand, npeers)
defer closePeers(peers)
txsenders := make([]common.Address, b.N)
for i := range txsenders {
rand.Read(txsenders[i][:])
}
self := enode.HexID("1111111111111111111111111111111111111111111111111111111111111111")
choice := newBroadcastChoice(self, [16]byte{1})
b.ResetTimer()
for i := range b.N {
set := choice.choosePeers(peers, txsenders[i])
if len(set) == 0 {
b.Fatal("empty result")
}
}
}
func createTestPeers(rand *rand.Rand, n int) []*ethPeer {
peers := make([]*ethPeer, n)
for i := range peers {
var id enode.ID
rand.Read(id[:])
p2pPeer := p2p.NewPeer(id, "test", nil)
ep := eth.NewPeer(eth.ETH69, p2pPeer, nil, nil)
peers[i] = &ethPeer{Peer: ep}
}
return peers
}
func closePeers(peers []*ethPeer) {
for _, p := range peers {
p.Close()
}
}

View file

@ -19,9 +19,10 @@ package eth
import ( import (
"errors" "errors"
"fmt" "fmt"
"maps"
"slices"
"sync" "sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/eth/protocols/eth"
"github.com/ethereum/go-ethereum/eth/protocols/snap" "github.com/ethereum/go-ethereum/eth/protocols/snap"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
@ -191,19 +192,12 @@ func (ps *peerSet) peer(id string) *ethPeer {
return ps.peers[id] return ps.peers[id]
} }
// peersWithoutTransaction retrieves a list of peers that do not have a given // all returns all current peers.
// transaction in their set of known hashes. func (ps *peerSet) all() []*ethPeer {
func (ps *peerSet) peersWithoutTransaction(hash common.Hash) []*ethPeer {
ps.lock.RLock() ps.lock.RLock()
defer ps.lock.RUnlock() defer ps.lock.RUnlock()
list := make([]*ethPeer, 0, len(ps.peers)) return slices.Collect(maps.Values(ps.peers))
for _, p := range ps.peers {
if !p.KnownTransaction(hash) {
list = append(list, p)
}
}
return list
} }
// len returns if the current number of `eth` peers in the set. Since the `snap` // len returns if the current number of `eth` peers in the set. Since the `snap`

View file

@ -88,9 +88,17 @@ func testSnapSyncDisabling(t *testing.T, ethVer uint, snapVer uint) {
if err := empty.handler.downloader.BeaconSync(ethconfig.SnapSync, full.chain.CurrentBlock(), nil); err != nil { if err := empty.handler.downloader.BeaconSync(ethconfig.SnapSync, full.chain.CurrentBlock(), nil); err != nil {
t.Fatal("sync failed:", err) t.Fatal("sync failed:", err)
} }
time.Sleep(time.Second * 5) // Downloader internally has to wait a timer (3s) to be expired before exiting // Downloader internally has to wait for a timer (3s) to be expired before
// exiting. Poll after to determine if sync is disabled.
if empty.handler.snapSync.Load() { time.Sleep(time.Second * 3)
for timeout := time.After(time.Second); ; {
select {
case <-timeout:
t.Fatalf("snap sync not disabled after successful synchronisation") t.Fatalf("snap sync not disabled after successful synchronisation")
case <-time.After(100 * time.Millisecond):
if !empty.handler.snapSync.Load() {
return
}
}
} }
} }

View file

@ -89,6 +89,7 @@ func (s *Syncer) run() {
target *types.Header target *types.Header
ticker = time.NewTicker(time.Second * 5) ticker = time.NewTicker(time.Second * 5)
) )
defer ticker.Stop()
for { for {
select { select {
case req := <-s.request: case req := <-s.request:

View file

@ -527,7 +527,7 @@ func TestTraceTransaction(t *testing.T) {
b.AddTx(tx) b.AddTx(tx)
target = tx.Hash() target = tx.Hash()
}) })
defer backend.chain.Stop() defer backend.teardown()
api := NewAPI(backend) api := NewAPI(backend)
result, err := api.TraceTransaction(context.Background(), target, nil) result, err := api.TraceTransaction(context.Background(), target, nil)
if err != nil { if err != nil {
@ -584,7 +584,7 @@ func TestTraceBlock(t *testing.T) {
b.AddTx(tx) b.AddTx(tx)
txHash = tx.Hash() txHash = tx.Hash()
}) })
defer backend.chain.Stop() defer backend.teardown()
api := NewAPI(backend) api := NewAPI(backend)
var testSuite = []struct { var testSuite = []struct {
@ -681,7 +681,7 @@ func TestTracingWithOverrides(t *testing.T) {
signer, accounts[0].key) signer, accounts[0].key)
b.AddTx(tx) b.AddTx(tx)
}) })
defer backend.chain.Stop() defer backend.teardown()
api := NewAPI(backend) api := NewAPI(backend)
randomAccounts := newAccounts(3) randomAccounts := newAccounts(3)
type res struct { type res struct {
@ -1105,6 +1105,7 @@ func TestTraceChain(t *testing.T) {
nonce += 1 nonce += 1
} }
}) })
defer backend.teardown()
backend.refHook = func() { ref.Add(1) } backend.refHook = func() { ref.Add(1) }
backend.relHook = func() { rel.Add(1) } backend.relHook = func() { rel.Add(1) }
api := NewAPI(backend) api := NewAPI(backend)
@ -1212,7 +1213,7 @@ func TestTraceBlockWithBasefee(t *testing.T) {
txHash = tx.Hash() txHash = tx.Hash()
baseFee.Set(b.BaseFee()) baseFee.Set(b.BaseFee())
}) })
defer backend.chain.Stop() defer backend.teardown()
api := NewAPI(backend) api := NewAPI(backend)
var testSuite = []struct { var testSuite = []struct {
@ -1298,7 +1299,7 @@ func TestStandardTraceBlockToFile(t *testing.T) {
b.AddTx(tx) b.AddTx(tx)
txHashs = append(txHashs, tx.Hash()) txHashs = append(txHashs, tx.Hash())
}) })
defer backend.chain.Stop() defer backend.teardown()
var testSuite = []struct { var testSuite = []struct {
blockNumber rpc.BlockNumber blockNumber rpc.BlockNumber

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