Merge branch 'master' into supply-delta-live-tracer

# Conflicts:
#	core/tracing/hooks.go
This commit is contained in:
Chris Ziogas 2024-04-25 09:34:59 +03:00
commit a6541d2e57
No known key found for this signature in database
GPG key ID: 2329CF479E36E2DA
314 changed files with 5429 additions and 12471 deletions

View file

@ -127,7 +127,7 @@ func (arguments Arguments) Copy(v interface{}, values []interface{}) error {
return arguments.copyAtomic(v, values[0]) return arguments.copyAtomic(v, values[0])
} }
// unpackAtomic unpacks ( hexdata -> go ) a single value // copyAtomic copies ( hexdata -> go ) a single value
func (arguments Arguments) copyAtomic(v interface{}, marshalledValues interface{}) error { func (arguments Arguments) copyAtomic(v interface{}, marshalledValues interface{}) error {
dst := reflect.ValueOf(v).Elem() dst := reflect.ValueOf(v).Elem()
src := reflect.ValueOf(marshalledValues) src := reflect.ValueOf(marshalledValues)

View file

@ -24,7 +24,7 @@ import (
"strings" "strings"
) )
// ConvertType converts an interface of a runtime type into a interface of the // ConvertType converts an interface of a runtime type into an interface of the
// given type, e.g. turn this code: // given type, e.g. turn this code:
// //
// var fields []reflect.StructField // var fields []reflect.StructField
@ -33,7 +33,7 @@ import (
// Name: "X", // Name: "X",
// Type: reflect.TypeOf(new(big.Int)), // Type: reflect.TypeOf(new(big.Int)),
// Tag: reflect.StructTag("json:\"" + "x" + "\""), // Tag: reflect.StructTag("json:\"" + "x" + "\""),
// } // })
// //
// into: // into:
// //

View file

@ -25,7 +25,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
// typeWithoutStringer is a alias for the Type type which simply doesn't implement // typeWithoutStringer is an alias for the Type type which simply doesn't implement
// the stringer interface to allow printing type details in the tests below. // the stringer interface to allow printing type details in the tests below.
type typeWithoutStringer Type type typeWithoutStringer Type

View file

@ -205,7 +205,7 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio
to = &t to = &t
} }
args := &apitypes.SendTxArgs{ args := &apitypes.SendTxArgs{
Data: &data, Input: &data,
Nonce: hexutil.Uint64(tx.Nonce()), Nonce: hexutil.Uint64(tx.Nonce()),
Value: hexutil.Big(*tx.Value()), Value: hexutil.Big(*tx.Value()),
Gas: hexutil.Uint64(tx.Gas()), Gas: hexutil.Uint64(tx.Gas()),
@ -215,7 +215,7 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio
switch tx.Type() { switch tx.Type() {
case types.LegacyTxType, types.AccessListTxType: case types.LegacyTxType, types.AccessListTxType:
args.GasPrice = (*hexutil.Big)(tx.GasPrice()) args.GasPrice = (*hexutil.Big)(tx.GasPrice())
case types.DynamicFeeTxType: case types.DynamicFeeTxType, types.BlobTxType:
args.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap()) args.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap())
args.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap()) args.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap())
default: default:
@ -235,6 +235,17 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio
accessList := tx.AccessList() accessList := tx.AccessList()
args.AccessList = &accessList args.AccessList = &accessList
} }
if tx.Type() == types.BlobTxType {
args.BlobHashes = tx.BlobHashes()
sidecar := tx.BlobTxSidecar()
if sidecar == nil {
return nil, errors.New("blobs must be present for signing")
}
args.Blobs = sidecar.Blobs
args.Commitments = sidecar.Commitments
args.Proofs = sidecar.Proofs
}
var res signTransactionResult var res signTransactionResult
if err := api.client.Call(&res, "account_signTransaction", args); err != nil { if err := api.client.Call(&res, "account_signTransaction", args); err != nil {
return nil, err return nil, err

View file

@ -51,7 +51,7 @@ var (
} }
) )
// waitWatcherStarts waits up to 1s for the keystore watcher to start. // waitWatcherStart waits up to 1s for the keystore watcher to start.
func waitWatcherStart(ks *KeyStore) bool { func waitWatcherStart(ks *KeyStore) bool {
// On systems where file watch is not supported, just return "ok". // On systems where file watch is not supported, just return "ok".
if !ks.cache.watcher.enabled() { if !ks.cache.watcher.enabled() {

View file

@ -343,7 +343,7 @@ func TestWalletNotifications(t *testing.T) {
checkEvents(t, wantEvents, events) checkEvents(t, wantEvents, events)
} }
// TestImportExport tests the import functionality of a keystore. // TestImportECDSA tests the import functionality of a keystore.
func TestImportECDSA(t *testing.T) { func TestImportECDSA(t *testing.T) {
t.Parallel() t.Parallel()
_, ks := tmpKeyStore(t) _, ks := tmpKeyStore(t)
@ -362,7 +362,7 @@ func TestImportECDSA(t *testing.T) {
} }
} }
// TestImportECDSA tests the import and export functionality of a keystore. // TestImportExport tests the import and export functionality of a keystore.
func TestImportExport(t *testing.T) { func TestImportExport(t *testing.T) {
t.Parallel() t.Parallel()
_, ks := tmpKeyStore(t) _, ks := tmpKeyStore(t)

View file

@ -19,6 +19,7 @@ package blsync
import ( import (
"github.com/ethereum/go-ethereum/beacon/light/request" "github.com/ethereum/go-ethereum/beacon/light/request"
"github.com/ethereum/go-ethereum/beacon/light/sync" "github.com/ethereum/go-ethereum/beacon/light/sync"
"github.com/ethereum/go-ethereum/beacon/params"
"github.com/ethereum/go-ethereum/beacon/types" "github.com/ethereum/go-ethereum/beacon/types"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/lru" "github.com/ethereum/go-ethereum/common/lru"
@ -40,7 +41,7 @@ type beaconBlockSync struct {
type headTracker interface { type headTracker interface {
PrefetchHead() types.HeadInfo PrefetchHead() types.HeadInfo
ValidatedHead() (types.SignedHeader, bool) ValidatedOptimistic() (types.OptimisticUpdate, bool)
ValidatedFinality() (types.FinalityUpdate, bool) ValidatedFinality() (types.FinalityUpdate, bool)
} }
@ -65,6 +66,7 @@ func (s *beaconBlockSync) Process(requester request.Requester, events []request.
case request.EvResponse, request.EvFail, request.EvTimeout: case request.EvResponse, request.EvFail, request.EvTimeout:
sid, req, resp := event.RequestInfo() sid, req, resp := event.RequestInfo()
blockRoot := common.Hash(req.(sync.ReqBeaconBlock)) blockRoot := common.Hash(req.(sync.ReqBeaconBlock))
log.Debug("Beacon block event", "type", event.Type.Name, "hash", blockRoot)
if resp != nil { if resp != nil {
s.recentBlocks.Add(blockRoot, resp.(*types.BeaconBlock)) s.recentBlocks.Add(blockRoot, resp.(*types.BeaconBlock))
} }
@ -79,8 +81,8 @@ func (s *beaconBlockSync) Process(requester request.Requester, events []request.
} }
s.updateEventFeed() s.updateEventFeed()
// request validated head block if unavailable and not yet requested // request validated head block if unavailable and not yet requested
if vh, ok := s.headTracker.ValidatedHead(); ok { if vh, ok := s.headTracker.ValidatedOptimistic(); ok {
s.tryRequestBlock(requester, vh.Header.Hash(), false) s.tryRequestBlock(requester, vh.Attested.Hash(), false)
} }
// request prefetch head if the given server has announced it // request prefetch head if the given server has announced it
if prefetchHead := s.headTracker.PrefetchHead().BlockRoot; prefetchHead != (common.Hash{}) { if prefetchHead := s.headTracker.PrefetchHead().BlockRoot; prefetchHead != (common.Hash{}) {
@ -113,19 +115,34 @@ func blockHeadInfo(block *types.BeaconBlock) types.HeadInfo {
} }
func (s *beaconBlockSync) updateEventFeed() { func (s *beaconBlockSync) updateEventFeed() {
head, ok := s.headTracker.ValidatedHead() optimistic, ok := s.headTracker.ValidatedOptimistic()
if !ok { if !ok {
return return
} }
finality, ok := s.headTracker.ValidatedFinality() //TODO fetch directly if subscription does not deliver
if !ok || head.Header.Epoch() != finality.Attested.Header.Epoch() { validatedHead := optimistic.Attested.Hash()
return
}
validatedHead := head.Header.Hash()
headBlock, ok := s.recentBlocks.Get(validatedHead) headBlock, ok := s.recentBlocks.Get(validatedHead)
if !ok { if !ok {
return return
} }
var finalizedHash common.Hash
if finality, ok := s.headTracker.ValidatedFinality(); ok {
he := optimistic.Attested.Epoch()
fe := finality.Attested.Header.Epoch()
switch {
case he == fe:
finalizedHash = finality.Finalized.PayloadHeader.BlockHash()
case he < fe:
return
case he == fe+1:
parent, ok := s.recentBlocks.Get(optimistic.Attested.ParentRoot)
if !ok || parent.Slot()/params.EpochLength == fe {
return // head is at first slot of next epoch, wait for finality update
}
}
}
headInfo := blockHeadInfo(headBlock) headInfo := blockHeadInfo(headBlock)
if headInfo == s.lastHeadInfo { if headInfo == s.lastHeadInfo {
return return
@ -139,8 +156,8 @@ func (s *beaconBlockSync) updateEventFeed() {
return return
} }
s.chainHeadFeed.Send(types.ChainHeadEvent{ s.chainHeadFeed.Send(types.ChainHeadEvent{
BeaconHead: head.Header, BeaconHead: optimistic.Attested.Header,
Block: execBlock, Block: execBlock,
Finalized: finality.Finalized.PayloadHeader.BlockHash(), Finalized: finalizedHash,
}) })
} }

View file

@ -28,8 +28,8 @@ import (
) )
var ( var (
testServer1 = "testServer1" testServer1 = testServer("testServer1")
testServer2 = "testServer2" testServer2 = testServer("testServer2")
testBlock1 = types.NewBeaconBlock(&deneb.BeaconBlock{ testBlock1 = types.NewBeaconBlock(&deneb.BeaconBlock{
Slot: 123, Slot: 123,
@ -51,6 +51,12 @@ var (
}) })
) )
type testServer string
func (t testServer) Name() string {
return string(t)
}
func TestBlockSync(t *testing.T) { func TestBlockSync(t *testing.T) {
ht := &testHeadTracker{} ht := &testHeadTracker{}
blockSync := newBeaconBlockSync(ht) blockSync := newBeaconBlockSync(ht)
@ -134,8 +140,12 @@ func (h *testHeadTracker) PrefetchHead() types.HeadInfo {
return h.prefetch return h.prefetch
} }
func (h *testHeadTracker) ValidatedHead() (types.SignedHeader, bool) { func (h *testHeadTracker) ValidatedOptimistic() (types.OptimisticUpdate, bool) {
return h.validated, h.validated.Header != (types.Header{}) return types.OptimisticUpdate{
Attested: types.HeaderWithExecProof{Header: h.validated.Header},
Signature: h.validated.Signature,
SignatureSlot: h.validated.SignatureSlot,
}, h.validated.Header != (types.Header{})
} }
// TODO add test case for finality // TODO add test case for finality

View file

@ -62,6 +62,7 @@ func (ec *engineClient) updateLoop(headCh <-chan types.ChainHeadEvent) {
for { for {
select { select {
case <-ec.rootCtx.Done(): case <-ec.rootCtx.Done():
log.Debug("Stopping engine API update loop")
return return
case event := <-headCh: case event := <-headCh:
@ -73,12 +74,14 @@ func (ec *engineClient) updateLoop(headCh <-chan types.ChainHeadEvent) {
fork := ec.config.ForkAtEpoch(event.BeaconHead.Epoch()) fork := ec.config.ForkAtEpoch(event.BeaconHead.Epoch())
forkName := strings.ToLower(fork.Name) forkName := strings.ToLower(fork.Name)
log.Debug("Calling NewPayload", "number", event.Block.NumberU64(), "hash", event.Block.Hash())
if status, err := ec.callNewPayload(forkName, event); err == nil { if status, err := ec.callNewPayload(forkName, event); err == nil {
log.Info("Successful NewPayload", "number", event.Block.NumberU64(), "hash", event.Block.Hash(), "status", status) log.Info("Successful NewPayload", "number", event.Block.NumberU64(), "hash", event.Block.Hash(), "status", status)
} else { } else {
log.Error("Failed NewPayload", "number", event.Block.NumberU64(), "hash", event.Block.Hash(), "error", err) log.Error("Failed NewPayload", "number", event.Block.NumberU64(), "hash", event.Block.Hash(), "error", err)
} }
log.Debug("Calling ForkchoiceUpdated", "head", event.Block.Hash())
if status, err := ec.callForkchoiceUpdated(forkName, event); err == nil { if status, err := ec.callForkchoiceUpdated(forkName, event); err == nil {
log.Info("Successful ForkchoiceUpdated", "head", event.Block.Hash(), "status", status) log.Info("Successful ForkchoiceUpdated", "head", event.Block.Hash(), "status", status)
} else { } else {

View file

@ -19,6 +19,7 @@ package engine
import ( import (
"fmt" "fmt"
"math/big" "math/big"
"slices"
"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"
@ -132,12 +133,7 @@ func (b PayloadID) Version() PayloadVersion {
// Is returns whether the identifier matches any of provided payload versions. // Is returns whether the identifier matches any of provided payload versions.
func (b PayloadID) Is(versions ...PayloadVersion) bool { func (b PayloadID) Is(versions ...PayloadVersion) bool {
for _, v := range versions { return slices.Contains(versions, b.Version())
if v == b.Version() {
return true
}
}
return false
} }
func (b PayloadID) String() string { func (b PayloadID) String() string {
@ -313,7 +309,7 @@ const (
// ClientVersionV1 contains information which identifies a client implementation. // ClientVersionV1 contains information which identifies a client implementation.
type ClientVersionV1 struct { type ClientVersionV1 struct {
Code string `json:"code"` Code string `json:"code"`
Name string `json:"clientName"` Name string `json:"name"`
Version string `json:"version"` Version string `json:"version"`
Commit string `json:"commit"` Commit string `json:"commit"`
} }

View file

@ -46,13 +46,13 @@ func (s *ApiServer) Subscribe(eventCallback func(event request.Event)) {
log.Debug("New head received", "slot", slot, "blockRoot", blockRoot) log.Debug("New head received", "slot", slot, "blockRoot", blockRoot)
eventCallback(request.Event{Type: sync.EvNewHead, Data: types.HeadInfo{Slot: slot, BlockRoot: blockRoot}}) eventCallback(request.Event{Type: sync.EvNewHead, Data: types.HeadInfo{Slot: slot, BlockRoot: blockRoot}})
}, },
OnSignedHead: func(head types.SignedHeader) { OnOptimistic: func(update types.OptimisticUpdate) {
log.Debug("New signed head received", "slot", head.Header.Slot, "blockRoot", head.Header.Hash(), "signerCount", head.Signature.SignerCount()) log.Debug("New optimistic update received", "slot", update.Attested.Slot, "blockRoot", update.Attested.Hash(), "signerCount", update.Signature.SignerCount())
eventCallback(request.Event{Type: sync.EvNewSignedHead, Data: head}) eventCallback(request.Event{Type: sync.EvNewOptimisticUpdate, Data: update})
}, },
OnFinality: func(head types.FinalityUpdate) { OnFinality: func(update types.FinalityUpdate) {
log.Debug("New finality update received", "slot", head.Attested.Slot, "blockRoot", head.Attested.Hash(), "signerCount", head.Signature.SignerCount()) log.Debug("New finality update received", "slot", update.Attested.Slot, "blockRoot", update.Attested.Hash(), "signerCount", update.Signature.SignerCount())
eventCallback(request.Event{Type: sync.EvNewFinalityUpdate, Data: head}) eventCallback(request.Event{Type: sync.EvNewFinalityUpdate, Data: update})
}, },
OnError: func(err error) { OnError: func(err error) {
log.Warn("Head event stream error", "err", err) log.Warn("Head event stream error", "err", err)
@ -73,14 +73,19 @@ func (s *ApiServer) SendRequest(id request.ID, req request.Request) {
r.Updates, r.Committees, err = s.api.GetBestUpdatesAndCommittees(data.FirstPeriod, data.Count) r.Updates, r.Committees, err = s.api.GetBestUpdatesAndCommittees(data.FirstPeriod, data.Count)
resp = r resp = r
case sync.ReqHeader: case sync.ReqHeader:
var r sync.RespHeader
log.Debug("Beacon API: requesting header", "reqid", id, "hash", common.Hash(data)) log.Debug("Beacon API: requesting header", "reqid", id, "hash", common.Hash(data))
resp, err = s.api.GetHeader(common.Hash(data)) r.Header, r.Canonical, r.Finalized, err = s.api.GetHeader(common.Hash(data))
resp = r
case sync.ReqCheckpointData: case sync.ReqCheckpointData:
log.Debug("Beacon API: requesting checkpoint data", "reqid", id, "hash", common.Hash(data)) log.Debug("Beacon API: requesting checkpoint data", "reqid", id, "hash", common.Hash(data))
resp, err = s.api.GetCheckpointData(common.Hash(data)) resp, err = s.api.GetCheckpointData(common.Hash(data))
case sync.ReqBeaconBlock: case sync.ReqBeaconBlock:
log.Debug("Beacon API: requesting block", "reqid", id, "hash", common.Hash(data)) log.Debug("Beacon API: requesting block", "reqid", id, "hash", common.Hash(data))
resp, err = s.api.GetBeaconBlock(common.Hash(data)) resp, err = s.api.GetBeaconBlock(common.Hash(data))
case sync.ReqFinality:
log.Debug("Beacon API: requesting finality update")
resp, err = s.api.GetFinalityUpdate()
default: default:
} }
@ -88,6 +93,7 @@ func (s *ApiServer) SendRequest(id request.ID, req request.Request) {
log.Warn("Beacon API request failed", "type", reflect.TypeOf(req), "reqid", id, "err", err) log.Warn("Beacon API request failed", "type", reflect.TypeOf(req), "reqid", id, "err", err)
s.eventCallback(request.Event{Type: request.EvFail, Data: request.RequestResponse{ID: id, Request: req}}) s.eventCallback(request.Event{Type: request.EvFail, Data: request.RequestResponse{ID: id, Request: req}})
} else { } else {
log.Debug("Beacon API request answered", "type", reflect.TypeOf(req), "reqid", id)
s.eventCallback(request.Event{Type: request.EvResponse, Data: request.RequestResponse{ID: id, Request: req, Response: resp}}) s.eventCallback(request.Event{Type: request.EvResponse, Data: request.RequestResponse{ID: id, Request: req, Response: resp}})
} }
}() }()
@ -101,3 +107,8 @@ func (s *ApiServer) Unsubscribe() {
s.unsubscribe = nil s.unsubscribe = nil
} }
} }
// Name implements request.Server
func (s *ApiServer) Name() string {
return s.api.url
}

View file

@ -32,6 +32,7 @@ import (
"github.com/ethereum/go-ethereum/beacon/types" "github.com/ethereum/go-ethereum/beacon/types"
"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/log"
) )
var ( var (
@ -184,46 +185,56 @@ func (api *BeaconLightApi) GetBestUpdatesAndCommittees(firstPeriod, count uint64
return updates, committees, nil return updates, committees, nil
} }
// GetOptimisticHeadUpdate fetches a signed header based on the latest available // GetOptimisticUpdate fetches the latest available optimistic update.
// optimistic update. Note that the signature should be verified by the caller // Note that the signature should be verified by the caller as its validity
// as its validity depends on the update chain. // depends on the update chain.
// //
// See data structure definition here: // See data structure definition here:
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientoptimisticupdate // https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientoptimisticupdate
func (api *BeaconLightApi) GetOptimisticHeadUpdate() (types.SignedHeader, error) { func (api *BeaconLightApi) GetOptimisticUpdate() (types.OptimisticUpdate, error) {
resp, err := api.httpGet("/eth/v1/beacon/light_client/optimistic_update") resp, err := api.httpGet("/eth/v1/beacon/light_client/optimistic_update")
if err != nil { if err != nil {
return types.SignedHeader{}, err return types.OptimisticUpdate{}, err
} }
return decodeOptimisticHeadUpdate(resp) return decodeOptimisticUpdate(resp)
} }
func decodeOptimisticHeadUpdate(enc []byte) (types.SignedHeader, error) { func decodeOptimisticUpdate(enc []byte) (types.OptimisticUpdate, error) {
var data struct { var data struct {
Data struct { Version string
Header jsonBeaconHeader `json:"attested_header"` Data struct {
Aggregate types.SyncAggregate `json:"sync_aggregate"` Attested jsonHeaderWithExecProof `json:"attested_header"`
SignatureSlot common.Decimal `json:"signature_slot"` Aggregate types.SyncAggregate `json:"sync_aggregate"`
SignatureSlot common.Decimal `json:"signature_slot"`
} `json:"data"` } `json:"data"`
} }
if err := json.Unmarshal(enc, &data); err != nil { if err := json.Unmarshal(enc, &data); err != nil {
return types.SignedHeader{}, err return types.OptimisticUpdate{}, err
} }
if data.Data.Header.Beacon.StateRoot == (common.Hash{}) { // Decode the execution payload headers.
attestedExecHeader, err := types.ExecutionHeaderFromJSON(data.Version, data.Data.Attested.Execution)
if err != nil {
return types.OptimisticUpdate{}, fmt.Errorf("invalid attested header: %v", err)
}
if data.Data.Attested.Beacon.StateRoot == (common.Hash{}) {
// workaround for different event encoding format in Lodestar // workaround for different event encoding format in Lodestar
if err := json.Unmarshal(enc, &data.Data); err != nil { if err := json.Unmarshal(enc, &data.Data); err != nil {
return types.SignedHeader{}, err return types.OptimisticUpdate{}, err
} }
} }
if len(data.Data.Aggregate.Signers) != params.SyncCommitteeBitmaskSize { if len(data.Data.Aggregate.Signers) != params.SyncCommitteeBitmaskSize {
return types.SignedHeader{}, errors.New("invalid sync_committee_bits length") return types.OptimisticUpdate{}, errors.New("invalid sync_committee_bits length")
} }
if len(data.Data.Aggregate.Signature) != params.BLSSignatureSize { if len(data.Data.Aggregate.Signature) != params.BLSSignatureSize {
return types.SignedHeader{}, errors.New("invalid sync_committee_signature length") return types.OptimisticUpdate{}, errors.New("invalid sync_committee_signature length")
} }
return types.SignedHeader{ return types.OptimisticUpdate{
Header: data.Data.Header.Beacon, Attested: types.HeaderWithExecProof{
Header: data.Data.Attested.Beacon,
PayloadHeader: attestedExecHeader,
PayloadBranch: data.Data.Attested.ExecutionBranch,
},
Signature: data.Data.Aggregate, Signature: data.Data.Aggregate,
SignatureSlot: uint64(data.Data.SignatureSlot), SignatureSlot: uint64(data.Data.SignatureSlot),
}, nil }, nil
@ -289,9 +300,11 @@ func decodeFinalityUpdate(enc []byte) (types.FinalityUpdate, error) {
}, nil }, nil
} }
// GetHead fetches and validates the beacon header with the given blockRoot. // GetHeader fetches and validates the beacon header with the given blockRoot.
// If blockRoot is null hash then the latest head header is fetched. // If blockRoot is null hash then the latest head header is fetched.
func (api *BeaconLightApi) GetHeader(blockRoot common.Hash) (types.Header, error) { // The values of the canonical and finalized flags are also returned. Note that
// these flags are not validated.
func (api *BeaconLightApi) GetHeader(blockRoot common.Hash) (types.Header, bool, bool, error) {
var blockId string var blockId string
if blockRoot == (common.Hash{}) { if blockRoot == (common.Hash{}) {
blockId = "head" blockId = "head"
@ -300,11 +313,12 @@ func (api *BeaconLightApi) GetHeader(blockRoot common.Hash) (types.Header, error
} }
resp, err := api.httpGetf("/eth/v1/beacon/headers/%s", blockId) resp, err := api.httpGetf("/eth/v1/beacon/headers/%s", blockId)
if err != nil { if err != nil {
return types.Header{}, err return types.Header{}, false, false, err
} }
var data struct { var data struct {
Data struct { Finalized bool `json:"finalized"`
Data struct {
Root common.Hash `json:"root"` Root common.Hash `json:"root"`
Canonical bool `json:"canonical"` Canonical bool `json:"canonical"`
Header struct { Header struct {
@ -314,16 +328,16 @@ func (api *BeaconLightApi) GetHeader(blockRoot common.Hash) (types.Header, error
} `json:"data"` } `json:"data"`
} }
if err := json.Unmarshal(resp, &data); err != nil { if err := json.Unmarshal(resp, &data); err != nil {
return types.Header{}, err return types.Header{}, false, false, err
} }
header := data.Data.Header.Message header := data.Data.Header.Message
if blockRoot == (common.Hash{}) { if blockRoot == (common.Hash{}) {
blockRoot = data.Data.Root blockRoot = data.Data.Root
} }
if header.Hash() != blockRoot { if header.Hash() != blockRoot {
return types.Header{}, errors.New("retrieved beacon header root does not match") return types.Header{}, false, false, errors.New("retrieved beacon header root does not match")
} }
return header, nil return header, data.Data.Canonical, data.Finalized, nil
} }
// GetCheckpointData fetches and validates bootstrap data belonging to the given checkpoint. // GetCheckpointData fetches and validates bootstrap data belonging to the given checkpoint.
@ -408,7 +422,7 @@ func decodeHeadEvent(enc []byte) (uint64, common.Hash, error) {
type HeadEventListener struct { type HeadEventListener struct {
OnNewHead func(slot uint64, blockRoot common.Hash) OnNewHead func(slot uint64, blockRoot common.Hash)
OnSignedHead func(head types.SignedHeader) OnOptimistic func(head types.OptimisticUpdate)
OnFinality func(head types.FinalityUpdate) OnFinality func(head types.FinalityUpdate)
OnError func(err error) OnError func(err error)
} }
@ -446,21 +460,35 @@ func (api *BeaconLightApi) StartHeadListener(listener HeadEventListener) func()
defer wg.Done() defer wg.Done()
// Request initial data. // Request initial data.
if head, err := api.GetHeader(common.Hash{}); err == nil { log.Trace("Requesting initial head header")
if head, _, _, err := api.GetHeader(common.Hash{}); err == nil {
log.Trace("Retrieved initial head header", "slot", head.Slot, "hash", head.Hash())
listener.OnNewHead(head.Slot, head.Hash()) listener.OnNewHead(head.Slot, head.Hash())
} else {
log.Debug("Failed to retrieve initial head header", "error", err)
} }
if signedHead, err := api.GetOptimisticHeadUpdate(); err == nil { log.Trace("Requesting initial optimistic update")
listener.OnSignedHead(signedHead) if optimisticUpdate, err := api.GetOptimisticUpdate(); err == nil {
log.Trace("Retrieved initial optimistic update", "slot", optimisticUpdate.Attested.Slot, "hash", optimisticUpdate.Attested.Hash())
listener.OnOptimistic(optimisticUpdate)
} else {
log.Debug("Failed to retrieve initial optimistic update", "error", err)
} }
log.Trace("Requesting initial finality update")
if finalityUpdate, err := api.GetFinalityUpdate(); err == nil { if finalityUpdate, err := api.GetFinalityUpdate(); err == nil {
log.Trace("Retrieved initial finality update", "slot", finalityUpdate.Finalized.Slot, "hash", finalityUpdate.Finalized.Hash())
listener.OnFinality(finalityUpdate) listener.OnFinality(finalityUpdate)
} else {
log.Debug("Failed to retrieve initial finality update", "error", err)
} }
log.Trace("Starting event stream processing loop")
// Receive the stream. // Receive the stream.
var stream *eventsource.Stream var stream *eventsource.Stream
select { select {
case stream = <-streamCh: case stream = <-streamCh:
case <-ctx.Done(): case <-ctx.Done():
log.Trace("Stopping event stream processing loop")
return return
} }
@ -471,8 +499,10 @@ func (api *BeaconLightApi) StartHeadListener(listener HeadEventListener) func()
case event, ok := <-stream.Events: case event, ok := <-stream.Events:
if !ok { if !ok {
log.Trace("Event stream closed")
return return
} }
log.Trace("New event received from event stream", "type", event.Event())
switch event.Event() { switch event.Event() {
case "head": case "head":
slot, blockRoot, err := decodeHeadEvent([]byte(event.Data())) slot, blockRoot, err := decodeHeadEvent([]byte(event.Data()))
@ -482,9 +512,9 @@ func (api *BeaconLightApi) StartHeadListener(listener HeadEventListener) func()
listener.OnError(fmt.Errorf("error decoding head event: %v", err)) listener.OnError(fmt.Errorf("error decoding head event: %v", err))
} }
case "light_client_optimistic_update": case "light_client_optimistic_update":
signedHead, err := decodeOptimisticHeadUpdate([]byte(event.Data())) optimisticUpdate, err := decodeOptimisticUpdate([]byte(event.Data()))
if err == nil { if err == nil {
listener.OnSignedHead(signedHead) listener.OnOptimistic(optimisticUpdate)
} else { } else {
listener.OnError(fmt.Errorf("error decoding optimistic update event: %v", err)) listener.OnError(fmt.Errorf("error decoding optimistic update event: %v", err))
} }
@ -518,7 +548,8 @@ func (api *BeaconLightApi) StartHeadListener(listener HeadEventListener) func()
// established. It can only return nil when the context is canceled. // established. It can only return nil when the context is canceled.
func (api *BeaconLightApi) startEventStream(ctx context.Context, listener *HeadEventListener) *eventsource.Stream { func (api *BeaconLightApi) startEventStream(ctx context.Context, listener *HeadEventListener) *eventsource.Stream {
for retry := true; retry; retry = ctxSleep(ctx, 5*time.Second) { for retry := true; retry; retry = ctxSleep(ctx, 5*time.Second) {
path := "/eth/v1/events?topics=head&topics=light_client_optimistic_update&topics=light_client_finality_update" path := "/eth/v1/events?topics=head&topics=light_client_finality_update&topics=light_client_optimistic_update"
log.Trace("Sending event subscription request")
req, err := http.NewRequestWithContext(ctx, "GET", api.url+path, nil) req, err := http.NewRequestWithContext(ctx, "GET", api.url+path, nil)
if err != nil { if err != nil {
listener.OnError(fmt.Errorf("error creating event subscription request: %v", err)) listener.OnError(fmt.Errorf("error creating event subscription request: %v", err))
@ -532,6 +563,7 @@ func (api *BeaconLightApi) startEventStream(ctx context.Context, listener *HeadE
listener.OnError(fmt.Errorf("error creating event subscription: %v", err)) listener.OnError(fmt.Errorf("error creating event subscription: %v", err))
continue continue
} }
log.Trace("Successfully created event stream")
return stream return stream
} }
return nil return nil

View file

@ -29,15 +29,15 @@ import (
// which is the (not necessarily validated) head announced by the majority of // which is the (not necessarily validated) head announced by the majority of
// servers. // servers.
type HeadTracker struct { type HeadTracker struct {
lock sync.RWMutex lock sync.RWMutex
committeeChain *CommitteeChain committeeChain *CommitteeChain
minSignerCount int minSignerCount int
signedHead types.SignedHeader optimisticUpdate types.OptimisticUpdate
hasSignedHead bool hasOptimisticUpdate bool
finalityUpdate types.FinalityUpdate finalityUpdate types.FinalityUpdate
hasFinalityUpdate bool hasFinalityUpdate bool
prefetchHead types.HeadInfo prefetchHead types.HeadInfo
changeCounter uint64 changeCounter uint64
} }
// NewHeadTracker creates a new HeadTracker. // NewHeadTracker creates a new HeadTracker.
@ -48,15 +48,15 @@ func NewHeadTracker(committeeChain *CommitteeChain, minSignerCount int) *HeadTra
} }
} }
// ValidatedHead returns the latest validated head. // ValidatedOptimistic returns the latest validated optimistic update.
func (h *HeadTracker) ValidatedHead() (types.SignedHeader, bool) { func (h *HeadTracker) ValidatedOptimistic() (types.OptimisticUpdate, bool) {
h.lock.RLock() h.lock.RLock()
defer h.lock.RUnlock() defer h.lock.RUnlock()
return h.signedHead, h.hasSignedHead return h.optimisticUpdate, h.hasOptimisticUpdate
} }
// ValidatedHead returns the latest validated head. // ValidatedFinality returns the latest validated finality update.
func (h *HeadTracker) ValidatedFinality() (types.FinalityUpdate, bool) { func (h *HeadTracker) ValidatedFinality() (types.FinalityUpdate, bool) {
h.lock.RLock() h.lock.RLock()
defer h.lock.RUnlock() defer h.lock.RUnlock()
@ -64,26 +64,36 @@ func (h *HeadTracker) ValidatedFinality() (types.FinalityUpdate, bool) {
return h.finalityUpdate, h.hasFinalityUpdate return h.finalityUpdate, h.hasFinalityUpdate
} }
// Validate validates the given signed head. If the head is successfully validated // ValidateOptimistic validates the given optimistic update. If the update is
// and it is better than the old validated head (higher slot or same slot and more // successfully validated and it is better than the old validated update (higher
// signers) then ValidatedHead is updated. The boolean return flag signals if // slot or same slot and more signers) then ValidatedOptimistic is updated.
// ValidatedHead has been changed. // The boolean return flag signals if ValidatedOptimistic has been changed.
func (h *HeadTracker) ValidateHead(head types.SignedHeader) (bool, error) { func (h *HeadTracker) ValidateOptimistic(update types.OptimisticUpdate) (bool, error) {
h.lock.Lock() h.lock.Lock()
defer h.lock.Unlock() defer h.lock.Unlock()
replace, err := h.validate(head, h.signedHead) if err := update.Validate(); err != nil {
return false, err
}
replace, err := h.validate(update.SignedHeader(), h.optimisticUpdate.SignedHeader())
if replace { if replace {
h.signedHead, h.hasSignedHead = head, true h.optimisticUpdate, h.hasOptimisticUpdate = update, true
h.changeCounter++ h.changeCounter++
} }
return replace, err return replace, err
} }
// ValidateFinality validates the given finality update. If the update is
// successfully validated and it is better than the old validated update (higher
// slot or same slot and more signers) then ValidatedFinality is updated.
// The boolean return flag signals if ValidatedFinality has been changed.
func (h *HeadTracker) ValidateFinality(update types.FinalityUpdate) (bool, error) { func (h *HeadTracker) ValidateFinality(update types.FinalityUpdate) (bool, error) {
h.lock.Lock() h.lock.Lock()
defer h.lock.Unlock() defer h.lock.Unlock()
if err := update.Validate(); err != nil {
return false, err
}
replace, err := h.validate(update.SignedHeader(), h.finalityUpdate.SignedHeader()) replace, err := h.validate(update.SignedHeader(), h.finalityUpdate.SignedHeader())
if replace { if replace {
h.finalityUpdate, h.hasFinalityUpdate = update, true h.finalityUpdate, h.hasFinalityUpdate = update, true
@ -142,6 +152,7 @@ func (h *HeadTracker) SetPrefetchHead(head types.HeadInfo) {
h.changeCounter++ h.changeCounter++
} }
// ChangeCounter implements request.targetData
func (h *HeadTracker) ChangeCounter() uint64 { func (h *HeadTracker) ChangeCounter() uint64 {
h.lock.RLock() h.lock.RLock()
defer h.lock.RUnlock() defer h.lock.RUnlock()

View file

@ -65,7 +65,7 @@ type Requester interface {
// allow new operations. // allow new operations.
type Scheduler struct { type Scheduler struct {
lock sync.Mutex lock sync.Mutex
modules []Module // first has highest priority modules []Module // first has the highest priority
names map[Module]string names map[Module]string
servers map[server]struct{} servers map[server]struct{}
targets map[targetData]uint64 targets map[targetData]uint64
@ -93,7 +93,9 @@ type (
// the modules that do not interact with them directly. // the modules that do not interact with them directly.
// In order to make module testing easier, Server interface is used in // In order to make module testing easier, Server interface is used in
// events and modules. // events and modules.
Server any Server interface {
Name() string
}
Request any Request any
Response any Response any
ID uint64 ID uint64

View file

@ -70,6 +70,10 @@ type testServer struct {
canRequest int canRequest int
} }
func (s *testServer) Name() string {
return ""
}
func (s *testServer) subscribe(eventCb func(Event)) { func (s *testServer) subscribe(eventCb func(Event)) {
s.eventCb = eventCb s.eventCb = eventCb
} }

View file

@ -58,6 +58,7 @@ const (
// EvResponse or EvFail. Additionally, it may also send application-defined // EvResponse or EvFail. Additionally, it may also send application-defined
// events that the Modules can interpret. // events that the Modules can interpret.
type requestServer interface { type requestServer interface {
Name() string
Subscribe(eventCallback func(Event)) Subscribe(eventCallback func(Event))
SendRequest(ID, Request) SendRequest(ID, Request)
Unsubscribe() Unsubscribe()
@ -69,6 +70,7 @@ type requestServer interface {
// limit the number of parallel in-flight requests and temporarily disable // limit the number of parallel in-flight requests and temporarily disable
// new requests based on timeouts and response failures. // new requests based on timeouts and response failures.
type server interface { type server interface {
Server
subscribe(eventCallback func(Event)) subscribe(eventCallback func(Event))
canRequestNow() bool canRequestNow() bool
sendRequest(Request) ID sendRequest(Request) ID
@ -138,6 +140,11 @@ type serverWithTimeout struct {
lastID ID lastID ID
} }
// Name implements request.Server
func (s *serverWithTimeout) Name() string {
return s.parent.Name()
}
// init initializes serverWithTimeout // init initializes serverWithTimeout
func (s *serverWithTimeout) init(clock mclock.Clock) { func (s *serverWithTimeout) init(clock mclock.Clock) {
s.clock = clock s.clock = clock
@ -212,7 +219,7 @@ func (s *serverWithTimeout) startTimeout(reqData RequestResponse) {
}) })
} }
// stop stops all goroutines associated with the server. // unsubscribe stops all goroutines associated with the server.
func (s *serverWithTimeout) unsubscribe() { func (s *serverWithTimeout) unsubscribe() {
s.lock.Lock() s.lock.Lock()
defer s.lock.Unlock() defer s.lock.Unlock()
@ -337,7 +344,7 @@ func (s *serverWithLimits) sendRequest(request Request) (reqId ID) {
return s.serverWithTimeout.sendRequest(request) return s.serverWithTimeout.sendRequest(request)
} }
// stop stops all goroutines associated with the server. // unsubscribe stops all goroutines associated with the server.
func (s *serverWithLimits) unsubscribe() { func (s *serverWithLimits) unsubscribe() {
s.lock.Lock() s.lock.Lock()
defer s.lock.Unlock() defer s.lock.Unlock()

View file

@ -153,6 +153,7 @@ type testRequestServer struct {
eventCb func(Event) eventCb func(Event)
} }
func (rs *testRequestServer) Name() string { return "" }
func (rs *testRequestServer) Subscribe(eventCb func(Event)) { rs.eventCb = eventCb } func (rs *testRequestServer) Subscribe(eventCb func(Event)) { rs.eventCb = eventCb }
func (rs *testRequestServer) SendRequest(ID, Request) {} func (rs *testRequestServer) SendRequest(ID, Request) {}
func (rs *testRequestServer) Unsubscribe() {} func (rs *testRequestServer) Unsubscribe() {}

View file

@ -19,11 +19,13 @@ package sync
import ( import (
"github.com/ethereum/go-ethereum/beacon/light/request" "github.com/ethereum/go-ethereum/beacon/light/request"
"github.com/ethereum/go-ethereum/beacon/types" "github.com/ethereum/go-ethereum/beacon/types"
"github.com/ethereum/go-ethereum/log"
) )
type headTracker interface { type headTracker interface {
ValidateHead(head types.SignedHeader) (bool, error) ValidateOptimistic(update types.OptimisticUpdate) (bool, error)
ValidateFinality(head types.FinalityUpdate) (bool, error) ValidateFinality(head types.FinalityUpdate) (bool, error)
ValidatedFinality() (types.FinalityUpdate, bool)
SetPrefetchHead(head types.HeadInfo) SetPrefetchHead(head types.HeadInfo)
} }
@ -33,16 +35,17 @@ type headTracker interface {
// It can also postpone the validation of the latest announced signed head // It can also postpone the validation of the latest announced signed head
// until the committee chain is synced up to at least the required period. // until the committee chain is synced up to at least the required period.
type HeadSync struct { type HeadSync struct {
headTracker headTracker headTracker headTracker
chain committeeChain chain committeeChain
nextSyncPeriod uint64 nextSyncPeriod uint64
chainInit bool chainInit bool
unvalidatedHeads map[request.Server]types.SignedHeader unvalidatedOptimistic map[request.Server]types.OptimisticUpdate
unvalidatedFinality map[request.Server]types.FinalityUpdate unvalidatedFinality map[request.Server]types.FinalityUpdate
serverHeads map[request.Server]types.HeadInfo serverHeads map[request.Server]types.HeadInfo
headServerCount map[types.HeadInfo]headServerCount reqFinalityEpoch map[request.Server]uint64 // next epoch to request finality update
headCounter uint64 headServerCount map[types.HeadInfo]headServerCount
prefetchHead types.HeadInfo headCounter uint64
prefetchHead types.HeadInfo
} }
// headServerCount is associated with most recently seen head infos; it counts // headServerCount is associated with most recently seen head infos; it counts
@ -57,75 +60,98 @@ type headServerCount struct {
// NewHeadSync creates a new HeadSync. // NewHeadSync creates a new HeadSync.
func NewHeadSync(headTracker headTracker, chain committeeChain) *HeadSync { func NewHeadSync(headTracker headTracker, chain committeeChain) *HeadSync {
s := &HeadSync{ s := &HeadSync{
headTracker: headTracker, headTracker: headTracker,
chain: chain, chain: chain,
unvalidatedHeads: make(map[request.Server]types.SignedHeader), unvalidatedOptimistic: make(map[request.Server]types.OptimisticUpdate),
unvalidatedFinality: make(map[request.Server]types.FinalityUpdate), unvalidatedFinality: make(map[request.Server]types.FinalityUpdate),
serverHeads: make(map[request.Server]types.HeadInfo), serverHeads: make(map[request.Server]types.HeadInfo),
headServerCount: make(map[types.HeadInfo]headServerCount), headServerCount: make(map[types.HeadInfo]headServerCount),
reqFinalityEpoch: make(map[request.Server]uint64),
} }
return s return s
} }
// Process implements request.Module. // Process implements request.Module.
func (s *HeadSync) Process(requester request.Requester, events []request.Event) { func (s *HeadSync) Process(requester request.Requester, events []request.Event) {
nextPeriod, chainInit := s.chain.NextSyncPeriod()
if nextPeriod != s.nextSyncPeriod || chainInit != s.chainInit {
s.nextSyncPeriod, s.chainInit = nextPeriod, chainInit
s.processUnvalidatedUpdates()
}
for _, event := range events { for _, event := range events {
switch event.Type { switch event.Type {
case EvNewHead: case EvNewHead:
s.setServerHead(event.Server, event.Data.(types.HeadInfo)) s.setServerHead(event.Server, event.Data.(types.HeadInfo))
case EvNewSignedHead: case EvNewOptimisticUpdate:
s.newSignedHead(event.Server, event.Data.(types.SignedHeader)) update := event.Data.(types.OptimisticUpdate)
s.newOptimisticUpdate(event.Server, update)
epoch := update.Attested.Epoch()
if epoch < s.reqFinalityEpoch[event.Server] {
continue
}
if finality, ok := s.headTracker.ValidatedFinality(); ok && finality.Attested.Header.Epoch() >= epoch {
continue
}
requester.Send(event.Server, ReqFinality{})
s.reqFinalityEpoch[event.Server] = epoch + 1
case EvNewFinalityUpdate: case EvNewFinalityUpdate:
s.newFinalityUpdate(event.Server, event.Data.(types.FinalityUpdate)) s.newFinalityUpdate(event.Server, event.Data.(types.FinalityUpdate))
case request.EvResponse:
_, _, resp := event.RequestInfo()
s.newFinalityUpdate(event.Server, resp.(types.FinalityUpdate))
case request.EvUnregistered: case request.EvUnregistered:
s.setServerHead(event.Server, types.HeadInfo{}) s.setServerHead(event.Server, types.HeadInfo{})
delete(s.serverHeads, event.Server) delete(s.serverHeads, event.Server)
delete(s.unvalidatedHeads, event.Server) delete(s.unvalidatedOptimistic, event.Server)
delete(s.unvalidatedFinality, event.Server)
} }
} }
nextPeriod, chainInit := s.chain.NextSyncPeriod()
if nextPeriod != s.nextSyncPeriod || chainInit != s.chainInit {
s.nextSyncPeriod, s.chainInit = nextPeriod, chainInit
s.processUnvalidated()
}
} }
// newSignedHead handles received signed head; either validates it if the chain // newOptimisticUpdate handles received optimistic update; either validates it if
// is properly synced or stores it for further validation. // the chain is properly synced or stores it for further validation.
func (s *HeadSync) newSignedHead(server request.Server, signedHead types.SignedHeader) { func (s *HeadSync) newOptimisticUpdate(server request.Server, optimisticUpdate types.OptimisticUpdate) {
if !s.chainInit || types.SyncPeriod(signedHead.SignatureSlot) > s.nextSyncPeriod { if !s.chainInit || types.SyncPeriod(optimisticUpdate.SignatureSlot) > s.nextSyncPeriod {
s.unvalidatedHeads[server] = signedHead s.unvalidatedOptimistic[server] = optimisticUpdate
return return
} }
s.headTracker.ValidateHead(signedHead) if _, err := s.headTracker.ValidateOptimistic(optimisticUpdate); err != nil {
log.Debug("Error validating optimistic update", "error", err)
}
} }
// newSignedHead handles received signed head; either validates it if the chain // newFinalityUpdate handles received finality update; either validates it if
// is properly synced or stores it for further validation. // the chain is properly synced or stores it for further validation.
func (s *HeadSync) newFinalityUpdate(server request.Server, finalityUpdate types.FinalityUpdate) { func (s *HeadSync) newFinalityUpdate(server request.Server, finalityUpdate types.FinalityUpdate) {
if !s.chainInit || types.SyncPeriod(finalityUpdate.SignatureSlot) > s.nextSyncPeriod { if !s.chainInit || types.SyncPeriod(finalityUpdate.SignatureSlot) > s.nextSyncPeriod {
s.unvalidatedFinality[server] = finalityUpdate s.unvalidatedFinality[server] = finalityUpdate
return return
} }
s.headTracker.ValidateFinality(finalityUpdate) if _, err := s.headTracker.ValidateFinality(finalityUpdate); err != nil {
log.Debug("Error validating finality update", "error", err)
}
} }
// processUnvalidatedHeads iterates the list of unvalidated heads and validates // processUnvalidatedUpdates iterates the list of unvalidated updates and validates
// those which can be validated. // those which can be validated.
func (s *HeadSync) processUnvalidated() { func (s *HeadSync) processUnvalidatedUpdates() {
if !s.chainInit { if !s.chainInit {
return return
} }
for server, signedHead := range s.unvalidatedHeads { for server, optimisticUpdate := range s.unvalidatedOptimistic {
if types.SyncPeriod(signedHead.SignatureSlot) <= s.nextSyncPeriod { if types.SyncPeriod(optimisticUpdate.SignatureSlot) <= s.nextSyncPeriod {
s.headTracker.ValidateHead(signedHead) if _, err := s.headTracker.ValidateOptimistic(optimisticUpdate); err != nil {
delete(s.unvalidatedHeads, server) log.Debug("Error validating deferred optimistic update", "error", err)
}
delete(s.unvalidatedOptimistic, server)
} }
} }
for server, finalityUpdate := range s.unvalidatedFinality { for server, finalityUpdate := range s.unvalidatedFinality {
if types.SyncPeriod(finalityUpdate.SignatureSlot) <= s.nextSyncPeriod { if types.SyncPeriod(finalityUpdate.SignatureSlot) <= s.nextSyncPeriod {
s.headTracker.ValidateFinality(finalityUpdate) if _, err := s.headTracker.ValidateFinality(finalityUpdate); err != nil {
log.Debug("Error validating deferred finality update", "error", err)
}
delete(s.unvalidatedFinality, server) delete(s.unvalidatedFinality, server)
} }
} }

View file

@ -19,15 +19,17 @@ package sync
import ( import (
"testing" "testing"
"github.com/ethereum/go-ethereum/beacon/light/request"
"github.com/ethereum/go-ethereum/beacon/types" "github.com/ethereum/go-ethereum/beacon/types"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
var ( var (
testServer1 = "testServer1" testServer1 = testServer("testServer1")
testServer2 = "testServer2" testServer2 = testServer("testServer2")
testServer3 = "testServer3" testServer3 = testServer("testServer3")
testServer4 = "testServer4" testServer4 = testServer("testServer4")
testServer5 = testServer("testServer5")
testHead0 = types.HeadInfo{} testHead0 = types.HeadInfo{}
testHead1 = types.HeadInfo{Slot: 123, BlockRoot: common.Hash{1}} testHead1 = types.HeadInfo{Slot: 123, BlockRoot: common.Hash{1}}
@ -35,13 +37,27 @@ var (
testHead3 = types.HeadInfo{Slot: 124, BlockRoot: common.Hash{3}} testHead3 = types.HeadInfo{Slot: 124, BlockRoot: common.Hash{3}}
testHead4 = types.HeadInfo{Slot: 125, BlockRoot: common.Hash{4}} testHead4 = types.HeadInfo{Slot: 125, BlockRoot: common.Hash{4}}
testSHead1 = types.SignedHeader{SignatureSlot: 0x0124, Header: types.Header{Slot: 0x0123, StateRoot: common.Hash{1}}} testOptUpdate1 = types.OptimisticUpdate{SignatureSlot: 0x0124, Attested: types.HeaderWithExecProof{Header: types.Header{Slot: 0x0123, StateRoot: common.Hash{1}}}}
testSHead2 = types.SignedHeader{SignatureSlot: 0x2010, Header: types.Header{Slot: 0x200e, StateRoot: common.Hash{2}}} testOptUpdate2 = types.OptimisticUpdate{SignatureSlot: 0x2010, Attested: types.HeaderWithExecProof{Header: types.Header{Slot: 0x200e, StateRoot: common.Hash{2}}}}
// testSHead3 is at the end of period 1 but signed in period 2 // testOptUpdate3 is at the end of period 1 but signed in period 2
testSHead3 = types.SignedHeader{SignatureSlot: 0x4000, Header: types.Header{Slot: 0x3fff, StateRoot: common.Hash{3}}} testOptUpdate3 = types.OptimisticUpdate{SignatureSlot: 0x4000, Attested: types.HeaderWithExecProof{Header: types.Header{Slot: 0x3fff, StateRoot: common.Hash{3}}}}
testSHead4 = types.SignedHeader{SignatureSlot: 0x6444, Header: types.Header{Slot: 0x6443, StateRoot: common.Hash{4}}} testOptUpdate4 = types.OptimisticUpdate{SignatureSlot: 0x6444, Attested: types.HeaderWithExecProof{Header: types.Header{Slot: 0x6443, StateRoot: common.Hash{4}}}}
) )
func finality(opt types.OptimisticUpdate) types.FinalityUpdate {
return types.FinalityUpdate{
SignatureSlot: opt.SignatureSlot,
Attested: opt.Attested,
Finalized: types.HeaderWithExecProof{Header: types.Header{Slot: (opt.Attested.Header.Slot - 64) & uint64(0xffffffffffffffe0)}},
}
}
type testServer string
func (t testServer) Name() string {
return string(t)
}
func TestValidatedHead(t *testing.T) { func TestValidatedHead(t *testing.T) {
chain := &TestCommitteeChain{} chain := &TestCommitteeChain{}
ht := &TestHeadTracker{} ht := &TestHeadTracker{}
@ -51,50 +67,66 @@ func TestValidatedHead(t *testing.T) {
ht.ExpValidated(t, 0, nil) ht.ExpValidated(t, 0, nil)
ts.AddServer(testServer1, 1) ts.AddServer(testServer1, 1)
ts.ServerEvent(EvNewSignedHead, testServer1, testSHead1) ts.ServerEvent(EvNewOptimisticUpdate, testServer1, testOptUpdate1)
ts.Run(1) ts.Run(1, testServer1, ReqFinality{})
// announced head should be queued because of uninitialized chain // announced head should be queued because of uninitialized chain
ht.ExpValidated(t, 1, nil) ht.ExpValidated(t, 1, nil)
chain.SetNextSyncPeriod(0) // initialize chain chain.SetNextSyncPeriod(0) // initialize chain
ts.Run(2) ts.Run(2)
// expect previously queued head to be validated // expect previously queued head to be validated
ht.ExpValidated(t, 2, []types.SignedHeader{testSHead1}) ht.ExpValidated(t, 2, []types.OptimisticUpdate{testOptUpdate1})
chain.SetNextSyncPeriod(1) chain.SetNextSyncPeriod(1)
ts.ServerEvent(EvNewSignedHead, testServer1, testSHead2) ts.ServerEvent(EvNewFinalityUpdate, testServer1, finality(testOptUpdate2))
ts.ServerEvent(EvNewOptimisticUpdate, testServer1, testOptUpdate2)
ts.AddServer(testServer2, 1) ts.AddServer(testServer2, 1)
ts.ServerEvent(EvNewSignedHead, testServer2, testSHead2) ts.ServerEvent(EvNewOptimisticUpdate, testServer2, testOptUpdate2)
ts.Run(3) ts.Run(3)
// expect both head announcements to be validated instantly // expect both head announcements to be validated instantly
ht.ExpValidated(t, 3, []types.SignedHeader{testSHead2, testSHead2}) ht.ExpValidated(t, 3, []types.OptimisticUpdate{testOptUpdate2, testOptUpdate2})
ts.ServerEvent(EvNewSignedHead, testServer1, testSHead3) ts.ServerEvent(EvNewOptimisticUpdate, testServer1, testOptUpdate3)
ts.AddServer(testServer3, 1) ts.AddServer(testServer3, 1)
ts.ServerEvent(EvNewSignedHead, testServer3, testSHead4) ts.ServerEvent(EvNewOptimisticUpdate, testServer3, testOptUpdate4)
ts.Run(4) // finality should be requested from both servers
// future period announced heads should be queued ts.Run(4, testServer1, ReqFinality{}, testServer3, ReqFinality{})
// future period annonced heads should be queued
ht.ExpValidated(t, 4, nil) ht.ExpValidated(t, 4, nil)
chain.SetNextSyncPeriod(2) chain.SetNextSyncPeriod(2)
ts.Run(5) ts.Run(5)
// testSHead3 can be validated now but not testSHead4 // testOptUpdate3 can be validated now but not testOptUpdate4
ht.ExpValidated(t, 5, []types.SignedHeader{testSHead3}) ht.ExpValidated(t, 5, []types.OptimisticUpdate{testOptUpdate3})
ts.AddServer(testServer4, 1)
ts.ServerEvent(EvNewOptimisticUpdate, testServer4, testOptUpdate3)
// new server joined with recent optimistic update but still no finality; should be requested
ts.Run(6, testServer4, ReqFinality{})
ht.ExpValidated(t, 6, []types.OptimisticUpdate{testOptUpdate3})
ts.AddServer(testServer5, 1)
ts.RequestEvent(request.EvResponse, ts.Request(6, 1), finality(testOptUpdate3))
ts.ServerEvent(EvNewOptimisticUpdate, testServer5, testOptUpdate3)
// finality update request answered; new server should not be requested
ts.Run(7)
ht.ExpValidated(t, 7, []types.OptimisticUpdate{testOptUpdate3})
// server 3 disconnected without proving period 3, its announced head should be dropped // server 3 disconnected without proving period 3, its announced head should be dropped
ts.RemoveServer(testServer3) ts.RemoveServer(testServer3)
ts.Run(6) ts.Run(8)
ht.ExpValidated(t, 6, nil) ht.ExpValidated(t, 8, nil)
chain.SetNextSyncPeriod(3) chain.SetNextSyncPeriod(3)
ts.Run(7) ts.Run(9)
// testSHead4 could be validated now but it's not queued by any registered server // testOptUpdate4 could be validated now but it's not queued by any registered server
ht.ExpValidated(t, 7, nil) ht.ExpValidated(t, 9, nil)
ts.ServerEvent(EvNewSignedHead, testServer2, testSHead4) ts.ServerEvent(EvNewFinalityUpdate, testServer2, finality(testOptUpdate4))
ts.Run(8) ts.ServerEvent(EvNewOptimisticUpdate, testServer2, testOptUpdate4)
// now testSHead4 should be validated ts.Run(10)
ht.ExpValidated(t, 8, []types.SignedHeader{testSHead4}) // now testOptUpdate4 should be validated
ht.ExpValidated(t, 10, []types.OptimisticUpdate{testOptUpdate4})
} }
func TestPrefetchHead(t *testing.T) { func TestPrefetchHead(t *testing.T) {

View file

@ -75,7 +75,7 @@ func (ts *TestScheduler) Run(testIndex int, exp ...any) {
if count == 0 { if count == 0 {
continue continue
} }
ts.t.Errorf("Missing %d Server.Fail(s) from server %s in test case #%d", count, server.(string), testIndex) ts.t.Errorf("Missing %d Server.Fail(s) from server %s in test case #%d", count, server.Name(), testIndex)
} }
if !reflect.DeepEqual(ts.sent[testIndex], expReqs) { if !reflect.DeepEqual(ts.sent[testIndex], expReqs) {
@ -104,7 +104,7 @@ func (ts *TestScheduler) Send(server request.Server, req request.Request) reques
func (ts *TestScheduler) Fail(server request.Server, desc string) { func (ts *TestScheduler) Fail(server request.Server, desc string) {
if ts.expFail[server] == 0 { if ts.expFail[server] == 0 {
ts.t.Errorf("Unexpected Fail from server %s in test case #%d: %s", server.(string), ts.testIndex, desc) ts.t.Errorf("Unexpected Fail from server %s in test case #%d: %s", server.Name(), ts.testIndex, desc)
return return
} }
ts.expFail[server]-- ts.expFail[server]--
@ -212,32 +212,37 @@ func (tc *TestCommitteeChain) ExpNextSyncPeriod(t *testing.T, expNsp uint64) {
type TestHeadTracker struct { type TestHeadTracker struct {
phead types.HeadInfo phead types.HeadInfo
validated []types.SignedHeader validated []types.OptimisticUpdate
finality types.FinalityUpdate
} }
func (ht *TestHeadTracker) ValidateHead(head types.SignedHeader) (bool, error) { func (ht *TestHeadTracker) ValidateOptimistic(update types.OptimisticUpdate) (bool, error) {
ht.validated = append(ht.validated, head) ht.validated = append(ht.validated, update)
return true, nil return true, nil
} }
// TODO add test case for finality func (ht *TestHeadTracker) ValidateFinality(update types.FinalityUpdate) (bool, error) {
func (ht *TestHeadTracker) ValidateFinality(head types.FinalityUpdate) (bool, error) { ht.finality = update
return true, nil return true, nil
} }
func (ht *TestHeadTracker) ExpValidated(t *testing.T, tci int, expHeads []types.SignedHeader) { func (ht *TestHeadTracker) ValidatedFinality() (types.FinalityUpdate, bool) {
return ht.finality, ht.finality.Attested.Header != (types.Header{})
}
func (ht *TestHeadTracker) ExpValidated(t *testing.T, tci int, expHeads []types.OptimisticUpdate) {
for i, expHead := range expHeads { for i, expHead := range expHeads {
if i >= len(ht.validated) { if i >= len(ht.validated) {
t.Errorf("Missing validated head in test case #%d index #%d (expected {slot %d blockRoot %x}, got none)", tci, i, expHead.Header.Slot, expHead.Header.Hash()) t.Errorf("Missing validated head in test case #%d index #%d (expected {slot %d blockRoot %x}, got none)", tci, i, expHead.Attested.Header.Slot, expHead.Attested.Header.Hash())
continue continue
} }
if ht.validated[i] != expHead { if !reflect.DeepEqual(ht.validated[i], expHead) {
vhead := ht.validated[i].Header vhead := ht.validated[i].Attested.Header
t.Errorf("Wrong validated head in test case #%d index #%d (expected {slot %d blockRoot %x}, got {slot %d blockRoot %x})", tci, i, expHead.Header.Slot, expHead.Header.Hash(), vhead.Slot, vhead.Hash()) t.Errorf("Wrong validated head in test case #%d index #%d (expected {slot %d blockRoot %x}, got {slot %d blockRoot %x})", tci, i, expHead.Attested.Header.Slot, expHead.Attested.Header.Hash(), vhead.Slot, vhead.Hash())
} }
} }
for i := len(expHeads); i < len(ht.validated); i++ { for i := len(expHeads); i < len(ht.validated); i++ {
vhead := ht.validated[i].Header vhead := ht.validated[i].Attested.Header
t.Errorf("Unexpected validated head in test case #%d index #%d (expected none, got {slot %d blockRoot %x})", tci, i, vhead.Slot, vhead.Hash()) t.Errorf("Unexpected validated head in test case #%d index #%d (expected none, got {slot %d blockRoot %x})", tci, i, vhead.Slot, vhead.Hash())
} }
ht.validated = nil ht.validated = nil

View file

@ -23,9 +23,9 @@ import (
) )
var ( var (
EvNewHead = &request.EventType{Name: "newHead"} // data: types.HeadInfo EvNewHead = &request.EventType{Name: "newHead"} // data: types.HeadInfo
EvNewSignedHead = &request.EventType{Name: "newSignedHead"} // data: types.SignedHeader EvNewOptimisticUpdate = &request.EventType{Name: "newOptimisticUpdate"} // data: types.OptimisticUpdate
EvNewFinalityUpdate = &request.EventType{Name: "newFinalityUpdate"} // data: types.FinalityUpdate EvNewFinalityUpdate = &request.EventType{Name: "newFinalityUpdate"} // data: types.FinalityUpdate
) )
type ( type (
@ -36,7 +36,12 @@ type (
Updates []*types.LightClientUpdate Updates []*types.LightClientUpdate
Committees []*types.SerializedSyncCommittee Committees []*types.SerializedSyncCommittee
} }
ReqHeader common.Hash ReqHeader common.Hash
RespHeader struct {
Header types.Header
Canonical, Finalized bool
}
ReqCheckpointData common.Hash ReqCheckpointData common.Hash
ReqBeaconBlock common.Hash ReqBeaconBlock common.Hash
ReqFinality struct{}
) )

View file

@ -21,6 +21,7 @@ import (
"github.com/ethereum/go-ethereum/beacon/light" "github.com/ethereum/go-ethereum/beacon/light"
"github.com/ethereum/go-ethereum/beacon/light/request" "github.com/ethereum/go-ethereum/beacon/light/request"
"github.com/ethereum/go-ethereum/beacon/params"
"github.com/ethereum/go-ethereum/beacon/types" "github.com/ethereum/go-ethereum/beacon/types"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
@ -42,6 +43,31 @@ type CheckpointInit struct {
checkpointHash common.Hash checkpointHash common.Hash
locked request.ServerAndID locked request.ServerAndID
initialized bool initialized bool
// per-server state is used to track the state of requesting checkpoint header
// info. Part of this info (canonical and finalized state) is not validated
// and therefore it is requested from each server separately after it has
// reported a missing checkpoint (which is also not validated info).
serverState map[request.Server]serverState
// the following fields are used to determine whether the checkpoint is on
// epoch boundary. This information is validated and therefore stored globally.
parentHash common.Hash
hasEpochInfo, epochBoundary bool
cpSlot, parentSlot uint64
}
const (
ssDefault = iota // no action yet or checkpoint requested
ssNeedHeader // checkpoint req failed, need cp header
ssHeaderRequested // cp header requested
ssNeedParent // cp header slot %32 != 0, need parent to check epoch boundary
ssParentRequested // cp parent header requested
ssPrintStatus // has all necessary info, print log message if init still not successful
ssDone // log message printed, no more action required
)
type serverState struct {
state int
hasHeader, canonical, finalized bool // stored per server because not validated
} }
// NewCheckpointInit creates a new CheckpointInit. // NewCheckpointInit creates a new CheckpointInit.
@ -49,40 +75,113 @@ func NewCheckpointInit(chain committeeChain, checkpointHash common.Hash) *Checkp
return &CheckpointInit{ return &CheckpointInit{
chain: chain, chain: chain,
checkpointHash: checkpointHash, checkpointHash: checkpointHash,
serverState: make(map[request.Server]serverState),
} }
} }
// Process implements request.Module. // Process implements request.Module.
func (s *CheckpointInit) Process(requester request.Requester, events []request.Event) { func (s *CheckpointInit) Process(requester request.Requester, events []request.Event) {
if s.initialized {
return
}
for _, event := range events { for _, event := range events {
if !event.IsRequestEvent() { switch event.Type {
continue case request.EvResponse, request.EvFail, request.EvTimeout:
} sid, req, resp := event.RequestInfo()
sid, req, resp := event.RequestInfo() if s.locked == sid {
if s.locked == sid { s.locked = request.ServerAndID{}
s.locked = request.ServerAndID{} }
} if event.Type == request.EvTimeout {
if resp != nil { continue
if checkpoint := resp.(*types.BootstrapData); checkpoint.Header.Hash() == common.Hash(req.(ReqCheckpointData)) { }
s.chain.CheckpointInit(*checkpoint) switch s.serverState[sid.Server].state {
s.initialized = true case ssDefault:
return if resp != nil {
if checkpoint := resp.(*types.BootstrapData); checkpoint.Header.Hash() == common.Hash(req.(ReqCheckpointData)) {
s.chain.CheckpointInit(*checkpoint)
s.initialized = true
return
}
requester.Fail(event.Server, "invalid checkpoint data")
}
s.serverState[sid.Server] = serverState{state: ssNeedHeader}
case ssHeaderRequested:
if resp == nil {
s.serverState[sid.Server] = serverState{state: ssPrintStatus}
continue
}
newState := serverState{
hasHeader: true,
canonical: resp.(RespHeader).Canonical,
finalized: resp.(RespHeader).Finalized,
}
s.cpSlot, s.parentHash = resp.(RespHeader).Header.Slot, resp.(RespHeader).Header.ParentRoot
if s.cpSlot%params.EpochLength == 0 {
s.hasEpochInfo, s.epochBoundary = true, true
}
if s.hasEpochInfo {
newState.state = ssPrintStatus
} else {
newState.state = ssNeedParent
}
s.serverState[sid.Server] = newState
case ssParentRequested:
s.parentSlot = resp.(RespHeader).Header.Slot
s.hasEpochInfo, s.epochBoundary = true, s.cpSlot/params.EpochLength > s.parentSlot/params.EpochLength
newState := s.serverState[sid.Server]
newState.state = ssPrintStatus
s.serverState[sid.Server] = newState
} }
requester.Fail(event.Server, "invalid checkpoint data") case request.EvUnregistered:
delete(s.serverState, event.Server)
} }
} }
// start a request if possible // start a request if possible
if s.initialized || s.locked != (request.ServerAndID{}) { for _, server := range requester.CanSendTo() {
return switch s.serverState[server].state {
case ssDefault:
if s.locked == (request.ServerAndID{}) {
id := requester.Send(server, ReqCheckpointData(s.checkpointHash))
s.locked = request.ServerAndID{Server: server, ID: id}
}
case ssNeedHeader:
requester.Send(server, ReqHeader(s.checkpointHash))
newState := s.serverState[server]
newState.state = ssHeaderRequested
s.serverState[server] = newState
case ssNeedParent:
requester.Send(server, ReqHeader(s.parentHash))
newState := s.serverState[server]
newState.state = ssParentRequested
s.serverState[server] = newState
}
} }
cs := requester.CanSendTo()
if len(cs) == 0 { // print log message if necessary
return for server, state := range s.serverState {
if state.state != ssPrintStatus {
continue
}
switch {
case !state.hasHeader:
log.Error("blsync: checkpoint block is not available, reported as unknown", "server", server.Name())
case !state.canonical:
log.Error("blsync: checkpoint block is not available, reported as non-canonical", "server", server.Name())
case !s.hasEpochInfo:
// should be available if hasHeader is true and state is ssPrintStatus
panic("checkpoint epoch info not available when printing retrieval status")
case !s.epochBoundary:
log.Error("blsync: checkpoint block is not first of epoch", "slot", s.cpSlot, "parent", s.parentSlot, "server", server.Name())
case !state.finalized:
log.Error("blsync: checkpoint block is reported as non-finalized", "server", server.Name())
default:
log.Error("blsync: checkpoint not available, but reported as finalized; specified checkpoint hash might be too old", "server", server.Name())
}
s.serverState[server] = serverState{state: ssDone}
} }
server := cs[0]
id := requester.Send(server, ReqCheckpointData(s.checkpointHash))
s.locked = request.ServerAndID{Server: server, ID: id}
} }
// ForwardUpdateSync implements request.Module; it fetches updates between the // ForwardUpdateSync implements request.Module; it fetches updates between the
@ -221,9 +320,9 @@ func (s *ForwardUpdateSync) Process(requester request.Requester, events []reques
if !queued { if !queued {
s.unlockRange(sid, req) s.unlockRange(sid, req)
} }
case EvNewSignedHead: case EvNewOptimisticUpdate:
signedHead := event.Data.(types.SignedHeader) update := event.Data.(types.OptimisticUpdate)
s.nextSyncPeriod[event.Server] = types.SyncPeriod(signedHead.SignatureSlot + 256) s.nextSyncPeriod[event.Server] = types.SyncPeriod(update.SignatureSlot + 256)
case request.EvUnregistered: case request.EvUnregistered:
delete(s.nextSyncPeriod, event.Server) delete(s.nextSyncPeriod, event.Server)
} }

View file

@ -68,9 +68,9 @@ func TestUpdateSyncParallel(t *testing.T) {
ts := NewTestScheduler(t, updateSync) ts := NewTestScheduler(t, updateSync)
// add 2 servers, head at period 100; allow 3-3 parallel requests for each // add 2 servers, head at period 100; allow 3-3 parallel requests for each
ts.AddServer(testServer1, 3) ts.AddServer(testServer1, 3)
ts.ServerEvent(EvNewSignedHead, testServer1, types.SignedHeader{SignatureSlot: 0x2000*100 + 0x1000}) ts.ServerEvent(EvNewOptimisticUpdate, testServer1, types.OptimisticUpdate{SignatureSlot: 0x2000*100 + 0x1000})
ts.AddServer(testServer2, 3) ts.AddServer(testServer2, 3)
ts.ServerEvent(EvNewSignedHead, testServer2, types.SignedHeader{SignatureSlot: 0x2000*100 + 0x1000}) ts.ServerEvent(EvNewOptimisticUpdate, testServer2, types.OptimisticUpdate{SignatureSlot: 0x2000*100 + 0x1000})
// expect 6 requests to be sent // expect 6 requests to be sent
ts.Run(1, ts.Run(1,
@ -150,11 +150,11 @@ func TestUpdateSyncDifferentHeads(t *testing.T) {
ts := NewTestScheduler(t, updateSync) ts := NewTestScheduler(t, updateSync)
// add 3 servers with different announced head periods // add 3 servers with different announced head periods
ts.AddServer(testServer1, 1) ts.AddServer(testServer1, 1)
ts.ServerEvent(EvNewSignedHead, testServer1, types.SignedHeader{SignatureSlot: 0x2000*15 + 0x1000}) ts.ServerEvent(EvNewOptimisticUpdate, testServer1, types.OptimisticUpdate{SignatureSlot: 0x2000*15 + 0x1000})
ts.AddServer(testServer2, 1) ts.AddServer(testServer2, 1)
ts.ServerEvent(EvNewSignedHead, testServer2, types.SignedHeader{SignatureSlot: 0x2000*16 + 0x1000}) ts.ServerEvent(EvNewOptimisticUpdate, testServer2, types.OptimisticUpdate{SignatureSlot: 0x2000*16 + 0x1000})
ts.AddServer(testServer3, 1) ts.AddServer(testServer3, 1)
ts.ServerEvent(EvNewSignedHead, testServer3, types.SignedHeader{SignatureSlot: 0x2000*17 + 0x1000}) ts.ServerEvent(EvNewOptimisticUpdate, testServer3, types.OptimisticUpdate{SignatureSlot: 0x2000*17 + 0x1000})
// expect request to the best announced head // expect request to the best announced head
ts.Run(1, testServer3, ReqUpdates{FirstPeriod: 10, Count: 7}) ts.Run(1, testServer3, ReqUpdates{FirstPeriod: 10, Count: 7})
@ -190,7 +190,7 @@ func TestUpdateSyncDifferentHeads(t *testing.T) {
// a new server is registered with announced head period 17 // a new server is registered with announced head period 17
ts.AddServer(testServer4, 1) ts.AddServer(testServer4, 1)
ts.ServerEvent(EvNewSignedHead, testServer4, types.SignedHeader{SignatureSlot: 0x2000*17 + 0x1000}) ts.ServerEvent(EvNewOptimisticUpdate, testServer4, types.OptimisticUpdate{SignatureSlot: 0x2000*17 + 0x1000})
// expect request to sync one more period // expect request to sync one more period
ts.Run(7, testServer4, ReqUpdates{FirstPeriod: 16, Count: 1}) ts.Run(7, testServer4, ReqUpdates{FirstPeriod: 16, Count: 1})

View file

@ -19,7 +19,9 @@ package types
import ( import (
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"math"
"os" "os"
"slices"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
@ -27,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/beacon/merkle" "github.com/ethereum/go-ethereum/beacon/merkle"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@ -34,6 +37,8 @@ import (
// across signing different data structures. // across signing different data structures.
const syncCommitteeDomain = 7 const syncCommitteeDomain = 7
var knownForks = []string{"GENESIS", "ALTAIR", "BELLATRIX", "CAPELLA", "DENEB"}
// Fork describes a single beacon chain fork and also stores the calculated // Fork describes a single beacon chain fork and also stores the calculated
// signature domain used after this fork. // signature domain used after this fork.
type Fork struct { type Fork struct {
@ -46,6 +51,9 @@ type Fork struct {
// Fork version, see https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#custom-types // Fork version, see https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#custom-types
Version []byte Version []byte
// index in list of known forks or MaxInt if unknown
knownIndex int
// calculated by computeDomain, based on fork version and genesis validators root // calculated by computeDomain, based on fork version and genesis validators root
domain merkle.Value domain merkle.Value
} }
@ -99,9 +107,14 @@ func (f Forks) SigningRoot(header Header) (common.Hash, error) {
return signingRoot, nil return signingRoot, nil
} }
func (f Forks) Len() int { return len(f) } func (f Forks) Len() int { return len(f) }
func (f Forks) Swap(i, j int) { f[i], f[j] = f[j], f[i] } func (f Forks) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
func (f Forks) Less(i, j int) bool { return f[i].Epoch < f[j].Epoch } func (f Forks) Less(i, j int) bool {
if f[i].Epoch != f[j].Epoch {
return f[i].Epoch < f[j].Epoch
}
return f[i].knownIndex < f[j].knownIndex
}
// ChainConfig contains the beacon chain configuration. // ChainConfig contains the beacon chain configuration.
type ChainConfig struct { type ChainConfig struct {
@ -122,16 +135,22 @@ func (c *ChainConfig) ForkAtEpoch(epoch uint64) Fork {
// AddFork adds a new item to the list of forks. // AddFork adds a new item to the list of forks.
func (c *ChainConfig) AddFork(name string, epoch uint64, version []byte) *ChainConfig { func (c *ChainConfig) AddFork(name string, epoch uint64, version []byte) *ChainConfig {
knownIndex := slices.Index(knownForks, name)
if knownIndex == -1 {
knownIndex = math.MaxInt // assume that the unknown fork happens after the known ones
if epoch != math.MaxUint64 {
log.Warn("Unknown fork in config.yaml", "fork name", name, "known forks", knownForks)
}
}
fork := &Fork{ fork := &Fork{
Name: name, Name: name,
Epoch: epoch, Epoch: epoch,
Version: version, Version: version,
knownIndex: knownIndex,
} }
fork.computeDomain(c.GenesisValidatorsRoot) fork.computeDomain(c.GenesisValidatorsRoot)
c.Forks = append(c.Forks, fork) c.Forks = append(c.Forks, fork)
sort.Sort(c.Forks) sort.Sort(c.Forks)
return c return c
} }
@ -181,6 +200,5 @@ func (c *ChainConfig) LoadForks(path string) error {
for name := range versions { for name := range versions {
return fmt.Errorf("epoch number missing for fork %q in beacon chain config file", name) return fmt.Errorf("epoch number missing for fork %q in beacon chain config file", name)
} }
sort.Sort(c.Forks)
return nil return nil
} }

View file

@ -36,7 +36,7 @@ type ExecutionHeader struct {
obj headerObject obj headerObject
} }
// HeaderFromJSON decodes an execution header from JSON data provided by // ExecutionHeaderFromJSON decodes an execution header from JSON data provided by
// the beacon chain API. // the beacon chain API.
func ExecutionHeaderFromJSON(forkName string, data []byte) (*ExecutionHeader, error) { func ExecutionHeaderFromJSON(forkName string, data []byte) (*ExecutionHeader, error) {
var obj headerObject var obj headerObject

View file

@ -66,9 +66,8 @@ func convertPayload[T payloadType](payload T, parentRoot *zrntcommon.Root) (*typ
block := types.NewBlockWithHeader(&header) block := types.NewBlockWithHeader(&header)
block = block.WithBody(transactions, nil) block = block.WithBody(transactions, nil)
block = block.WithWithdrawals(withdrawals) block = block.WithWithdrawals(withdrawals)
hash := block.Hash() if hash := block.Hash(); hash != expectedHash {
if hash != expectedHash { return nil, fmt.Errorf("Sanity check failed, payload hash does not match (expected %x, got %x)", expectedHash, hash)
return block, fmt.Errorf("Sanity check failed, payload hash does not match (expected %x, got %x)", expectedHash, hash)
} }
return block, nil return block, nil
} }

View file

@ -23,7 +23,7 @@ import (
"github.com/ethereum/go-ethereum/beacon/merkle" "github.com/ethereum/go-ethereum/beacon/merkle"
"github.com/ethereum/go-ethereum/beacon/params" "github.com/ethereum/go-ethereum/beacon/params"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" ctypes "github.com/ethereum/go-ethereum/core/types"
) )
// HeadInfo represents an unvalidated new head announcement. // HeadInfo represents an unvalidated new head announcement.
@ -142,17 +142,57 @@ func (u UpdateScore) BetterThan(w UpdateScore) bool {
return u.SignerCount > w.SignerCount return u.SignerCount > w.SignerCount
} }
// HeaderWithExecProof contains a beacon header and proves the belonging execution
// payload header with a Merkle proof.
type HeaderWithExecProof struct { type HeaderWithExecProof struct {
Header Header
PayloadHeader *ExecutionHeader PayloadHeader *ExecutionHeader
PayloadBranch merkle.Values PayloadBranch merkle.Values
} }
// Validate verifies the Merkle proof of the execution payload header.
func (h *HeaderWithExecProof) Validate() error { func (h *HeaderWithExecProof) Validate() error {
payloadRoot := h.PayloadHeader.PayloadRoot() return merkle.VerifyProof(h.BodyRoot, params.BodyIndexExecPayload, h.PayloadBranch, h.PayloadHeader.PayloadRoot())
return merkle.VerifyProof(h.BodyRoot, params.BodyIndexExecPayload, h.PayloadBranch, payloadRoot)
} }
// OptimisticUpdate proves sync committee commitment on the attested beacon header.
// It also proves the belonging execution payload header with a Merkle proof.
//
// See data structure definition here:
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientoptimisticupdate
type OptimisticUpdate struct {
Attested HeaderWithExecProof
// Sync committee BLS signature aggregate
Signature SyncAggregate
// Slot in which the signature has been created (newer than Header.Slot,
// determines the signing sync committee)
SignatureSlot uint64
}
// SignedHeader returns the signed attested header of the update.
func (u *OptimisticUpdate) SignedHeader() SignedHeader {
return SignedHeader{
Header: u.Attested.Header,
Signature: u.Signature,
SignatureSlot: u.SignatureSlot,
}
}
// Validate verifies the Merkle proof proving the execution payload header.
// Note that the sync committee signature of the attested header should be
// verified separately by a synced committee chain.
func (u *OptimisticUpdate) Validate() error {
return u.Attested.Validate()
}
// FinalityUpdate proves a finalized beacon header by a sync committee commitment
// on an attested beacon header, referring to the latest finalized header with a
// Merkle proof.
// It also proves the execution payload header belonging to both the attested and
// the finalized beacon header with Merkle proofs.
//
// See data structure definition here:
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientfinalityupdate
type FinalityUpdate struct { type FinalityUpdate struct {
Attested, Finalized HeaderWithExecProof Attested, Finalized HeaderWithExecProof
FinalityBranch merkle.Values FinalityBranch merkle.Values
@ -163,6 +203,7 @@ type FinalityUpdate struct {
SignatureSlot uint64 SignatureSlot uint64
} }
// SignedHeader returns the signed attested header of the update.
func (u *FinalityUpdate) SignedHeader() SignedHeader { func (u *FinalityUpdate) SignedHeader() SignedHeader {
return SignedHeader{ return SignedHeader{
Header: u.Attested.Header, Header: u.Attested.Header,
@ -171,6 +212,10 @@ func (u *FinalityUpdate) SignedHeader() SignedHeader {
} }
} }
// Validate verifies the Merkle proofs proving the finalized beacon header and
// the execution payload headers belonging to the attested and finalized headers.
// Note that the sync committee signature of the attested header should be
// verified separately by a synced committee chain.
func (u *FinalityUpdate) Validate() error { func (u *FinalityUpdate) Validate() error {
if err := u.Attested.Validate(); err != nil { if err := u.Attested.Validate(); err != nil {
return err return err
@ -186,6 +231,6 @@ func (u *FinalityUpdate) Validate() error {
// finalized execution block. // finalized execution block.
type ChainHeadEvent struct { type ChainHeadEvent struct {
BeaconHead Header BeaconHead Header
Block *types.Block Block *ctypes.Block
Finalized common.Hash Finalized common.Hash
} }

View file

@ -5,22 +5,22 @@
# https://github.com/ethereum/execution-spec-tests/releases/download/v2.1.0/ # https://github.com/ethereum/execution-spec-tests/releases/download/v2.1.0/
ca89c76851b0900bfcc3cbb9a26cbece1f3d7c64a3bed38723e914713290df6c fixtures_develop.tar.gz ca89c76851b0900bfcc3cbb9a26cbece1f3d7c64a3bed38723e914713290df6c fixtures_develop.tar.gz
# version:golang 1.22.1 # version:golang 1.22.2
# https://go.dev/dl/ # https://go.dev/dl/
79c9b91d7f109515a25fc3ecdaad125d67e6bdb54f6d4d98580f46799caea321 go1.22.1.src.tar.gz 374ea82b289ec738e968267cac59c7d5ff180f9492250254784b2044e90df5a9 go1.22.2.src.tar.gz
3bc971772f4712fec0364f4bc3de06af22a00a12daab10b6f717fdcd13156cc0 go1.22.1.darwin-amd64.tar.gz 33e7f63077b1c5bce4f1ecadd4d990cf229667c40bfb00686990c950911b7ab7 go1.22.2.darwin-amd64.tar.gz
f6a9cec6b8a002fcc9c0ee24ec04d67f430a52abc3cfd613836986bcc00d8383 go1.22.1.darwin-arm64.tar.gz 660298be38648723e783ba0398e90431de1cb288c637880cdb124f39bd977f0d go1.22.2.darwin-arm64.tar.gz
99f81c10d5a3f8a886faf8fa86aaa2aaf929fbed54a972ae5eec3c5e0bdb961a go1.22.1.freebsd-386.tar.gz efc7162b0cad2f918ac566a923d4701feb29dc9c0ab625157d49b1cbcbba39da go1.22.2.freebsd-386.tar.gz
51c614ddd92ee4a9913a14c39bf80508d9cfba08561f24d2f075fd00f3cfb067 go1.22.1.freebsd-amd64.tar.gz d753428296e6709527e291fd204700a587ffef2c0a472b21aebea11618245929 go1.22.2.freebsd-amd64.tar.gz
8484df36d3d40139eaf0fe5e647b006435d826cc12f9ae72973bf7ec265e0ae4 go1.22.1.linux-386.tar.gz 586d9eb7fe0489ab297ad80dd06414997df487c5cf536c490ffeaa8d8f1807a7 go1.22.2.linux-386.tar.gz
aab8e15785c997ae20f9c88422ee35d962c4562212bb0f879d052a35c8307c7f go1.22.1.linux-amd64.tar.gz 5901c52b7a78002aeff14a21f93e0f064f74ce1360fce51c6ee68cd471216a17 go1.22.2.linux-amd64.tar.gz
e56685a245b6a0c592fc4a55f0b7803af5b3f827aaa29feab1f40e491acf35b8 go1.22.1.linux-arm64.tar.gz 36e720b2d564980c162a48c7e97da2e407dfcc4239e1e58d98082dfa2486a0c1 go1.22.2.linux-arm64.tar.gz
8cb7a90e48c20daed39a6ac8b8a40760030ba5e93c12274c42191d868687c281 go1.22.1.linux-armv6l.tar.gz 9243dfafde06e1efe24d59df6701818e6786b4adfdf1191098050d6d023c5369 go1.22.2.linux-armv6l.tar.gz
ac775e19d93cc1668999b77cfe8c8964abfbc658718feccfe6e0eb87663cd668 go1.22.1.linux-ppc64le.tar.gz 251a8886c5113be6490bdbb955ddee98763b49c9b1bf4c8364c02d3b482dab00 go1.22.2.linux-ppc64le.tar.gz
7bb7dd8e10f95c9a4cc4f6bef44c816a6e7c9e03f56ac6af6efbb082b19b379f go1.22.1.linux-s390x.tar.gz 2b39019481c28c560d65e9811a478ae10e3ef765e0f59af362031d386a71bfef go1.22.2.linux-s390x.tar.gz
0c5ebb7eb39b7884ec99f92b425d4c03a96a72443562aafbf6e7d15c42a3108a go1.22.1.windows-386.zip 651753c06df037020ef4d162c5b273452e9ba976ed17ae39e66ef7ee89d8147e go1.22.2.windows-386.zip
cf9c66a208a106402a527f5b956269ca506cfe535fc388e828d249ea88ed28ba go1.22.1.windows-amd64.zip 8e581cf330f49d3266e936521a2d8263679ef7e2fc2cbbceb85659122d883596 go1.22.2.windows-amd64.zip
85b8511b298c9f4199ecae26afafcc3d46155bac934d43f2357b9224bcaa310f go1.22.1.windows-arm64.zip ddfca5beb9a0c62254266c3090c2555d899bf3e7aa26243e7de3621108f06875 go1.22.2.windows-arm64.zip
# version:golangci 1.55.2 # version:golangci 1.55.2
# https://github.com/golangci/golangci-lint/releases/ # https://github.com/golangci/golangci-lint/releases/

View file

@ -291,8 +291,8 @@ func writeAuthors(files []string) {
} }
} }
// Write sorted list of authors back to the file. // Write sorted list of authors back to the file.
slices.SortFunc(list, func(a, b string) bool { slices.SortFunc(list, func(a, b string) int {
return strings.ToLower(a) < strings.ToLower(b) return strings.Compare(strings.ToLower(a), strings.ToLower(b))
}) })
content := new(bytes.Buffer) content := new(bytes.Buffer)
content.WriteString(authorsFileHeader) content.WriteString(authorsFileHeader)

View file

@ -9,14 +9,14 @@ It enables usecases like the following:
The two main features that are required for this to work well are; The two main features that are required for this to work well are;
1. Rule Implementation: how to create, manage and interpret rules in a flexible but secure manner 1. Rule Implementation: how to create, manage, and interpret rules in a flexible but secure manner
2. Credential managements and credentials; how to provide auto-unlock without exposing keys unnecessarily. 2. Credential management and credentials; how to provide auto-unlock without exposing keys unnecessarily.
The section below deals with both of them The section below deals with both of them
## Rule Implementation ## Rule Implementation
A ruleset file is implemented as a `js` file. Under the hood, the ruleset-engine is a `SignerUI`, implementing the same methods as the `json-rpc` methods A ruleset file is implemented as a `js` file. Under the hood, the ruleset engine is a `SignerUI`, implementing the same methods as the `json-rpc` methods
defined in the UI protocol. Example: defined in the UI protocol. Example:
```js ```js
@ -27,7 +27,7 @@ function asBig(str) {
return new BigNumber(str) return new BigNumber(str)
} }
// Approve transactions to a certain contract if value is below a certain limit // Approve transactions to a certain contract if the value is below a certain limit
function ApproveTx(req) { function ApproveTx(req) {
var limit = big.Newint("0xb1a2bc2ec50000") var limit = big.Newint("0xb1a2bc2ec50000")
var value = asBig(req.transaction.value); var value = asBig(req.transaction.value);
@ -70,7 +70,7 @@ The Otto vm has a few [caveats](https://github.com/robertkrimen/otto):
Additionally, a few more have been added Additionally, a few more have been added
* The rule execution cannot load external javascript files. * The rule execution cannot load external javascript files.
* The only preloaded library is [`bignumber.js`](https://github.com/MikeMcl/bignumber.js) version `2.0.3`. This one is fairly old, and is not aligned with the documentation at the github repository. * The only preloaded library is [`bignumber.js`](https://github.com/MikeMcl/bignumber.js) version `2.0.3`. This one is fairly old, and is not aligned with the documentation at the GitHub repository.
* Each invocation is made in a fresh virtual machine. This means that you cannot store data in global variables between invocations. This is a deliberate choice -- if you want to store data, use the disk-backed `storage`, since rules should not rely on ephemeral data. * Each invocation is made in a fresh virtual machine. This means that you cannot store data in global variables between invocations. This is a deliberate choice -- if you want to store data, use the disk-backed `storage`, since rules should not rely on ephemeral data.
* Javascript API parameters are _always_ an object. This is also a design choice, to ensure that parameters are accessed by _key_ and not by order. This is to prevent mistakes due to missing parameters or parameter changes. * Javascript API parameters are _always_ an object. This is also a design choice, to ensure that parameters are accessed by _key_ and not by order. This is to prevent mistakes due to missing parameters or parameter changes.
* The JS engine has access to `storage` and `console`. * The JS engine has access to `storage` and `console`.
@ -88,8 +88,8 @@ Some security precautions can be made, such as:
##### Security of implementation ##### Security of implementation
The drawbacks of this very flexible solution is that the `signer` needs to contain a javascript engine. This is pretty simple to implement, since it's already The drawback of this very flexible solution is that the `signer` needs to contain a javascript engine. This is pretty simple to implement since it's already
implemented for `geth`. There are no known security vulnerabilities in, nor have we had any security-problems with it so far. implemented for `geth`. There are no known security vulnerabilities in it, nor have we had any security problems with it so far.
The javascript engine would be an added attack surface; but if the validation of `rulesets` is made good (with hash-based attestation), the actual javascript cannot be considered The javascript engine would be an added attack surface; but if the validation of `rulesets` is made good (with hash-based attestation), the actual javascript cannot be considered
an attack surface -- if an attacker can control the ruleset, a much simpler attack would be to implement an "always-approve" rule instead of exploiting the js vm. The only benefit an attack surface -- if an attacker can control the ruleset, a much simpler attack would be to implement an "always-approve" rule instead of exploiting the js vm. The only benefit
@ -105,7 +105,7 @@ It's unclear whether any other DSL could be more secure; since there's always th
## Credential management ## Credential management
The ability to auto-approve transaction means that the signer needs to have necessary credentials to decrypt keyfiles. These passwords are hereafter called `ksp` (keystore pass). The ability to auto-approve transactions means that the signer needs to have the necessary credentials to decrypt keyfiles. These passwords are hereafter called `ksp` (keystore pass).
### Example implementation ### Example implementation
@ -127,8 +127,8 @@ The `vault.dat` would be an encrypted container storing the following informatio
### Security considerations ### Security considerations
This would leave it up to the user to ensure that the `path/to/masterseed` is handled in a secure way. It's difficult to get around this, although one could This would leave it up to the user to ensure that the `path/to/masterseed` is handled securely. It's difficult to get around this, although one could
imagine leveraging OS-level keychains where supported. The setup is however in general similar to how ssh-keys are stored in `.ssh/`. imagine leveraging OS-level keychains where supported. The setup is however, in general, similar to how ssh-keys are stored in `.ssh/`.
# Implementation status # Implementation status
@ -149,7 +149,7 @@ function big(str) {
// Time window: 1 week // Time window: 1 week
var window = 1000* 3600*24*7; var window = 1000* 3600*24*7;
// Limit : 1 ether // Limit: 1 ether
var limit = new BigNumber("1e18"); var limit = new BigNumber("1e18");
function isLimitOk(transaction) { function isLimitOk(transaction) {
@ -163,7 +163,7 @@ function isLimitOk(transaction) {
if (stored != "") { if (stored != "") {
txs = JSON.parse(stored) txs = JSON.parse(stored)
} }
// First, remove all that have passed out of the time-window // First, remove all that has passed out of the time window
var newtxs = txs.filter(function(tx){return tx.tstamp > windowstart}); var newtxs = txs.filter(function(tx){return tx.tstamp > windowstart});
console.log(txs, newtxs.length); console.log(txs, newtxs.length);
@ -174,7 +174,7 @@ function isLimitOk(transaction) {
console.log("ApproveTx > Sum so far", sum); console.log("ApproveTx > Sum so far", sum);
console.log("ApproveTx > Requested", value.toNumber()); console.log("ApproveTx > Requested", value.toNumber());
// Would we exceed weekly limit ? // Would we exceed the weekly limit ?
return sum.plus(value).lt(limit) return sum.plus(value).lt(limit)
} }

View file

@ -102,7 +102,7 @@ func (s *Suite) sendTxs(t *utesting.T, txs []*types.Transaction) error {
} }
} }
return fmt.Errorf("timed out waiting for txs") return errors.New("timed out waiting for txs")
} }
func (s *Suite) sendInvalidTxs(t *utesting.T, txs []*types.Transaction) error { func (s *Suite) sendInvalidTxs(t *utesting.T, txs []*types.Transaction) error {

View file

@ -19,6 +19,7 @@ package v5test
import ( import (
"bytes" "bytes"
"net" "net"
"slices"
"sync" "sync"
"time" "time"
@ -266,7 +267,7 @@ func (s *Suite) TestFindnodeResults(t *utesting.T) {
n := bn.conn.localNode.Node() n := bn.conn.localNode.Node()
expect[n.ID()] = n expect[n.ID()] = n
d := uint(enode.LogDist(n.ID(), s.Dest.ID())) d := uint(enode.LogDist(n.ID(), s.Dest.ID()))
if !containsUint(dists, d) { if !slices.Contains(dists, d) {
dists = append(dists, d) dists = append(dists, d)
} }
} }

View file

@ -252,12 +252,3 @@ func checkRecords(records []*enr.Record) ([]*enode.Node, error) {
} }
return nodes, nil return nodes, nil
} }
func containsUint(ints []uint, x uint) bool {
for i := range ints {
if ints[i] == x {
return true
}
}
return false
}

View file

@ -50,4 +50,4 @@ contains the password.
## JSON ## JSON
In case you need to output the result in a JSON format, you shall by using the `--json` flag. In case you need to output the result in a JSON format, you shall use the `--json` flag.

View file

@ -50,6 +50,10 @@ var (
Name: "trace.returndata", Name: "trace.returndata",
Usage: "Enable return data output in traces", Usage: "Enable return data output in traces",
} }
TraceEnableCallFramesFlag = &cli.BoolFlag{
Name: "trace.callframes",
Usage: "Enable call frames output in traces",
}
OutputBasedir = &cli.StringFlag{ OutputBasedir = &cli.StringFlag{
Name: "output.basedir", Name: "output.basedir",
Usage: "Specifies where output files are placed. Will be created if it does not exist.", Usage: "Specifies where output files are placed. Will be created if it does not exist.",

View file

@ -29,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/consensus/misc/eip1559"
"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/eth/tracers" "github.com/ethereum/go-ethereum/eth/tracers"
@ -101,9 +102,14 @@ func Transition(ctx *cli.Context) error {
if err != nil { if err != nil {
return nil, nil, NewError(ErrorIO, fmt.Errorf("failed creating trace-file: %v", err)) return nil, nil, NewError(ErrorIO, fmt.Errorf("failed creating trace-file: %v", err))
} }
logger := logger.NewJSONLogger(logConfig, traceFile) var l *tracing.Hooks
if ctx.Bool(TraceEnableCallFramesFlag.Name) {
l = logger.NewJSONLoggerWithCallFrames(logConfig, traceFile)
} else {
l = logger.NewJSONLogger(logConfig, traceFile)
}
tracer := &tracers.Tracer{ tracer := &tracers.Tracer{
Hooks: logger, Hooks: l,
// jsonLogger streams out result to file. // jsonLogger streams out result to file.
GetResult: func() (json.RawMessage, error) { return nil, nil }, GetResult: func() (json.RawMessage, error) { return nil, nil },
Stop: func(err error) {}, Stop: func(err error) {},

View file

@ -152,6 +152,7 @@ var stateTransitionCommand = &cli.Command{
t8ntool.TraceEnableMemoryFlag, t8ntool.TraceEnableMemoryFlag,
t8ntool.TraceDisableStackFlag, t8ntool.TraceDisableStackFlag,
t8ntool.TraceEnableReturnDataFlag, t8ntool.TraceEnableReturnDataFlag,
t8ntool.TraceEnableCallFramesFlag,
t8ntool.OutputBasedir, t8ntool.OutputBasedir,
t8ntool.OutputAllocFlag, t8ntool.OutputAllocFlag,
t8ntool.OutputResultFlag, t8ntool.OutputResultFlag,

View file

@ -272,8 +272,17 @@ func runCmd(ctx *cli.Context) error {
output, leftOverGas, stats, err := timedExec(bench, execFunc) output, leftOverGas, stats, err := timedExec(bench, execFunc)
if ctx.Bool(DumpFlag.Name) { if ctx.Bool(DumpFlag.Name) {
statedb.Commit(genesisConfig.Number, true) root, err := statedb.Commit(genesisConfig.Number, true)
fmt.Println(string(statedb.Dump(nil))) if err != nil {
fmt.Printf("Failed to commit changes %v\n", err)
return err
}
dumpdb, err := state.New(root, sdb, nil)
if err != nil {
fmt.Printf("Failed to open statedb %v\n", err)
return err
}
fmt.Println(string(dumpdb.Dump(nil)))
} }
if ctx.Bool(DebugFlag.Name) { if ctx.Bool(DebugFlag.Name) {

View file

@ -375,6 +375,14 @@ func TestT8nTracing(t *testing.T) {
}`}, }`},
expectedTraces: []string{"trace-0-0x88f5fbd1524731a81e49f637aa847543268a5aaf2a6b32a69d2c6d978c45dcfb.json"}, expectedTraces: []string{"trace-0-0x88f5fbd1524731a81e49f637aa847543268a5aaf2a6b32a69d2c6d978c45dcfb.json"},
}, },
{
base: "./testdata/32",
input: t8nInput{
"alloc.json", "txs.json", "env.json", "Merge", "",
},
extraArgs: []string{"--trace", "--trace.callframes"},
expectedTraces: []string{"trace-0-0x47806361c0fa084be3caa18afe8c48156747c01dbdfc1ee11b5aecdbe4fcf23e.jsonl"},
},
} { } {
args := []string{"t8n"} args := []string{"t8n"}
args = append(args, tc.input.get(tc.base)...) args = append(args, tc.input.get(tc.base)...)

1
cmd/evm/testdata/32/README.md vendored Normal file
View file

@ -0,0 +1 @@
This test does some EVM execution, and can be used to test callframes emitted by the tracer when they are enabled.

30
cmd/evm/testdata/32/alloc.json vendored Normal file
View file

@ -0,0 +1,30 @@
{
"0x8a0a19589531694250d570040a0c4b74576919b8": {
"nonce": "0x00",
"balance": "0x0de0b6b3a7640000",
"code": "0x600060006000600060007310000000000000000000000000000000000000015af1600155600060006000600060007310000000000000000000000000000000000000025af16002553d600060003e600051600355",
"storage": {
"0x01": "0x0100",
"0x02": "0x0100",
"0x03": "0x0100"
}
},
"0x1000000000000000000000000000000000000001": {
"nonce": "0x00",
"balance": "0x29a2241af62c0000",
"code": "0x6103e8ff",
"storage": {}
},
"0x1000000000000000000000000000000000000002": {
"nonce": "0x00",
"balance": "0x4563918244f40000",
"code": "0x600060006000600060647310000000000000000000000000000000000000015af1600f0160005260206000fd",
"storage": {}
},
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
"nonce": "0x00",
"balance": "0x6124fee993bc0000",
"code": "0x",
"storage": {}
}
}

12
cmd/evm/testdata/32/env.json vendored Normal file
View file

@ -0,0 +1,12 @@
{
"currentCoinbase": "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
"currentGasLimit": "71794957647893862",
"currentNumber": "1",
"currentTimestamp": "1000",
"currentRandom": "0",
"currentDifficulty": "0",
"blockHashes": {},
"ommers": [],
"currentBaseFee": "7",
"parentUncleHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}

View file

@ -0,0 +1,61 @@
{"from":"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b","to":"0x8a0a19589531694250d570040a0c4b74576919b8","gas":"0x74f18","value":"0x0","type":"CALL"}
{"pc":0,"op":96,"gas":"0x74f18","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":2,"op":96,"gas":"0x74f15","gasCost":"0x3","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":4,"op":96,"gas":"0x74f12","gasCost":"0x3","memSize":0,"stack":["0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":6,"op":96,"gas":"0x74f0f","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":8,"op":96,"gas":"0x74f0c","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":10,"op":115,"gas":"0x74f09","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH20"}
{"pc":31,"op":90,"gas":"0x74f06","gasCost":"0x2","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0","0x1000000000000000000000000000000000000001"],"depth":1,"refund":0,"opName":"GAS"}
{"pc":32,"op":241,"gas":"0x74f04","gasCost":"0x731f1","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0","0x1000000000000000000000000000000000000001","0x74f04"],"depth":1,"refund":0,"opName":"CALL"}
{"from":"0x8a0a19589531694250d570040a0c4b74576919b8","to":"0x1000000000000000000000000000000000000001","gas":"0x727c9","value":"0x0","type":"CALL"}
{"pc":0,"op":97,"gas":"0x727c9","gasCost":"0x3","memSize":0,"stack":[],"depth":2,"refund":0,"opName":"PUSH2"}
{"pc":3,"op":255,"gas":"0x727c6","gasCost":"0x7f58","memSize":0,"stack":["0x3e8"],"depth":2,"refund":0,"opName":"SELFDESTRUCT"}
{"from":"0x1000000000000000000000000000000000000001","to":"0x00000000000000000000000000000000000003e8","gas":"0x0","value":"0x29a2241af62c0000","type":"SELFDESTRUCT"}
{"output":"","gasUsed":"0x0"}
{"output":"","gasUsed":"0x7f5b"}
{"pc":33,"op":96,"gas":"0x6c581","gasCost":"0x3","memSize":0,"stack":["0x1"],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":35,"op":85,"gas":"0x6c57e","gasCost":"0x1388","memSize":0,"stack":["0x1","0x1"],"depth":1,"refund":0,"opName":"SSTORE"}
{"pc":36,"op":96,"gas":"0x6b1f6","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":38,"op":96,"gas":"0x6b1f3","gasCost":"0x3","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":40,"op":96,"gas":"0x6b1f0","gasCost":"0x3","memSize":0,"stack":["0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":42,"op":96,"gas":"0x6b1ed","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":44,"op":96,"gas":"0x6b1ea","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":46,"op":115,"gas":"0x6b1e7","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH20"}
{"pc":67,"op":90,"gas":"0x6b1e4","gasCost":"0x2","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0","0x1000000000000000000000000000000000000002"],"depth":1,"refund":0,"opName":"GAS"}
{"pc":68,"op":241,"gas":"0x6b1e2","gasCost":"0x69744","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0","0x1000000000000000000000000000000000000002","0x6b1e2"],"depth":1,"refund":0,"opName":"CALL"}
{"from":"0x8a0a19589531694250d570040a0c4b74576919b8","to":"0x1000000000000000000000000000000000000002","gas":"0x68d1c","value":"0x0","type":"CALL"}
{"pc":0,"op":96,"gas":"0x68d1c","gasCost":"0x3","memSize":0,"stack":[],"depth":2,"refund":0,"opName":"PUSH1"}
{"pc":2,"op":96,"gas":"0x68d19","gasCost":"0x3","memSize":0,"stack":["0x0"],"depth":2,"refund":0,"opName":"PUSH1"}
{"pc":4,"op":96,"gas":"0x68d16","gasCost":"0x3","memSize":0,"stack":["0x0","0x0"],"depth":2,"refund":0,"opName":"PUSH1"}
{"pc":6,"op":96,"gas":"0x68d13","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0"],"depth":2,"refund":0,"opName":"PUSH1"}
{"pc":8,"op":96,"gas":"0x68d10","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0","0x0"],"depth":2,"refund":0,"opName":"PUSH1"}
{"pc":10,"op":115,"gas":"0x68d0d","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x64"],"depth":2,"refund":0,"opName":"PUSH20"}
{"pc":31,"op":90,"gas":"0x68d0a","gasCost":"0x2","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x64","0x1000000000000000000000000000000000000001"],"depth":2,"refund":0,"opName":"GAS"}
{"pc":32,"op":241,"gas":"0x68d08","gasCost":"0x67363","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x64","0x1000000000000000000000000000000000000001","0x68d08"],"depth":2,"refund":0,"opName":"CALL"}
{"from":"0x1000000000000000000000000000000000000002","to":"0x1000000000000000000000000000000000000001","gas":"0x658d3","value":"0x64","type":"CALL"}
{"pc":0,"op":97,"gas":"0x658d3","gasCost":"0x3","memSize":0,"stack":[],"depth":3,"refund":0,"opName":"PUSH2"}
{"pc":3,"op":255,"gas":"0x658d0","gasCost":"0x1388","memSize":0,"stack":["0x3e8"],"depth":3,"refund":0,"opName":"SELFDESTRUCT"}
{"from":"0x1000000000000000000000000000000000000001","to":"0x00000000000000000000000000000000000003e8","gas":"0x0","value":"0x64","type":"SELFDESTRUCT"}
{"output":"","gasUsed":"0x0"}
{"output":"","gasUsed":"0x138b"}
{"pc":33,"op":96,"gas":"0x65eed","gasCost":"0x3","memSize":0,"stack":["0x1"],"depth":2,"refund":0,"opName":"PUSH1"}
{"pc":35,"op":1,"gas":"0x65eea","gasCost":"0x3","memSize":0,"stack":["0x1","0xf"],"depth":2,"refund":0,"opName":"ADD"}
{"pc":36,"op":96,"gas":"0x65ee7","gasCost":"0x3","memSize":0,"stack":["0x10"],"depth":2,"refund":0,"opName":"PUSH1"}
{"pc":38,"op":82,"gas":"0x65ee4","gasCost":"0x6","memSize":0,"stack":["0x10","0x0"],"depth":2,"refund":0,"opName":"MSTORE"}
{"pc":39,"op":96,"gas":"0x65ede","gasCost":"0x3","memSize":32,"stack":[],"depth":2,"refund":0,"opName":"PUSH1"}
{"pc":41,"op":96,"gas":"0x65edb","gasCost":"0x3","memSize":32,"stack":["0x20"],"depth":2,"refund":0,"opName":"PUSH1"}
{"pc":43,"op":253,"gas":"0x65ed8","gasCost":"0x0","memSize":32,"stack":["0x20","0x0"],"depth":2,"refund":0,"opName":"REVERT"}
{"pc":43,"op":253,"gas":"0x65ed8","gasCost":"0x0","memSize":32,"stack":[],"depth":2,"refund":0,"opName":"REVERT","error":"execution reverted"}
{"output":"0000000000000000000000000000000000000000000000000000000000000010","gasUsed":"0x2e44","error":"execution reverted"}
{"pc":69,"op":96,"gas":"0x67976","gasCost":"0x3","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":71,"op":85,"gas":"0x67973","gasCost":"0x1388","memSize":0,"stack":["0x0","0x2"],"depth":1,"refund":4800,"opName":"SSTORE"}
{"pc":72,"op":61,"gas":"0x665eb","gasCost":"0x2","memSize":0,"stack":[],"depth":1,"refund":4800,"opName":"RETURNDATASIZE"}
{"pc":73,"op":96,"gas":"0x665e9","gasCost":"0x3","memSize":0,"stack":["0x20"],"depth":1,"refund":4800,"opName":"PUSH1"}
{"pc":75,"op":96,"gas":"0x665e6","gasCost":"0x3","memSize":0,"stack":["0x20","0x0"],"depth":1,"refund":4800,"opName":"PUSH1"}
{"pc":77,"op":62,"gas":"0x665e3","gasCost":"0x9","memSize":0,"stack":["0x20","0x0","0x0"],"depth":1,"refund":4800,"opName":"RETURNDATACOPY"}
{"pc":78,"op":96,"gas":"0x665da","gasCost":"0x3","memSize":32,"stack":[],"depth":1,"refund":4800,"opName":"PUSH1"}
{"pc":80,"op":81,"gas":"0x665d7","gasCost":"0x3","memSize":32,"stack":["0x0"],"depth":1,"refund":4800,"opName":"MLOAD"}
{"pc":81,"op":96,"gas":"0x665d4","gasCost":"0x3","memSize":32,"stack":["0x10"],"depth":1,"refund":4800,"opName":"PUSH1"}
{"pc":83,"op":85,"gas":"0x665d1","gasCost":"0x1388","memSize":32,"stack":["0x10","0x3"],"depth":1,"refund":4800,"opName":"SSTORE"}
{"pc":84,"op":0,"gas":"0x65249","gasCost":"0x0","memSize":32,"stack":[],"depth":1,"refund":4800,"opName":"STOP"}
{"output":"","gasUsed":"0xfccf"}

17
cmd/evm/testdata/32/txs.json vendored Normal file
View file

@ -0,0 +1,17 @@
[
{
"type": "0x0",
"chainId": "0x0",
"nonce": "0x0",
"gasPrice": "0xa",
"gas": "0x7a120",
"to": "0x8a0a19589531694250d570040a0c4b74576919b8",
"value": "0x0",
"input": "0x",
"v": "0x1c",
"r": "0x9a207ad45b7fc2aa5f8e72a30487f2b0bc489778e6d022f19036efdf2a922a17",
"s": "0x640d4da05078b5a4aa561f1b4d58176ea828bfa0f88d27d14459c1d789e1a1eb",
"sender": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8"
}
]

View file

@ -48,7 +48,7 @@ func TestAttachWithHeaders(t *testing.T) {
// This is fixed in a follow-up PR. // This is fixed in a follow-up PR.
} }
// TestAttachWithHeaders tests that 'geth db --remotedb' with custom headers works, i.e // TestRemoteDbWithHeaders tests that 'geth db --remotedb' with custom headers works, i.e
// that custom headers are forwarded to the target. // that custom headers are forwarded to the target.
func TestRemoteDbWithHeaders(t *testing.T) { func TestRemoteDbWithHeaders(t *testing.T) {
t.Parallel() t.Parallel()

View file

@ -100,7 +100,7 @@ if one is set. Otherwise it prints the genesis from the datadir.`,
utils.MetricsInfluxDBOrganizationFlag, utils.MetricsInfluxDBOrganizationFlag,
utils.TxLookupLimitFlag, utils.TxLookupLimitFlag,
utils.VMTraceFlag, utils.VMTraceFlag,
utils.VMTraceConfigFlag, utils.VMTraceJsonConfigFlag,
utils.TransactionHistoryFlag, utils.TransactionHistoryFlag,
utils.StateHistoryFlag, utils.StateHistoryFlag,
}, utils.DatabaseFlags), }, utils.DatabaseFlags),

View file

@ -37,7 +37,6 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/eth/catalyst" "github.com/ethereum/go-ethereum/eth/catalyst"
"github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/internal/version" "github.com/ethereum/go-ethereum/internal/version"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
@ -169,7 +168,7 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
} }
// makeFullNode loads geth configuration and creates the Ethereum backend. // makeFullNode loads geth configuration and creates the Ethereum backend.
func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { func makeFullNode(ctx *cli.Context) *node.Node {
stack, cfg := makeConfigNode(ctx) stack, cfg := makeConfigNode(ctx)
if ctx.IsSet(utils.OverrideCancun.Name) { if ctx.IsSet(utils.OverrideCancun.Name) {
v := ctx.Uint64(utils.OverrideCancun.Name) v := ctx.Uint64(utils.OverrideCancun.Name)
@ -238,7 +237,7 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
utils.Fatalf("failed to register catalyst service: %v", err) utils.Fatalf("failed to register catalyst service: %v", err)
} }
} }
return stack, backend return stack
} }
// dumpConfig is the dumpconfig command. // dumpConfig is the dumpconfig command.

View file

@ -70,8 +70,8 @@ JavaScript API. See https://geth.ethereum.org/docs/interacting-with-geth/javascr
func localConsole(ctx *cli.Context) error { func localConsole(ctx *cli.Context) error {
// Create and start the node based on the CLI flags // Create and start the node based on the CLI flags
prepare(ctx) prepare(ctx)
stack, backend := makeFullNode(ctx) stack := makeFullNode(ctx)
startNode(ctx, stack, backend, true) startNode(ctx, stack, true)
defer stack.Close() defer stack.Close()
// Attach to the newly started node and create the JavaScript console. // Attach to the newly started node and create the JavaScript console.

View file

@ -33,7 +33,6 @@ import (
"github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/internal/debug"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics"
@ -138,7 +137,7 @@ var (
utils.DeveloperPeriodFlag, utils.DeveloperPeriodFlag,
utils.VMEnableDebugFlag, utils.VMEnableDebugFlag,
utils.VMTraceFlag, utils.VMTraceFlag,
utils.VMTraceConfigFlag, utils.VMTraceJsonConfigFlag,
utils.NetworkIdFlag, utils.NetworkIdFlag,
utils.EthStatsURLFlag, utils.EthStatsURLFlag,
utils.NoCompactionFlag, utils.NoCompactionFlag,
@ -348,10 +347,10 @@ func geth(ctx *cli.Context) error {
} }
prepare(ctx) prepare(ctx)
stack, backend := makeFullNode(ctx) stack := makeFullNode(ctx)
defer stack.Close() defer stack.Close()
startNode(ctx, stack, backend, false) startNode(ctx, stack, false)
stack.Wait() stack.Wait()
return nil return nil
} }
@ -359,7 +358,7 @@ func geth(ctx *cli.Context) error {
// startNode boots up the system node and all registered protocols, after which // startNode boots up the system node and all registered protocols, after which
// it unlocks any requested accounts, and starts the RPC/IPC interfaces and the // it unlocks any requested accounts, and starts the RPC/IPC interfaces and the
// miner. // miner.
func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend, isConsole bool) { func startNode(ctx *cli.Context, stack *node.Node, isConsole bool) {
debug.Memsize.Add("node", stack) debug.Memsize.Add("node", stack)
// Start up the node itself // Start up the node itself

View file

@ -97,7 +97,7 @@ func testExport(t *testing.T, f string) {
} }
} }
// testDeletion tests if the deletion markers can be exported/imported correctly // TestDeletionExport tests if the deletion markers can be exported/imported correctly
func TestDeletionExport(t *testing.T) { func TestDeletionExport(t *testing.T) {
f := fmt.Sprintf("%v/tempdump", os.TempDir()) f := fmt.Sprintf("%v/tempdump", os.TempDir())
defer func() { defer func() {

View file

@ -544,8 +544,8 @@ var (
Usage: "Name of tracer which should record internal VM operations (costly)", Usage: "Name of tracer which should record internal VM operations (costly)",
Category: flags.VMCategory, Category: flags.VMCategory,
} }
VMTraceConfigFlag = &cli.StringFlag{ VMTraceJsonConfigFlag = &cli.StringFlag{
Name: "vmtrace.config", Name: "vmtrace.jsonconfig",
Usage: "Tracer configuration (JSON)", Usage: "Tracer configuration (JSON)",
Category: flags.VMCategory, Category: flags.VMCategory,
} }
@ -661,7 +661,7 @@ var (
} }
HTTPPathPrefixFlag = &cli.StringFlag{ HTTPPathPrefixFlag = &cli.StringFlag{
Name: "http.rpcprefix", Name: "http.rpcprefix",
Usage: "HTTP path path prefix on which JSON-RPC is served. Use '/' to serve on all paths.", Usage: "HTTP path prefix on which JSON-RPC is served. Use '/' to serve on all paths.",
Value: "", Value: "",
Category: flags.APICategory, Category: flags.APICategory,
} }
@ -1903,12 +1903,12 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
if ctx.IsSet(VMTraceFlag.Name) { if ctx.IsSet(VMTraceFlag.Name) {
if name := ctx.String(VMTraceFlag.Name); name != "" { if name := ctx.String(VMTraceFlag.Name); name != "" {
var config string var config string
if ctx.IsSet(VMTraceConfigFlag.Name) { if ctx.IsSet(VMTraceJsonConfigFlag.Name) {
config = ctx.String(VMTraceConfigFlag.Name) config = ctx.String(VMTraceJsonConfigFlag.Name)
} }
cfg.VMTrace = name cfg.VMTrace = name
cfg.VMTraceConfig = config cfg.VMTraceJsonConfig = config
} }
} }
} }
@ -1959,7 +1959,7 @@ func RegisterFilterAPI(stack *node.Node, backend ethapi.Backend, ethcfg *ethconf
}) })
stack.RegisterAPIs([]rpc.API{{ stack.RegisterAPIs([]rpc.API{{
Namespace: "eth", Namespace: "eth",
Service: filters.NewFilterAPI(filterSystem, false), Service: filters.NewFilterAPI(filterSystem),
}}) }})
return filterSystem return filterSystem
} }
@ -2192,8 +2192,8 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
if ctx.IsSet(VMTraceFlag.Name) { if ctx.IsSet(VMTraceFlag.Name) {
if name := ctx.String(VMTraceFlag.Name); name != "" { if name := ctx.String(VMTraceFlag.Name); name != "" {
var config json.RawMessage var config json.RawMessage
if ctx.IsSet(VMTraceConfigFlag.Name) { if ctx.IsSet(VMTraceJsonConfigFlag.Name) {
config = json.RawMessage(ctx.String(VMTraceConfigFlag.Name)) config = json.RawMessage(ctx.String(VMTraceJsonConfigFlag.Name))
} }
t, err := tracers.LiveDirectory.New(name, config) t, err := tracers.LiveDirectory.New(name, config)
if err != nil { if err != nil {

View file

@ -83,7 +83,7 @@ func TestHistoryImportAndExport(t *testing.T) {
t.Fatalf("unable to initialize chain: %v", err) t.Fatalf("unable to initialize chain: %v", err)
} }
if _, err := chain.InsertChain(blocks); err != nil { if _, err := chain.InsertChain(blocks); err != nil {
t.Fatalf("error insterting chain: %v", err) t.Fatalf("error inserting chain: %v", err)
} }
// Make temp directory for era files. // Make temp directory for era files.

View file

@ -115,9 +115,7 @@ func (c *BasicLRU[K, V]) Peek(key K) (value V, ok bool) {
// Purge empties the cache. // Purge empties the cache.
func (c *BasicLRU[K, V]) Purge() { func (c *BasicLRU[K, V]) Purge() {
c.list.init() c.list.init()
for k := range c.items { clear(c.items)
delete(c.items, k)
}
} }
// Remove drops an item from the cache. Returns true if the key was present in cache. // Remove drops an item from the cache. Returns true if the key was present in cache.
@ -174,7 +172,7 @@ func (l *list[T]) init() {
l.root.prev = &l.root l.root.prev = &l.root
} }
// push adds an element to the front of the list. // pushElem adds an element to the front of the list.
func (l *list[T]) pushElem(e *listElem[T]) { func (l *list[T]) pushElem(e *listElem[T]) {
e.prev = &l.root e.prev = &l.root
e.next = l.root.next e.next = l.root.next

View file

@ -22,7 +22,7 @@ import (
"container/heap" "container/heap"
) )
// Priority queue data structure. // Prque is a priority queue data structure.
type Prque[P cmp.Ordered, V any] struct { type Prque[P cmp.Ordered, V any] struct {
cont *sstack[P, V] cont *sstack[P, V]
} }
@ -32,7 +32,7 @@ func New[P cmp.Ordered, V any](setIndex SetIndexCallback[V]) *Prque[P, V] {
return &Prque[P, V]{newSstack[P, V](setIndex)} return &Prque[P, V]{newSstack[P, V](setIndex)}
} }
// Pushes a value with a given priority into the queue, expanding if necessary. // Push a value with a given priority into the queue, expanding if necessary.
func (p *Prque[P, V]) Push(data V, priority P) { func (p *Prque[P, V]) Push(data V, priority P) {
heap.Push(p.cont, &item[P, V]{data, priority}) heap.Push(p.cont, &item[P, V]{data, priority})
} }
@ -43,14 +43,14 @@ func (p *Prque[P, V]) Peek() (V, P) {
return item.value, item.priority return item.value, item.priority
} }
// Pops the value with the greatest priority off the stack and returns it. // Pop the value with the greatest priority off the stack and returns it.
// Currently no shrinking is done. // Currently no shrinking is done.
func (p *Prque[P, V]) Pop() (V, P) { func (p *Prque[P, V]) Pop() (V, P) {
item := heap.Pop(p.cont).(*item[P, V]) item := heap.Pop(p.cont).(*item[P, V])
return item.value, item.priority return item.value, item.priority
} }
// Pops only the item from the queue, dropping the associated priority value. // PopItem pops only the item from the queue, dropping the associated priority value.
func (p *Prque[P, V]) PopItem() V { func (p *Prque[P, V]) PopItem() V {
return heap.Pop(p.cont).(*item[P, V]).value return heap.Pop(p.cont).(*item[P, V]).value
} }
@ -60,17 +60,17 @@ func (p *Prque[P, V]) Remove(i int) V {
return heap.Remove(p.cont, i).(*item[P, V]).value return heap.Remove(p.cont, i).(*item[P, V]).value
} }
// Checks whether the priority queue is empty. // Empty checks whether the priority queue is empty.
func (p *Prque[P, V]) Empty() bool { func (p *Prque[P, V]) Empty() bool {
return p.cont.Len() == 0 return p.cont.Len() == 0
} }
// Returns the number of element in the priority queue. // Size returns the number of element in the priority queue.
func (p *Prque[P, V]) Size() int { func (p *Prque[P, V]) Size() int {
return p.cont.Len() return p.cont.Len()
} }
// Clears the contents of the priority queue. // Reset clears the contents of the priority queue.
func (p *Prque[P, V]) Reset() { func (p *Prque[P, V]) Reset() {
*p = *New[P, V](p.cont.setIndex) *p = *New[P, V](p.cont.setIndex)
} }

View file

@ -49,7 +49,7 @@ func newSstack[P cmp.Ordered, V any](setIndex SetIndexCallback[V]) *sstack[P, V]
return result return result
} }
// Pushes a value onto the stack, expanding it if necessary. Required by // Push a value onto the stack, expanding it if necessary. Required by
// heap.Interface. // heap.Interface.
func (s *sstack[P, V]) Push(data any) { func (s *sstack[P, V]) Push(data any) {
if s.size == s.capacity { if s.size == s.capacity {
@ -69,7 +69,7 @@ func (s *sstack[P, V]) Push(data any) {
s.size++ s.size++
} }
// Pops a value off the stack and returns it. Currently no shrinking is done. // Pop a value off the stack and returns it. Currently no shrinking is done.
// Required by heap.Interface. // Required by heap.Interface.
func (s *sstack[P, V]) Pop() (res any) { func (s *sstack[P, V]) Pop() (res any) {
s.size-- s.size--
@ -85,18 +85,18 @@ func (s *sstack[P, V]) Pop() (res any) {
return return
} }
// Returns the length of the stack. Required by sort.Interface. // Len returns the length of the stack. Required by sort.Interface.
func (s *sstack[P, V]) Len() int { func (s *sstack[P, V]) Len() int {
return s.size return s.size
} }
// Compares the priority of two elements of the stack (higher is first). // Less compares the priority of two elements of the stack (higher is first).
// Required by sort.Interface. // Required by sort.Interface.
func (s *sstack[P, V]) Less(i, j int) bool { func (s *sstack[P, V]) Less(i, j int) bool {
return s.blocks[i/blockSize][i%blockSize].priority > s.blocks[j/blockSize][j%blockSize].priority return s.blocks[i/blockSize][i%blockSize].priority > s.blocks[j/blockSize][j%blockSize].priority
} }
// Swaps two elements in the stack. Required by sort.Interface. // Swap two elements in the stack. Required by sort.Interface.
func (s *sstack[P, V]) Swap(i, j int) { func (s *sstack[P, V]) Swap(i, j int) {
ib, io, jb, jo := i/blockSize, i%blockSize, j/blockSize, j%blockSize ib, io, jb, jo := i/blockSize, i%blockSize, j/blockSize, j%blockSize
a, b := s.blocks[jb][jo], s.blocks[ib][io] a, b := s.blocks[jb][jo], s.blocks[ib][io]
@ -107,7 +107,7 @@ func (s *sstack[P, V]) Swap(i, j int) {
s.blocks[ib][io], s.blocks[jb][jo] = a, b s.blocks[ib][io], s.blocks[jb][jo] = a, b
} }
// Resets the stack, effectively clearing its contents. // Reset the stack, effectively clearing its contents.
func (s *sstack[P, V]) Reset() { func (s *sstack[P, V]) Reset() {
*s = *newSstack[P, V](s.setIndex) *s = *newSstack[P, V](s.setIndex)
} }

View file

@ -19,6 +19,7 @@ package clique
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"maps"
"slices" "slices"
"time" "time"
@ -108,28 +109,16 @@ func (s *Snapshot) store(db ethdb.Database) error {
// copy creates a deep copy of the snapshot, though not the individual votes. // copy creates a deep copy of the snapshot, though not the individual votes.
func (s *Snapshot) copy() *Snapshot { func (s *Snapshot) copy() *Snapshot {
cpy := &Snapshot{ return &Snapshot{
config: s.config, config: s.config,
sigcache: s.sigcache, sigcache: s.sigcache,
Number: s.Number, Number: s.Number,
Hash: s.Hash, Hash: s.Hash,
Signers: make(map[common.Address]struct{}), Signers: maps.Clone(s.Signers),
Recents: make(map[uint64]common.Address), Recents: maps.Clone(s.Recents),
Votes: make([]*Vote, len(s.Votes)), Votes: slices.Clone(s.Votes),
Tally: make(map[common.Address]Tally), Tally: maps.Clone(s.Tally),
} }
for signer := range s.Signers {
cpy.Signers[signer] = struct{}{}
}
for block, signer := range s.Recents {
cpy.Recents[block] = signer
}
for address, tally := range s.Tally {
cpy.Tally[address] = tally
}
copy(cpy.Votes, s.Votes)
return cpy
} }
// validVote returns whether it makes sense to cast the specified vote in the // validVote returns whether it makes sense to cast the specified vote in the

View file

@ -568,7 +568,7 @@ var (
u256_32 = uint256.NewInt(32) u256_32 = uint256.NewInt(32)
) )
// AccumulateRewards credits the coinbase of the given block with the mining // accumulateRewards credits the coinbase of the given block with the mining
// reward. The total reward consists of the static block reward and rewards for // reward. The total reward consists of the static block reward and rewards for
// included uncles. The coinbase of each uncle block is also rewarded. // included uncles. The coinbase of each uncle block is also rewarded.
func accumulateRewards(config *params.ChainConfig, stateDB *state.StateDB, header *types.Header, uncles []*types.Header) { func accumulateRewards(config *params.ChainConfig, stateDB *state.StateDB, header *types.Header, uncles []*types.Header) {

View file

@ -127,7 +127,7 @@ func (l *lexer) ignore() {
l.start = l.pos l.start = l.pos
} }
// Accepts checks whether the given input matches the next rune // accept checks whether the given input matches the next rune
func (l *lexer) accept(valid string) bool { func (l *lexer) accept(valid string) bool {
if strings.ContainsRune(valid, l.next()) { if strings.ContainsRune(valid, l.next()) {
return true return true

View file

@ -132,7 +132,7 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD
if rbloom != header.Bloom { if rbloom != header.Bloom {
return fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom, rbloom) return fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom, rbloom)
} }
// Tre receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, Rn]])) // The receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, Rn]]))
receiptSha := types.DeriveSha(receipts, trie.NewStackTrie(nil)) receiptSha := types.DeriveSha(receipts, trie.NewStackTrie(nil))
if receiptSha != header.ReceiptHash { if receiptSha != header.ReceiptHash {
return fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha) return fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha)

View file

@ -147,8 +147,11 @@ type CacheConfig struct {
} }
// triedbConfig derives the configures for trie database. // triedbConfig derives the configures for trie database.
func (c *CacheConfig) triedbConfig() *triedb.Config { func (c *CacheConfig) triedbConfig(isVerkle bool) *triedb.Config {
config := &triedb.Config{Preimages: c.Preimages} config := &triedb.Config{
Preimages: c.Preimages,
IsVerkle: isVerkle,
}
if c.StateScheme == rawdb.HashScheme { if c.StateScheme == rawdb.HashScheme {
config.HashDB = &hashdb.Config{ config.HashDB = &hashdb.Config{
CleanCacheSize: c.TrieCleanLimit * 1024 * 1024, CleanCacheSize: c.TrieCleanLimit * 1024 * 1024,
@ -241,6 +244,8 @@ type BlockChain struct {
bodyRLPCache *lru.Cache[common.Hash, rlp.RawValue] bodyRLPCache *lru.Cache[common.Hash, rlp.RawValue]
receiptsCache *lru.Cache[common.Hash, []*types.Receipt] receiptsCache *lru.Cache[common.Hash, []*types.Receipt]
blockCache *lru.Cache[common.Hash, *types.Block] blockCache *lru.Cache[common.Hash, *types.Block]
txLookupLock sync.RWMutex
txLookupCache *lru.Cache[common.Hash, txLookup] txLookupCache *lru.Cache[common.Hash, txLookup]
wg sync.WaitGroup wg sync.WaitGroup
@ -265,7 +270,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
cacheConfig = defaultCacheConfig cacheConfig = defaultCacheConfig
} }
// Open trie database with provided config // Open trie database with provided config
triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig()) triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig(genesis != nil && genesis.IsVerkle()))
// Setup the genesis block, commit the provided genesis specification // Setup the genesis block, commit the provided genesis specification
// to database if the genesis block is not present yet, or load the // to database if the genesis block is not present yet, or load the
@ -408,37 +413,18 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
// it in advance. // it in advance.
bc.engine.VerifyHeader(bc, bc.CurrentHeader()) bc.engine.VerifyHeader(bc, bc.CurrentHeader())
// Check the current state of the block hashes and make sure that we do not have any of the bad blocks in our chain
for hash := range BadHashes {
if header := bc.GetHeaderByHash(hash); header != nil {
// get the canonical block corresponding to the offending header's number
headerByNumber := bc.GetHeaderByNumber(header.Number.Uint64())
// make sure the headerByNumber (if present) is in our current canonical chain
if headerByNumber != nil && headerByNumber.Hash() == header.Hash() {
log.Error("Found bad hash, rewinding chain", "number", header.Number, "hash", header.ParentHash)
if err := bc.SetHead(header.Number.Uint64() - 1); err != nil {
return nil, err
}
log.Error("Chain rewind was successful, resuming normal operation")
}
}
}
if bc.logger != nil && bc.logger.OnBlockchainInit != nil { if bc.logger != nil && bc.logger.OnBlockchainInit != nil {
bc.logger.OnBlockchainInit(chainConfig) bc.logger.OnBlockchainInit(chainConfig)
} }
if bc.logger != nil && bc.logger.OnGenesisBlock != nil { if bc.logger != nil && bc.logger.OnGenesisBlock != nil {
if block := bc.CurrentBlock(); block.Number.Uint64() == 0 { if block := bc.CurrentBlock(); block.Number.Uint64() == 0 {
alloc, err := getGenesisState(bc.db, block.Hash()) alloc, err := getGenesisState(bc.db, block.Hash())
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get genesis state: %w", err) return nil, fmt.Errorf("failed to get genesis state: %w", err)
} }
if alloc == nil { if alloc == nil {
return nil, fmt.Errorf("live blockchain tracer requires genesis alloc to be set") return nil, errors.New("live blockchain tracer requires genesis alloc to be set")
} }
bc.logger.OnGenesisBlock(bc.genesisBlock, alloc) bc.logger.OnGenesisBlock(bc.genesisBlock, alloc)
} }
} }
@ -639,7 +625,7 @@ func (bc *BlockChain) SetSafe(header *types.Header) {
} }
} }
// rewindPathHead implements the logic of rewindHead in the context of hash scheme. // rewindHashHead implements the logic of rewindHead in the context of hash scheme.
func (bc *BlockChain) rewindHashHead(head *types.Header, root common.Hash) (*types.Header, uint64) { func (bc *BlockChain) rewindHashHead(head *types.Header, root common.Hash) (*types.Header, uint64) {
var ( var (
limit uint64 // The oldest block that will be searched for this rewinding limit uint64 // The oldest block that will be searched for this rewinding
@ -1167,6 +1153,10 @@ func (bc *BlockChain) Stop() {
} }
} }
} }
// Allow tracers to clean-up and release resources.
if bc.logger != nil && bc.logger.OnClose != nil {
bc.logger.OnClose()
}
// Close the trie database, release all the held resources as the last step. // Close the trie database, release all the held resources as the last step.
if err := bc.triedb.Close(); err != nil { if err := bc.triedb.Close(); err != nil {
log.Error("Failed to close trie database", "err", err) log.Error("Failed to close trie database", "err", err)
@ -1553,17 +1543,6 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
return nil return nil
} }
// WriteBlockAndSetHead writes the given block and all associated state to the database,
// and applies the block as the new chain head.
func (bc *BlockChain) WriteBlockAndSetHead(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) {
if !bc.chainmu.TryLock() {
return NonStatTy, errChainStopped
}
defer bc.chainmu.Unlock()
return bc.writeBlockAndSetHead(block, receipts, logs, state, emitHeadEvent)
}
// writeBlockAndSetHead is the internal implementation of WriteBlockAndSetHead. // writeBlockAndSetHead is the internal implementation of WriteBlockAndSetHead.
// This function expects the chain mutex to be held. // This function expects the chain mutex to be held.
func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) { func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) {
@ -1772,11 +1751,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
log.Debug("Abort during block processing") log.Debug("Abort during block processing")
break break
} }
// If the header is a banned one, straight out abort
if BadHashes[block.Hash()] {
bc.reportBlock(block, nil, ErrBannedHash)
return it.index, ErrBannedHash
}
// If the block is known (in the middle of the chain), it's a special case for // If the block is known (in the middle of the chain), it's a special case for
// Clique blocks where they can share state among each other, so importing an // Clique blocks where they can share state among each other, so importing an
// older block might complete the state of the subsequent one. In this case, // older block might complete the state of the subsequent one. In this case,
@ -2302,14 +2276,14 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error {
// rewind the canonical chain to a lower point. // rewind the canonical chain to a lower point.
log.Error("Impossible reorg, please file an issue", "oldnum", oldBlock.Number(), "oldhash", oldBlock.Hash(), "oldblocks", len(oldChain), "newnum", newBlock.Number(), "newhash", newBlock.Hash(), "newblocks", len(newChain)) log.Error("Impossible reorg, please file an issue", "oldnum", oldBlock.Number(), "oldhash", oldBlock.Hash(), "oldblocks", len(oldChain), "newnum", newBlock.Number(), "newhash", newBlock.Hash(), "newblocks", len(newChain))
} }
// Reset the tx lookup cache in case to clear stale txlookups. // Acquire the tx-lookup lock before mutation. This step is essential
// This is done before writing any new chain data to avoid the // as the txlookups should be changed atomically, and all subsequent
// weird scenario that canonical chain is changed while the // reads should be blocked until the mutation is complete.
// stale lookups are still cached. bc.txLookupLock.Lock()
bc.txLookupCache.Purge()
// Insert the new chain(except the head block(reverse order)), // Insert the new chain segment in incremental order, from the old
// taking care of the proper incremental order. // to the new. The new chain head (newChain[0]) is not inserted here,
// as it will be handled separately outside of this function
for i := len(newChain) - 1; i >= 1; i-- { for i := len(newChain) - 1; i >= 1; i-- {
// Insert the block in the canonical way, re-writing history // Insert the block in the canonical way, re-writing history
bc.writeHeadBlock(newChain[i]) bc.writeHeadBlock(newChain[i])
@ -2346,6 +2320,11 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error {
if err := indexesBatch.Write(); err != nil { if err := indexesBatch.Write(); err != nil {
log.Crit("Failed to delete useless indexes", "err", err) log.Crit("Failed to delete useless indexes", "err", err)
} }
// Reset the tx lookup cache to clear stale txlookup cache.
bc.txLookupCache.Purge()
// Release the tx-lookup lock after mutation.
bc.txLookupLock.Unlock()
// Send out events for logs from the old canon chain, and 'reborn' // Send out events for logs from the old canon chain, and 'reborn'
// logs from the new canon chain. The number of logs can be very // logs from the new canon chain. The number of logs can be very

View file

@ -170,7 +170,7 @@ func (it *insertIterator) current() *types.Header {
return it.chain[it.index].Header() return it.chain[it.index].Header()
} }
// first returns the first block in the it. // first returns the first block in it.
func (it *insertIterator) first() *types.Block { func (it *insertIterator) first() *types.Block {
return it.chain[0] return it.chain[0]
} }

View file

@ -266,6 +266,9 @@ func (bc *BlockChain) GetAncestor(hash common.Hash, number, ancestor uint64, max
// transaction indexing is already finished. The transaction is not existent // transaction indexing is already finished. The transaction is not existent
// from the node's perspective. // from the node's perspective.
func (bc *BlockChain) GetTransactionLookup(hash common.Hash) (*rawdb.LegacyTxLookupEntry, *types.Transaction, error) { func (bc *BlockChain) GetTransactionLookup(hash common.Hash) (*rawdb.LegacyTxLookupEntry, *types.Transaction, error) {
bc.txLookupLock.RLock()
defer bc.txLookupLock.RUnlock()
// Short circuit if the txlookup already in the cache, retrieve otherwise // Short circuit if the txlookup already in the cache, retrieve otherwise
if item, exist := bc.txLookupCache.Get(hash); exist { if item, exist := bc.txLookupCache.Get(hash); exist {
return item.lookup, item.transaction, nil return item.lookup, item.transaction, nil

View file

@ -22,7 +22,7 @@ package core
import ( import (
"fmt" "fmt"
"math/big" "math/big"
"path" "path/filepath"
"strings" "strings"
"testing" "testing"
"time" "time"
@ -1966,7 +1966,7 @@ func testSetHeadWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme
// Create a temporary persistent database // Create a temporary persistent database
datadir := t.TempDir() datadir := t.TempDir()
ancient := path.Join(datadir, "ancient") ancient := filepath.Join(datadir, "ancient")
db, err := rawdb.Open(rawdb.OpenOptions{ db, err := rawdb.Open(rawdb.OpenOptions{
Directory: datadir, Directory: datadir,

View file

@ -658,107 +658,6 @@ func testReorg(t *testing.T, first, second []int64, td int64, full bool, scheme
} }
} }
// Tests that the insertion functions detect banned hashes.
func TestBadHeaderHashes(t *testing.T) {
testBadHashes(t, false, rawdb.HashScheme)
testBadHashes(t, false, rawdb.PathScheme)
}
func TestBadBlockHashes(t *testing.T) {
testBadHashes(t, true, rawdb.HashScheme)
testBadHashes(t, true, rawdb.PathScheme)
}
func testBadHashes(t *testing.T, full bool, scheme string) {
// Create a pristine chain and database
genDb, _, blockchain, err := newCanonical(ethash.NewFaker(), 0, full, scheme)
if err != nil {
t.Fatalf("failed to create pristine chain: %v", err)
}
defer blockchain.Stop()
// Create a chain, ban a hash and try to import
if full {
blocks := makeBlockChain(blockchain.chainConfig, blockchain.GetBlockByHash(blockchain.CurrentBlock().Hash()), 3, ethash.NewFaker(), genDb, 10)
BadHashes[blocks[2].Header().Hash()] = true
defer func() { delete(BadHashes, blocks[2].Header().Hash()) }()
_, err = blockchain.InsertChain(blocks)
} else {
headers := makeHeaderChain(blockchain.chainConfig, blockchain.CurrentHeader(), 3, ethash.NewFaker(), genDb, 10)
BadHashes[headers[2].Hash()] = true
defer func() { delete(BadHashes, headers[2].Hash()) }()
_, err = blockchain.InsertHeaderChain(headers)
}
if !errors.Is(err, ErrBannedHash) {
t.Errorf("error mismatch: have: %v, want: %v", err, ErrBannedHash)
}
}
// Tests that bad hashes are detected on boot, and the chain rolled back to a
// good state prior to the bad hash.
func TestReorgBadHeaderHashes(t *testing.T) {
testReorgBadHashes(t, false, rawdb.HashScheme)
testReorgBadHashes(t, false, rawdb.PathScheme)
}
func TestReorgBadBlockHashes(t *testing.T) {
testReorgBadHashes(t, true, rawdb.HashScheme)
testReorgBadHashes(t, true, rawdb.PathScheme)
}
func testReorgBadHashes(t *testing.T, full bool, scheme string) {
// Create a pristine chain and database
genDb, gspec, blockchain, err := newCanonical(ethash.NewFaker(), 0, full, scheme)
if err != nil {
t.Fatalf("failed to create pristine chain: %v", err)
}
// Create a chain, import and ban afterwards
headers := makeHeaderChain(blockchain.chainConfig, blockchain.CurrentHeader(), 4, ethash.NewFaker(), genDb, 10)
blocks := makeBlockChain(blockchain.chainConfig, blockchain.GetBlockByHash(blockchain.CurrentBlock().Hash()), 4, ethash.NewFaker(), genDb, 10)
if full {
if _, err = blockchain.InsertChain(blocks); err != nil {
t.Errorf("failed to import blocks: %v", err)
}
if blockchain.CurrentBlock().Hash() != blocks[3].Hash() {
t.Errorf("last block hash mismatch: have: %x, want %x", blockchain.CurrentBlock().Hash(), blocks[3].Header().Hash())
}
BadHashes[blocks[3].Header().Hash()] = true
defer func() { delete(BadHashes, blocks[3].Header().Hash()) }()
} else {
if _, err = blockchain.InsertHeaderChain(headers); err != nil {
t.Errorf("failed to import headers: %v", err)
}
if blockchain.CurrentHeader().Hash() != headers[3].Hash() {
t.Errorf("last header hash mismatch: have: %x, want %x", blockchain.CurrentHeader().Hash(), headers[3].Hash())
}
BadHashes[headers[3].Hash()] = true
defer func() { delete(BadHashes, headers[3].Hash()) }()
}
blockchain.Stop()
// Create a new BlockChain and check that it rolled back the state.
ncm, err := NewBlockChain(blockchain.db, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil)
if err != nil {
t.Fatalf("failed to create new chain manager: %v", err)
}
if full {
if ncm.CurrentBlock().Hash() != blocks[2].Header().Hash() {
t.Errorf("last block hash mismatch: have: %x, want %x", ncm.CurrentBlock().Hash(), blocks[2].Header().Hash())
}
if blocks[2].Header().GasLimit != ncm.GasLimit() {
t.Errorf("last block gasLimit mismatch: have: %d, want %d", ncm.GasLimit(), blocks[2].Header().GasLimit)
}
} else {
if ncm.CurrentHeader().Hash() != headers[2].Hash() {
t.Errorf("last header hash mismatch: have: %x, want %x", ncm.CurrentHeader().Hash(), headers[2].Hash())
}
}
ncm.Stop()
}
// Tests chain insertions in the face of one entity containing an invalid nonce. // Tests chain insertions in the face of one entity containing an invalid nonce.
func TestHeadersInsertNonceError(t *testing.T) { func TestHeadersInsertNonceError(t *testing.T) {
testInsertNonceError(t, false, rawdb.HashScheme) testInsertNonceError(t, false, rawdb.HashScheme)

View file

@ -1,25 +0,0 @@
// Copyright 2015 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 core
import "github.com/ethereum/go-ethereum/common"
// BadHashes represent a set of manually tracked bad hashes (usually hard forks)
var BadHashes = map[common.Hash]bool{
common.HexToHash("05bef30ef572270f654746da22639a7a0c97dd97a7050b9e252391996aaeb689"): true,
common.HexToHash("7d05d08cbc596a2e5e4f13b80a743e53e09221b5323c3a61946b20873e58583f"): true,
}

View file

@ -596,6 +596,9 @@ func (s *MatcherSession) deliverSections(bit uint, sections []uint64, bitsets []
// of the session, any request in-flight need to be responded to! Empty responses // of the session, any request in-flight need to be responded to! Empty responses
// are fine though in that case. // are fine though in that case.
func (s *MatcherSession) Multiplex(batch int, wait time.Duration, mux chan chan *Retrieval) { func (s *MatcherSession) Multiplex(batch int, wait time.Duration, mux chan chan *Retrieval) {
waitTimer := time.NewTimer(wait)
defer waitTimer.Stop()
for { for {
// Allocate a new bloom bit index to retrieve data for, stopping when done // Allocate a new bloom bit index to retrieve data for, stopping when done
bit, ok := s.allocateRetrieval() bit, ok := s.allocateRetrieval()
@ -604,6 +607,7 @@ func (s *MatcherSession) Multiplex(batch int, wait time.Duration, mux chan chan
} }
// Bit allocated, throttle a bit if we're below our batch limit // Bit allocated, throttle a bit if we're below our batch limit
if s.pendingSections(bit) < batch { if s.pendingSections(bit) < batch {
waitTimer.Reset(wait)
select { select {
case <-s.quit: case <-s.quit:
// Session terminating, we can't meaningfully service, abort // Session terminating, we can't meaningfully service, abort
@ -611,7 +615,7 @@ func (s *MatcherSession) Multiplex(batch int, wait time.Duration, mux chan chan
s.deliverSections(bit, []uint64{}, [][]byte{}) s.deliverSections(bit, []uint64{}, [][]byte{})
return return
case <-time.After(wait): case <-waitTimer.C:
// Throttling up, fetch whatever is available // Throttling up, fetch whatever is available
} }
} }

View file

@ -32,6 +32,7 @@ import (
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/triedb" "github.com/ethereum/go-ethereum/triedb"
"github.com/gballet/go-verkle"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
@ -418,6 +419,112 @@ func GenerateChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int,
return db, blocks, receipts return db, blocks, receipts
} }
func GenerateVerkleChain(config *params.ChainConfig, parent *types.Block, engine consensus.Engine, db ethdb.Database, trdb *triedb.Database, n int, gen func(int, *BlockGen)) ([]*types.Block, []types.Receipts, []*verkle.VerkleProof, []verkle.StateDiff) {
if config == nil {
config = params.TestChainConfig
}
proofs := make([]*verkle.VerkleProof, 0, n)
keyvals := make([]verkle.StateDiff, 0, n)
cm := newChainMaker(parent, config, engine)
genblock := func(i int, parent *types.Block, triedb *triedb.Database, statedb *state.StateDB) (*types.Block, types.Receipts) {
b := &BlockGen{i: i, cm: cm, parent: parent, statedb: statedb, engine: engine}
b.header = cm.makeHeader(parent, statedb, b.engine)
// TODO uncomment when proof generation is merged
// Save pre state for proof generation
// preState := statedb.Copy()
// TODO uncomment when the 2935 PR is merged
// if config.IsPrague(b.header.Number, b.header.Time) {
// if !config.IsPrague(b.parent.Number(), b.parent.Time()) {
// Transition case: insert all 256 ancestors
// InsertBlockHashHistoryAtEip2935Fork(statedb, b.header.Number.Uint64()-1, b.header.ParentHash, chainreader)
// } else {
// ProcessParentBlockHash(statedb, b.header.Number.Uint64()-1, b.header.ParentHash)
// }
// }
// Execute any user modifications to the block
if gen != nil {
gen(i, b)
}
body := &types.Body{
Transactions: b.txs,
Uncles: b.uncles,
Withdrawals: b.withdrawals,
}
block, err := b.engine.FinalizeAndAssemble(cm, b.header, statedb, body, b.receipts)
if err != nil {
panic(err)
}
// Write state changes to db
root, err := statedb.Commit(b.header.Number.Uint64(), config.IsEIP158(b.header.Number))
if err != nil {
panic(fmt.Sprintf("state write error: %v", err))
}
if err = triedb.Commit(root, false); err != nil {
panic(fmt.Sprintf("trie write error: %v", err))
}
// TODO uncomment when proof generation is merged
// proofs = append(proofs, block.ExecutionWitness().VerkleProof)
// keyvals = append(keyvals, block.ExecutionWitness().StateDiff)
return block, b.receipts
}
for i := 0; i < n; i++ {
statedb, err := state.New(parent.Root(), state.NewDatabaseWithNodeDB(db, trdb), nil)
if err != nil {
panic(err)
}
block, receipts := genblock(i, parent, trdb, statedb)
// Post-process the receipts.
// Here we assign the final block hash and other info into the receipt.
// In order for DeriveFields to work, the transaction and receipt lists need to be
// of equal length. If AddUncheckedTx or AddUncheckedReceipt are used, there will be
// extra ones, so we just trim the lists here.
receiptsCount := len(receipts)
txs := block.Transactions()
if len(receipts) > len(txs) {
receipts = receipts[:len(txs)]
} else if len(receipts) < len(txs) {
txs = txs[:len(receipts)]
}
var blobGasPrice *big.Int
if block.ExcessBlobGas() != nil {
blobGasPrice = eip4844.CalcBlobFee(*block.ExcessBlobGas())
}
if err := receipts.DeriveFields(config, block.Hash(), block.NumberU64(), block.Time(), block.BaseFee(), blobGasPrice, txs); err != nil {
panic(err)
}
// Re-expand to ensure all receipts are returned.
receipts = receipts[:receiptsCount]
// Advance the chain.
cm.add(block, receipts)
parent = block
}
return cm.chain, cm.receipts, proofs, keyvals
}
func GenerateVerkleChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int, gen func(int, *BlockGen)) (ethdb.Database, []*types.Block, []types.Receipts, []*verkle.VerkleProof, []verkle.StateDiff) {
db := rawdb.NewMemoryDatabase()
cacheConfig := DefaultCacheConfigWithScheme(rawdb.PathScheme)
cacheConfig.SnapshotLimit = 0
triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig(true))
defer triedb.Close()
genesisBlock, err := genesis.Commit(db, triedb)
if err != nil {
panic(err)
}
blocks, receipts, proofs, keyvals := GenerateVerkleChain(genesis.Config, genesisBlock, engine, db, triedb, n, gen)
return db, blocks, receipts, proofs, keyvals
}
func (cm *chainMaker) makeHeader(parent *types.Block, state *state.StateDB, engine consensus.Engine) *types.Header { func (cm *chainMaker) makeHeader(parent *types.Block, state *state.StateDB, engine consensus.Engine) *types.Header {
time := parent.Time() + 10 // block time is fixed at 10 seconds time := parent.Time() + 10 // block time is fixed at 10 seconds
header := &types.Header{ header := &types.Header{
@ -482,7 +589,7 @@ func makeBlockChain(chainConfig *params.ChainConfig, parent *types.Block, n int,
return blocks return blocks
} }
// makeBlockChain creates a deterministic chain of blocks from genesis // makeBlockChainWithGenesis creates a deterministic chain of blocks from genesis
func makeBlockChainWithGenesis(genesis *Genesis, n int, engine consensus.Engine, seed int) (ethdb.Database, []*types.Block) { func makeBlockChainWithGenesis(genesis *Genesis, n int, engine consensus.Engine, seed int) (ethdb.Database, []*types.Block) {
db, blocks, _ := GenerateChainWithGenesis(genesis, engine, n, func(i int, b *BlockGen) { db, blocks, _ := GenerateChainWithGenesis(genesis, engine, n, func(i int, b *BlockGen) {
b.SetCoinbase(common.Address{0: byte(seed), 19: byte(i)}) b.SetCoinbase(common.Address{0: byte(seed), 19: byte(i)})

View file

@ -26,9 +26,6 @@ var (
// ErrKnownBlock is returned when a block to import is already known locally. // ErrKnownBlock is returned when a block to import is already known locally.
ErrKnownBlock = errors.New("block already known") ErrKnownBlock = errors.New("block already known")
// ErrBannedHash is returned if a block to import is on the banned list.
ErrBannedHash = errors.New("banned hash")
// ErrNoGenesis is returned when there is no Genesis Block. // ErrNoGenesis is returned when there is no Genesis Block.
ErrNoGenesis = errors.New("genesis not found in chain") ErrNoGenesis = errors.New("genesis not found in chain")

View file

@ -59,7 +59,7 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
if header.ExcessBlobGas != nil { if header.ExcessBlobGas != nil {
blobBaseFee = eip4844.CalcBlobFee(*header.ExcessBlobGas) blobBaseFee = eip4844.CalcBlobFee(*header.ExcessBlobGas)
} }
if header.Difficulty.Cmp(common.Big0) == 0 { if header.Difficulty.Sign() == 0 {
random = &header.MixDigest random = &header.MixDigest
} }
return vm.BlockContext{ return vm.BlockContext{

View file

@ -19,21 +19,21 @@ var _ = (*genesisSpecMarshaling)(nil)
// MarshalJSON marshals as JSON. // MarshalJSON marshals as JSON.
func (g Genesis) MarshalJSON() ([]byte, error) { func (g Genesis) MarshalJSON() ([]byte, error) {
type Genesis struct { type Genesis struct {
Config *params.ChainConfig `json:"config"` Config *params.ChainConfig `json:"config"`
Nonce math.HexOrDecimal64 `json:"nonce"` Nonce math.HexOrDecimal64 `json:"nonce"`
Timestamp math.HexOrDecimal64 `json:"timestamp"` Timestamp math.HexOrDecimal64 `json:"timestamp"`
ExtraData hexutil.Bytes `json:"extraData"` ExtraData hexutil.Bytes `json:"extraData"`
GasLimit math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"` GasLimit math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"`
Difficulty *math.HexOrDecimal256 `json:"difficulty" gencodec:"required"` Difficulty *math.HexOrDecimal256 `json:"difficulty" gencodec:"required"`
Mixhash common.Hash `json:"mixHash"` Mixhash common.Hash `json:"mixHash"`
Coinbase common.Address `json:"coinbase"` Coinbase common.Address `json:"coinbase"`
Alloc map[common.UnprefixedAddress]types.Account `json:"alloc" gencodec:"required"` Alloc map[common.UnprefixedAddress]types.Account `json:"alloc" gencodec:"required"`
Number math.HexOrDecimal64 `json:"number"` Number math.HexOrDecimal64 `json:"number"`
GasUsed math.HexOrDecimal64 `json:"gasUsed"` GasUsed math.HexOrDecimal64 `json:"gasUsed"`
ParentHash common.Hash `json:"parentHash"` ParentHash common.Hash `json:"parentHash"`
BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"` BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"`
ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"` ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"`
BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"` BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"`
} }
var enc Genesis var enc Genesis
enc.Config = g.Config enc.Config = g.Config
@ -62,21 +62,21 @@ func (g Genesis) MarshalJSON() ([]byte, error) {
// UnmarshalJSON unmarshals from JSON. // UnmarshalJSON unmarshals from JSON.
func (g *Genesis) UnmarshalJSON(input []byte) error { func (g *Genesis) UnmarshalJSON(input []byte) error {
type Genesis struct { type Genesis struct {
Config *params.ChainConfig `json:"config"` Config *params.ChainConfig `json:"config"`
Nonce *math.HexOrDecimal64 `json:"nonce"` Nonce *math.HexOrDecimal64 `json:"nonce"`
Timestamp *math.HexOrDecimal64 `json:"timestamp"` Timestamp *math.HexOrDecimal64 `json:"timestamp"`
ExtraData *hexutil.Bytes `json:"extraData"` ExtraData *hexutil.Bytes `json:"extraData"`
GasLimit *math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"` GasLimit *math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"`
Difficulty *math.HexOrDecimal256 `json:"difficulty" gencodec:"required"` Difficulty *math.HexOrDecimal256 `json:"difficulty" gencodec:"required"`
Mixhash *common.Hash `json:"mixHash"` Mixhash *common.Hash `json:"mixHash"`
Coinbase *common.Address `json:"coinbase"` Coinbase *common.Address `json:"coinbase"`
Alloc map[common.UnprefixedAddress]types.Account `json:"alloc" gencodec:"required"` Alloc map[common.UnprefixedAddress]types.Account `json:"alloc" gencodec:"required"`
Number *math.HexOrDecimal64 `json:"number"` Number *math.HexOrDecimal64 `json:"number"`
GasUsed *math.HexOrDecimal64 `json:"gasUsed"` GasUsed *math.HexOrDecimal64 `json:"gasUsed"`
ParentHash *common.Hash `json:"parentHash"` ParentHash *common.Hash `json:"parentHash"`
BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"` BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"`
ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"` ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"`
BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"` BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"`
} }
var dec Genesis var dec Genesis
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {

View file

@ -582,7 +582,7 @@ func DeveloperGenesisBlock(gasLimit uint64, faucet *common.Address) *Genesis {
Config: &config, Config: &config,
GasLimit: gasLimit, GasLimit: gasLimit,
BaseFee: big.NewInt(params.InitialBaseFee), BaseFee: big.NewInt(params.InitialBaseFee),
Difficulty: big.NewInt(1), Difficulty: big.NewInt(0),
Alloc: map[common.Address]types.Account{ Alloc: map[common.Address]types.Account{
common.BytesToAddress([]byte{1}): {Balance: big.NewInt(1)}, // ECRecover common.BytesToAddress([]byte{1}): {Balance: big.NewInt(1)}, // ECRecover
common.BytesToAddress([]byte{2}): {Balance: big.NewInt(1)}, // SHA256 common.BytesToAddress([]byte{2}): {Balance: big.NewInt(1)}, // SHA256

View file

@ -17,12 +17,9 @@
package core package core
import ( import (
crand "crypto/rand"
"errors" "errors"
"fmt" "fmt"
"math"
"math/big" "math/big"
mrand "math/rand"
"sync/atomic" "sync/atomic"
"time" "time"
@ -43,45 +40,41 @@ const (
numberCacheLimit = 2048 numberCacheLimit = 2048
) )
// HeaderChain implements the basic block header chain logic that is shared by // HeaderChain implements the basic block header chain logic. It is not usable
// core.BlockChain and light.LightChain. It is not usable in itself, only as // in itself, but rather an internal structure of core.Blockchain.
// a part of either structure.
// //
// HeaderChain is responsible for maintaining the header chain including the // HeaderChain is responsible for maintaining the header chain including the
// header query and updating. // header query and updating.
// //
// The components maintained by headerchain includes: (1) total difficulty // The data components maintained by HeaderChain include:
// (2) header (3) block hash -> number mapping (4) canonical number -> hash mapping
// and (5) head header flag.
// //
// It is not thread safe either, the encapsulating chain structures should do // - total difficulty
// the necessary mutex locking/unlocking. // - header
// - block hash -> number mapping
// - canonical number -> hash mapping
// - head header flag.
//
// It is not thread safe, the encapsulating chain structures should do the
// necessary mutex locking/unlocking.
type HeaderChain struct { type HeaderChain struct {
config *params.ChainConfig config *params.ChainConfig
chainDb ethdb.Database chainDb ethdb.Database
genesisHeader *types.Header genesisHeader *types.Header
currentHeader atomic.Value // Current head of the header chain (may be above the block chain!) currentHeader atomic.Pointer[types.Header] // Current head of the header chain (maybe above the block chain!)
currentHeaderHash common.Hash // Hash of the current head of the header chain (prevent recomputing all the time) currentHeaderHash common.Hash // Hash of the current head of the header chain (prevent recomputing all the time)
headerCache *lru.Cache[common.Hash, *types.Header] headerCache *lru.Cache[common.Hash, *types.Header]
tdCache *lru.Cache[common.Hash, *big.Int] // most recent total difficulties tdCache *lru.Cache[common.Hash, *big.Int] // most recent total difficulties
numberCache *lru.Cache[common.Hash, uint64] // most recent block numbers numberCache *lru.Cache[common.Hash, uint64] // most recent block numbers
procInterrupt func() bool procInterrupt func() bool
engine consensus.Engine
rand *mrand.Rand
engine consensus.Engine
} }
// NewHeaderChain creates a new HeaderChain structure. ProcInterrupt points // NewHeaderChain creates a new HeaderChain structure. ProcInterrupt points
// to the parent's interrupt semaphore. // to the parent's interrupt semaphore.
func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine consensus.Engine, procInterrupt func() bool) (*HeaderChain, error) { func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine consensus.Engine, procInterrupt func() bool) (*HeaderChain, error) {
// Seed a fast but crypto originating random generator
seed, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64))
if err != nil {
return nil, err
}
hc := &HeaderChain{ hc := &HeaderChain{
config: config, config: config,
chainDb: chainDb, chainDb: chainDb,
@ -89,7 +82,6 @@ func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine c
tdCache: lru.NewCache[common.Hash, *big.Int](tdCacheLimit), tdCache: lru.NewCache[common.Hash, *big.Int](tdCacheLimit),
numberCache: lru.NewCache[common.Hash, uint64](numberCacheLimit), numberCache: lru.NewCache[common.Hash, uint64](numberCacheLimit),
procInterrupt: procInterrupt, procInterrupt: procInterrupt,
rand: mrand.New(mrand.NewSource(seed.Int64())),
engine: engine, engine: engine,
} }
hc.genesisHeader = hc.GetHeaderByNumber(0) hc.genesisHeader = hc.GetHeaderByNumber(0)
@ -313,14 +305,6 @@ func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header) (int, error) {
return 0, fmt.Errorf("non contiguous insert: item %d is #%d [%x..], item %d is #%d [%x..] (parent [%x..])", i-1, chain[i-1].Number, return 0, fmt.Errorf("non contiguous insert: item %d is #%d [%x..], item %d is #%d [%x..] (parent [%x..])", i-1, chain[i-1].Number,
parentHash.Bytes()[:4], i, chain[i].Number, hash.Bytes()[:4], chain[i].ParentHash[:4]) parentHash.Bytes()[:4], i, chain[i].Number, hash.Bytes()[:4], chain[i].ParentHash[:4])
} }
// If the header is a banned one, straight out abort
if BadHashes[chain[i].ParentHash] {
return i - 1, ErrBannedHash
}
// If it's the last header in the cunk, we need to check it too
if i == len(chain)-1 && BadHashes[chain[i].Hash()] {
return i, ErrBannedHash
}
} }
// Start the parallel verifier // Start the parallel verifier
abort, results := hc.engine.VerifyHeaders(hc, chain) abort, results := hc.engine.VerifyHeaders(hc, chain)
@ -525,7 +509,7 @@ func (hc *HeaderChain) GetCanonicalHash(number uint64) common.Hash {
// CurrentHeader retrieves the current head header of the canonical chain. The // CurrentHeader retrieves the current head header of the canonical chain. The
// header is retrieved from the HeaderChain's internal cache. // header is retrieved from the HeaderChain's internal cache.
func (hc *HeaderChain) CurrentHeader() *types.Header { func (hc *HeaderChain) CurrentHeader() *types.Header {
return hc.currentHeader.Load().(*types.Header) return hc.currentHeader.Load()
} }
// SetCurrentHeader sets the in-memory head header marker of the canonical chan // SetCurrentHeader sets the in-memory head header marker of the canonical chan

View file

@ -316,8 +316,8 @@ func ReadHeaderRange(db ethdb.Reader, number uint64, count uint64) []rlp.RawValu
if count == 0 { if count == 0 {
return rlpHeaders return rlpHeaders
} }
// read remaining from ancients // read remaining from ancients, cap at 2M
data, err := db.AncientRange(ChainFreezerHeaderTable, i+1-count, count, 0) data, err := db.AncientRange(ChainFreezerHeaderTable, i+1-count, count, 2*1024*1024)
if err != nil { if err != nil {
log.Error("Failed to read headers from freezer", "err", err) log.Error("Failed to read headers from freezer", "err", err)
return rlpHeaders return rlpHeaders
@ -695,7 +695,7 @@ func (r *receiptLogs) DecodeRLP(s *rlp.Stream) error {
return nil return nil
} }
// DeriveLogFields fills the logs in receiptLogs with information such as block number, txhash, etc. // deriveLogFields fills the logs in receiptLogs with information such as block number, txhash, etc.
func deriveLogFields(receipts []*receiptLogs, hash common.Hash, number uint64, txs types.Transactions) error { func deriveLogFields(receipts []*receiptLogs, hash common.Hash, number uint64, txs types.Transactions) error {
logIndex := uint(0) logIndex := uint(0)
if len(txs) != len(receipts) { if len(txs) != len(receipts) {

View file

@ -92,20 +92,20 @@ func DeleteAccountSnapshot(db ethdb.KeyValueWriter, hash common.Hash) {
} }
} }
// ReadStorageSnapshot retrieves the snapshot entry of an storage trie leaf. // ReadStorageSnapshot retrieves the snapshot entry of a storage trie leaf.
func ReadStorageSnapshot(db ethdb.KeyValueReader, accountHash, storageHash common.Hash) []byte { func ReadStorageSnapshot(db ethdb.KeyValueReader, accountHash, storageHash common.Hash) []byte {
data, _ := db.Get(storageSnapshotKey(accountHash, storageHash)) data, _ := db.Get(storageSnapshotKey(accountHash, storageHash))
return data return data
} }
// WriteStorageSnapshot stores the snapshot entry of an storage trie leaf. // WriteStorageSnapshot stores the snapshot entry of a storage trie leaf.
func WriteStorageSnapshot(db ethdb.KeyValueWriter, accountHash, storageHash common.Hash, entry []byte) { func WriteStorageSnapshot(db ethdb.KeyValueWriter, accountHash, storageHash common.Hash, entry []byte) {
if err := db.Put(storageSnapshotKey(accountHash, storageHash), entry); err != nil { if err := db.Put(storageSnapshotKey(accountHash, storageHash), entry); err != nil {
log.Crit("Failed to store storage snapshot", "err", err) log.Crit("Failed to store storage snapshot", "err", err)
} }
} }
// DeleteStorageSnapshot removes the snapshot entry of an storage trie leaf. // DeleteStorageSnapshot removes the snapshot entry of a storage trie leaf.
func DeleteStorageSnapshot(db ethdb.KeyValueWriter, accountHash, storageHash common.Hash) { func DeleteStorageSnapshot(db ethdb.KeyValueWriter, accountHash, storageHash common.Hash) {
if err := db.Delete(storageSnapshotKey(accountHash, storageHash)); err != nil { if err := db.Delete(storageSnapshotKey(accountHash, storageHash)); err != nil {
log.Crit("Failed to delete storage snapshot", "err", err) log.Crit("Failed to delete storage snapshot", "err", err)

View file

@ -21,7 +21,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"path"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
@ -172,7 +171,7 @@ func resolveChainFreezerDir(ancient string) string {
// sub folder, if not then two possibilities: // sub folder, if not then two possibilities:
// - chain freezer is not initialized // - chain freezer is not initialized
// - chain freezer exists in legacy location (root ancient folder) // - chain freezer exists in legacy location (root ancient folder)
freezer := path.Join(ancient, ChainFreezerName) freezer := filepath.Join(ancient, ChainFreezerName)
if !common.FileExist(freezer) { if !common.FileExist(freezer) {
if !common.FileExist(ancient) { if !common.FileExist(ancient) {
// The entire ancient store is not initialized, still use the sub // The entire ancient store is not initialized, still use the sub

View file

@ -23,7 +23,6 @@ import (
"math/big" "math/big"
"math/rand" "math/rand"
"os" "os"
"path"
"path/filepath" "path/filepath"
"sync" "sync"
"testing" "testing"
@ -398,11 +397,11 @@ func TestRenameWindows(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
f2, err := os.Create(path.Join(dir1, fname2)) f2, err := os.Create(filepath.Join(dir1, fname2))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
f3, err := os.Create(path.Join(dir2, fname2)) f3, err := os.Create(filepath.Join(dir2, fname2))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -424,15 +423,15 @@ func TestRenameWindows(t *testing.T) {
if err := f3.Close(); err != nil { if err := f3.Close(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := os.Rename(f.Name(), path.Join(dir2, fname)); err != nil { if err := os.Rename(f.Name(), filepath.Join(dir2, fname)); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := os.Rename(f2.Name(), path.Join(dir2, fname2)); err != nil { if err := os.Rename(f2.Name(), filepath.Join(dir2, fname2)); err != nil {
t.Fatal(err) t.Fatal(err)
} }
// Check file contents // Check file contents
f, err = os.Open(path.Join(dir2, fname)) f, err = os.Open(filepath.Join(dir2, fname))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -446,7 +445,7 @@ func TestRenameWindows(t *testing.T) {
t.Errorf("unexpected file contents. Got %v\n", buf) t.Errorf("unexpected file contents. Got %v\n", buf)
} }
f, err = os.Open(path.Join(dir2, fname2)) f, err = os.Open(filepath.Join(dir2, fname2))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -17,6 +17,8 @@
package state package state
import ( import (
"maps"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
@ -57,16 +59,10 @@ func newAccessList() *accessList {
// Copy creates an independent copy of an accessList. // Copy creates an independent copy of an accessList.
func (a *accessList) Copy() *accessList { func (a *accessList) Copy() *accessList {
cp := newAccessList() cp := newAccessList()
for k, v := range a.addresses { cp.addresses = maps.Clone(a.addresses)
cp.addresses[k] = v
}
cp.slots = make([]map[common.Hash]struct{}, len(a.slots)) cp.slots = make([]map[common.Hash]struct{}, len(a.slots))
for i, slotMap := range a.slots { for i, slotMap := range a.slots {
newSlotmap := make(map[common.Hash]struct{}, len(slotMap)) cp.slots[i] = maps.Clone(slotMap)
for k := range slotMap {
newSlotmap[k] = struct{}{}
}
cp.slots[i] = newSlotmap
} }
return cp return cp
} }

View file

@ -209,6 +209,8 @@ func (db *cachingDB) CopyTrie(t Trie) Trie {
switch t := t.(type) { switch t := t.(type) {
case *trie.StateTrie: case *trie.StateTrie:
return t.Copy() return t.Copy()
case *trie.VerkleTrie:
return t.Copy()
default: default:
panic(fmt.Errorf("unknown trie type %T", t)) panic(fmt.Errorf("unknown trie type %T", t))
} }

View file

@ -46,7 +46,7 @@ type nodeIterator struct {
Error error // Failure set in case of an internal error in the iterator Error error // Failure set in case of an internal error in the iterator
} }
// newNodeIterator creates an post-order state node iterator. // newNodeIterator creates a post-order state node iterator.
func newNodeIterator(state *StateDB) *nodeIterator { func newNodeIterator(state *StateDB) *nodeIterator {
return &nodeIterator{ return &nodeIterator{
state: state, state: state,

View file

@ -17,6 +17,8 @@
package state package state
import ( import (
"maps"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
@ -29,6 +31,9 @@ type journalEntry interface {
// dirtied returns the Ethereum address modified by this journal entry. // dirtied returns the Ethereum address modified by this journal entry.
dirtied() *common.Address dirtied() *common.Address
// copy returns a deep-copied journal entry.
copy() journalEntry
} }
// journal contains the list of state modifications applied since the last state // journal contains the list of state modifications applied since the last state
@ -83,22 +88,31 @@ func (j *journal) length() int {
return len(j.entries) return len(j.entries)
} }
// copy returns a deep-copied journal.
func (j *journal) copy() *journal {
entries := make([]journalEntry, 0, j.length())
for i := 0; i < j.length(); i++ {
entries = append(entries, j.entries[i].copy())
}
return &journal{
entries: entries,
dirties: maps.Clone(j.dirties),
}
}
type ( type (
// Changes to the account trie. // Changes to the account trie.
createObjectChange struct { createObjectChange struct {
account *common.Address account *common.Address
} }
resetObjectChange struct {
account *common.Address
prev *stateObject
prevdestruct bool
prevAccount []byte
prevStorage map[common.Hash][]byte
prevAccountOriginExist bool // createContractChange represents an account becoming a contract-account.
prevAccountOrigin []byte // This event happens prior to executing initcode. The journal-event simply
prevStorageOrigin map[common.Hash][]byte // manages the created-flag, in order to allow same-tx destruction.
createContractChange struct {
account common.Address
} }
selfDestructChange struct { selfDestructChange struct {
account *common.Address account *common.Address
prev bool // whether account had already self-destructed prev bool // whether account had already self-destructed
@ -115,8 +129,9 @@ type (
prev uint64 prev uint64
} }
storageChange struct { storageChange struct {
account *common.Address account *common.Address
key, prevalue common.Hash key common.Hash
prevvalue *common.Hash
} }
codeChange struct { codeChange struct {
account *common.Address account *common.Address
@ -136,6 +151,7 @@ type (
touchChange struct { touchChange struct {
account *common.Address account *common.Address
} }
// Changes to the access list // Changes to the access list
accessListAddAccountChange struct { accessListAddAccountChange struct {
address *common.Address address *common.Address
@ -145,6 +161,7 @@ type (
slot *common.Hash slot *common.Hash
} }
// Changes to transient storage
transientStorageChange struct { transientStorageChange struct {
account *common.Address account *common.Address
key, prevalue common.Hash key, prevalue common.Hash
@ -153,34 +170,30 @@ type (
func (ch createObjectChange) revert(s *StateDB) { func (ch createObjectChange) revert(s *StateDB) {
delete(s.stateObjects, *ch.account) delete(s.stateObjects, *ch.account)
delete(s.stateObjectsDirty, *ch.account)
} }
func (ch createObjectChange) dirtied() *common.Address { func (ch createObjectChange) dirtied() *common.Address {
return ch.account return ch.account
} }
func (ch resetObjectChange) revert(s *StateDB) { func (ch createObjectChange) copy() journalEntry {
s.setStateObject(ch.prev) return createObjectChange{
if !ch.prevdestruct { account: ch.account,
delete(s.stateObjectsDestruct, ch.prev.address)
}
if ch.prevAccount != nil {
s.accounts[ch.prev.addrHash] = ch.prevAccount
}
if ch.prevStorage != nil {
s.storages[ch.prev.addrHash] = ch.prevStorage
}
if ch.prevAccountOriginExist {
s.accountsOrigin[ch.prev.address] = ch.prevAccountOrigin
}
if ch.prevStorageOrigin != nil {
s.storagesOrigin[ch.prev.address] = ch.prevStorageOrigin
} }
} }
func (ch resetObjectChange) dirtied() *common.Address { func (ch createContractChange) revert(s *StateDB) {
return ch.account s.getStateObject(ch.account).newContract = false
}
func (ch createContractChange) dirtied() *common.Address {
return nil
}
func (ch createContractChange) copy() journalEntry {
return createContractChange{
account: ch.account,
}
} }
func (ch selfDestructChange) revert(s *StateDB) { func (ch selfDestructChange) revert(s *StateDB) {
@ -195,6 +208,14 @@ func (ch selfDestructChange) dirtied() *common.Address {
return ch.account return ch.account
} }
func (ch selfDestructChange) copy() journalEntry {
return selfDestructChange{
account: ch.account,
prev: ch.prev,
prevbalance: new(uint256.Int).Set(ch.prevbalance),
}
}
var ripemd = common.HexToAddress("0000000000000000000000000000000000000003") var ripemd = common.HexToAddress("0000000000000000000000000000000000000003")
func (ch touchChange) revert(s *StateDB) { func (ch touchChange) revert(s *StateDB) {
@ -204,6 +225,12 @@ func (ch touchChange) dirtied() *common.Address {
return ch.account return ch.account
} }
func (ch touchChange) copy() journalEntry {
return touchChange{
account: ch.account,
}
}
func (ch balanceChange) revert(s *StateDB) { func (ch balanceChange) revert(s *StateDB) {
s.getStateObject(*ch.account).setBalance(ch.prev) s.getStateObject(*ch.account).setBalance(ch.prev)
} }
@ -212,6 +239,13 @@ func (ch balanceChange) dirtied() *common.Address {
return ch.account return ch.account
} }
func (ch balanceChange) copy() journalEntry {
return balanceChange{
account: ch.account,
prev: new(uint256.Int).Set(ch.prev),
}
}
func (ch nonceChange) revert(s *StateDB) { func (ch nonceChange) revert(s *StateDB) {
s.getStateObject(*ch.account).setNonce(ch.prev) s.getStateObject(*ch.account).setNonce(ch.prev)
} }
@ -220,6 +254,13 @@ func (ch nonceChange) dirtied() *common.Address {
return ch.account return ch.account
} }
func (ch nonceChange) copy() journalEntry {
return nonceChange{
account: ch.account,
prev: ch.prev,
}
}
func (ch codeChange) revert(s *StateDB) { func (ch codeChange) revert(s *StateDB) {
s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode) s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode)
} }
@ -228,14 +269,30 @@ func (ch codeChange) dirtied() *common.Address {
return ch.account return ch.account
} }
func (ch codeChange) copy() journalEntry {
return codeChange{
account: ch.account,
prevhash: common.CopyBytes(ch.prevhash),
prevcode: common.CopyBytes(ch.prevcode),
}
}
func (ch storageChange) revert(s *StateDB) { func (ch storageChange) revert(s *StateDB) {
s.getStateObject(*ch.account).setState(ch.key, ch.prevalue) s.getStateObject(*ch.account).setState(ch.key, ch.prevvalue)
} }
func (ch storageChange) dirtied() *common.Address { func (ch storageChange) dirtied() *common.Address {
return ch.account return ch.account
} }
func (ch storageChange) copy() journalEntry {
return storageChange{
account: ch.account,
key: ch.key,
prevvalue: ch.prevvalue,
}
}
func (ch transientStorageChange) revert(s *StateDB) { func (ch transientStorageChange) revert(s *StateDB) {
s.setTransientState(*ch.account, ch.key, ch.prevalue) s.setTransientState(*ch.account, ch.key, ch.prevalue)
} }
@ -244,6 +301,14 @@ func (ch transientStorageChange) dirtied() *common.Address {
return nil return nil
} }
func (ch transientStorageChange) copy() journalEntry {
return transientStorageChange{
account: ch.account,
key: ch.key,
prevalue: ch.prevalue,
}
}
func (ch refundChange) revert(s *StateDB) { func (ch refundChange) revert(s *StateDB) {
s.refund = ch.prev s.refund = ch.prev
} }
@ -252,6 +317,12 @@ func (ch refundChange) dirtied() *common.Address {
return nil return nil
} }
func (ch refundChange) copy() journalEntry {
return refundChange{
prev: ch.prev,
}
}
func (ch addLogChange) revert(s *StateDB) { func (ch addLogChange) revert(s *StateDB) {
logs := s.logs[ch.txhash] logs := s.logs[ch.txhash]
if len(logs) == 1 { if len(logs) == 1 {
@ -266,6 +337,12 @@ func (ch addLogChange) dirtied() *common.Address {
return nil return nil
} }
func (ch addLogChange) copy() journalEntry {
return addLogChange{
txhash: ch.txhash,
}
}
func (ch addPreimageChange) revert(s *StateDB) { func (ch addPreimageChange) revert(s *StateDB) {
delete(s.preimages, ch.hash) delete(s.preimages, ch.hash)
} }
@ -274,6 +351,12 @@ func (ch addPreimageChange) dirtied() *common.Address {
return nil return nil
} }
func (ch addPreimageChange) copy() journalEntry {
return addPreimageChange{
hash: ch.hash,
}
}
func (ch accessListAddAccountChange) revert(s *StateDB) { func (ch accessListAddAccountChange) revert(s *StateDB) {
/* /*
One important invariant here, is that whenever a (addr, slot) is added, if the One important invariant here, is that whenever a (addr, slot) is added, if the
@ -291,6 +374,12 @@ func (ch accessListAddAccountChange) dirtied() *common.Address {
return nil return nil
} }
func (ch accessListAddAccountChange) copy() journalEntry {
return accessListAddAccountChange{
address: ch.address,
}
}
func (ch accessListAddSlotChange) revert(s *StateDB) { func (ch accessListAddSlotChange) revert(s *StateDB) {
s.accessList.DeleteSlot(*ch.address, *ch.slot) s.accessList.DeleteSlot(*ch.address, *ch.slot)
} }
@ -298,3 +387,10 @@ func (ch accessListAddSlotChange) revert(s *StateDB) {
func (ch accessListAddSlotChange) dirtied() *common.Address { func (ch accessListAddSlotChange) dirtied() *common.Address {
return nil return nil
} }
func (ch accessListAddSlotChange) copy() journalEntry {
return accessListAddSlotChange{
address: ch.address,
slot: ch.slot,
}
}

View file

@ -46,7 +46,7 @@ type generatorStats struct {
storage common.StorageSize // Total account and storage slot size(generation or recovery) storage common.StorageSize // Total account and storage slot size(generation or recovery)
} }
// Log creates an contextual log with the given message and the context pulled // Log creates a contextual log with the given message and the context pulled
// from the internally maintained statistics. // from the internally maintained statistics.
func (gs *generatorStats) Log(msg string, root common.Hash, marker []byte) { func (gs *generatorStats) Log(msg string, root common.Hash, marker []byte) {
var ctx []interface{} var ctx []interface{}

View file

@ -362,15 +362,15 @@ func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, accou
} }
func stackTrieGenerate(db ethdb.KeyValueWriter, scheme string, owner common.Hash, in chan trieKV, out chan common.Hash) { func stackTrieGenerate(db ethdb.KeyValueWriter, scheme string, owner common.Hash, in chan trieKV, out chan common.Hash) {
options := trie.NewStackTrieOptions() var onTrieNode trie.OnTrieNode
if db != nil { if db != nil {
options = options.WithWriter(func(path []byte, hash common.Hash, blob []byte) { onTrieNode = func(path []byte, hash common.Hash, blob []byte) {
rawdb.WriteTrieNode(db, owner, path, hash, blob, scheme) rawdb.WriteTrieNode(db, owner, path, hash, blob, scheme)
}) }
} }
t := trie.NewStackTrie(options) t := trie.NewStackTrie(onTrieNode)
for leaf := range in { for leaf := range in {
t.Update(leaf.key[:], leaf.value) t.Update(leaf.key[:], leaf.value)
} }
out <- t.Commit() out <- t.Hash()
} }

View file

@ -68,7 +68,7 @@ func (dl *diffLayer) initBinaryStorageIterator(account common.Hash) Iterator {
parent, ok := dl.parent.(*diffLayer) parent, ok := dl.parent.(*diffLayer)
if !ok { if !ok {
// If the storage in this layer is already destructed, discard all // If the storage in this layer is already destructed, discard all
// deeper layers but still return an valid single-branch iterator. // deeper layers but still return a valid single-branch iterator.
a, destructed := dl.StorageIterator(account, common.Hash{}) a, destructed := dl.StorageIterator(account, common.Hash{})
if destructed { if destructed {
l := &binaryIterator{ l := &binaryIterator{
@ -92,7 +92,7 @@ func (dl *diffLayer) initBinaryStorageIterator(account common.Hash) Iterator {
return l return l
} }
// If the storage in this layer is already destructed, discard all // If the storage in this layer is already destructed, discard all
// deeper layers but still return an valid single-branch iterator. // deeper layers but still return a valid single-branch iterator.
a, destructed := dl.StorageIterator(account, common.Hash{}) a, destructed := dl.StorageIterator(account, common.Hash{})
if destructed { if destructed {
l := &binaryIterator{ l := &binaryIterator{

View file

@ -25,7 +25,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
// weightedIterator is a iterator with an assigned weight. It is used to prioritise // weightedIterator is an iterator with an assigned weight. It is used to prioritise
// which account or storage slot is the correct one if multiple iterators find the // which account or storage slot is the correct one if multiple iterators find the
// same one (modified in multiple consecutive blocks). // same one (modified in multiple consecutive blocks).
type weightedIterator struct { type weightedIterator struct {

View file

@ -835,7 +835,7 @@ func (t *Tree) disklayer() *diskLayer {
} }
} }
// diskRoot is a internal helper function to return the disk layer root. // diskRoot is an internal helper function to return the disk layer root.
// The lock of snapTree is assumed to be held already. // The lock of snapTree is assumed to be held already.
func (t *Tree) diskRoot() common.Hash { func (t *Tree) diskRoot() common.Hash {
disklayer := t.disklayer() disklayer := t.disklayer()

View file

@ -20,6 +20,7 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"maps"
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -31,27 +32,10 @@ import (
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
type Code []byte
func (c Code) String() string {
return string(c) //strings.Join(Disassemble(c), " ")
}
type Storage map[common.Hash]common.Hash type Storage map[common.Hash]common.Hash
func (s Storage) String() (str string) {
for key, value := range s {
str += fmt.Sprintf("%X : %X\n", key, value)
}
return
}
func (s Storage) Copy() Storage { func (s Storage) Copy() Storage {
cpy := make(Storage, len(s)) return maps.Clone(s)
for key, value := range s {
cpy[key] = value
}
return cpy
} }
// stateObject represents an Ethereum account which is being modified. // stateObject represents an Ethereum account which is being modified.
@ -68,8 +52,8 @@ type stateObject struct {
data types.StateAccount // Account data with all mutations applied in the scope of block data types.StateAccount // Account data with all mutations applied in the scope of block
// Write caches. // Write caches.
trie Trie // storage trie, which becomes non-nil on first access trie Trie // storage trie, which becomes non-nil on first access
code Code // contract bytecode, which gets set when code is loaded code []byte // contract bytecode, which gets set when code is loaded
originStorage Storage // Storage cache of original entries to dedup rewrites originStorage Storage // Storage cache of original entries to dedup rewrites
pendingStorage Storage // Storage entries that need to be flushed to disk, at the end of an entire block pendingStorage Storage // Storage entries that need to be flushed to disk, at the end of an entire block
@ -78,17 +62,16 @@ type stateObject struct {
// Cache flags. // Cache flags.
dirtyCode bool // true if the code was updated dirtyCode bool // true if the code was updated
// Flag whether the account was marked as self-destructed. The self-destructed account // Flag whether the account was marked as self-destructed. The self-destructed
// is still accessible in the scope of same transaction. // account is still accessible in the scope of same transaction.
selfDestructed bool selfDestructed bool
// Flag whether the account was marked as deleted. A self-destructed account // This is an EIP-6780 flag indicating whether the object is eligible for
// or an account that is considered as empty will be marked as deleted at // self-destruct according to EIP-6780. The flag could be set either when
// the end of transaction and no longer accessible anymore. // the contract is just created within the current transaction, or when the
deleted bool // object was previously existent and is being deployed as a contract within
// the current transaction.
// Flag whether the object was created in the current transaction newContract bool
created bool
} }
// empty returns whether the account is considered empty. // empty returns whether the account is considered empty.
@ -98,10 +81,7 @@ func (s *stateObject) empty() bool {
// newObject creates a state object. // newObject creates a state object.
func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *stateObject { func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *stateObject {
var ( origin := acct
origin = acct
created = acct == nil // true if the account was not existent
)
if acct == nil { if acct == nil {
acct = types.NewEmptyStateAccount() acct = types.NewEmptyStateAccount()
} }
@ -114,7 +94,6 @@ func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *s
originStorage: make(Storage), originStorage: make(Storage),
pendingStorage: make(Storage), pendingStorage: make(Storage),
dirtyStorage: make(Storage), dirtyStorage: make(Storage),
created: created,
} }
} }
@ -161,13 +140,20 @@ func (s *stateObject) getTrie() (Trie, error) {
// GetState retrieves a value from the account storage trie. // GetState retrieves a value from the account storage trie.
func (s *stateObject) GetState(key common.Hash) common.Hash { func (s *stateObject) GetState(key common.Hash) common.Hash {
value, _ := s.getState(key)
return value
}
// getState retrieves a value from the account storage trie and also returns if
// the slot is already dirty or not.
func (s *stateObject) getState(key common.Hash) (common.Hash, bool) {
// If we have a dirty value for this state entry, return it // If we have a dirty value for this state entry, return it
value, dirty := s.dirtyStorage[key] value, dirty := s.dirtyStorage[key]
if dirty { if dirty {
return value return value, true
} }
// Otherwise return the entry's original value // Otherwise return the entry's original value
return s.GetCommittedState(key) return s.GetCommittedState(key), false
} }
// GetCommittedState retrieves a value from the committed account storage trie. // GetCommittedState retrieves a value from the committed account storage trie.
@ -230,25 +216,38 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
// SetState updates a value in account storage. // SetState updates a value in account storage.
func (s *stateObject) SetState(key, value common.Hash) { func (s *stateObject) SetState(key, value common.Hash) {
// If the new value is the same as old, don't set // If the new value is the same as old, don't set. Otherwise, track only the
prev := s.GetState(key) // dirty changes, supporting reverting all of it back to no change.
prev, dirty := s.getState(key)
if prev == value { if prev == value {
return return
} }
var prevvalue *common.Hash
if dirty {
prevvalue = &prev
}
// New value is different, update and journal the change // New value is different, update and journal the change
s.db.journal.append(storageChange{ s.db.journal.append(storageChange{
account: &s.address, account: &s.address,
key: key, key: key,
prevalue: prev, prevvalue: prevvalue,
}) })
if s.db.logger != nil && s.db.logger.OnStorageChange != nil { if s.db.logger != nil && s.db.logger.OnStorageChange != nil {
s.db.logger.OnStorageChange(s.address, key, prev, value) s.db.logger.OnStorageChange(s.address, key, prev, value)
} }
s.setState(key, value) s.setState(key, &value)
} }
func (s *stateObject) setState(key, value common.Hash) { // setState updates a value in account dirty storage. If the value being set is
s.dirtyStorage[key] = value // nil (assuming journal revert), the dirtyness is removed.
func (s *stateObject) setState(key common.Hash, value *common.Hash) {
// If the first set is being reverted, undo the dirty marker
if value == nil {
delete(s.dirtyStorage, key)
return
}
// Otherwise restore the previous value
s.dirtyStorage[key] = *value
} }
// finalise moves all dirty storage slots into the pending area to be hashed or // finalise moves all dirty storage slots into the pending area to be hashed or
@ -267,6 +266,10 @@ func (s *stateObject) finalise(prefetch bool) {
if len(s.dirtyStorage) > 0 { if len(s.dirtyStorage) > 0 {
s.dirtyStorage = make(Storage) s.dirtyStorage = make(Storage)
} }
// Revoke the flag at the end of the transaction. It finalizes the status
// of the newly-created object as it's no longer eligible for self-destruct
// by EIP-6780. For non-newly-created objects, it's a no-op.
s.newContract = false
} }
// updateTrie is responsible for persisting cached storage changes into the // updateTrie is responsible for persisting cached storage changes into the
@ -298,6 +301,18 @@ func (s *stateObject) updateTrie() (Trie, error) {
} }
// Insert all the pending storage updates into the trie // Insert all the pending storage updates into the trie
usedStorage := make([][]byte, 0, len(s.pendingStorage)) usedStorage := make([][]byte, 0, len(s.pendingStorage))
// Perform trie updates before deletions. This prevents resolution of unnecessary trie nodes
// in circumstances similar to the following:
//
// Consider nodes `A` and `B` who share the same full node parent `P` and have no other siblings.
// During the execution of a block:
// - `A` is deleted,
// - `C` is created, and also shares the parent `P`.
// If the deletion is handled first, then `P` would be left with only one child, thus collapsed
// into a shortnode. This requires `B` to be resolved from disk.
// Whereas if the created node is handled first, then the collapse is avoided, and `B` is not resolved.
var deletions []common.Hash
for key, value := range s.pendingStorage { for key, value := range s.pendingStorage {
// Skip noop changes, persist actual changes // Skip noop changes, persist actual changes
if value == s.originStorage[key] { if value == s.originStorage[key] {
@ -307,13 +322,7 @@ func (s *stateObject) updateTrie() (Trie, error) {
s.originStorage[key] = value s.originStorage[key] = value
var encoded []byte // rlp-encoded value to be used by the snapshot var encoded []byte // rlp-encoded value to be used by the snapshot
if (value == common.Hash{}) { if (value != common.Hash{}) {
if err := tr.DeleteStorage(s.address, key[:]); err != nil {
s.db.setError(err)
return nil, err
}
s.db.StorageDeleted += 1
} else {
// Encoding []byte cannot fail, ok to ignore the error. // Encoding []byte cannot fail, ok to ignore the error.
trimmed := common.TrimLeftZeroes(value[:]) trimmed := common.TrimLeftZeroes(value[:])
encoded, _ = rlp.EncodeToBytes(trimmed) encoded, _ = rlp.EncodeToBytes(trimmed)
@ -322,6 +331,8 @@ func (s *stateObject) updateTrie() (Trie, error) {
return nil, err return nil, err
} }
s.db.StorageUpdated += 1 s.db.StorageUpdated += 1
} else {
deletions = append(deletions, key)
} }
// Cache the mutated storage slots until commit // Cache the mutated storage slots until commit
if storage == nil { if storage == nil {
@ -353,6 +364,13 @@ func (s *stateObject) updateTrie() (Trie, error) {
// Cache the items for preloading // Cache the items for preloading
usedStorage = append(usedStorage, common.CopyBytes(key[:])) // Copy needed for closure usedStorage = append(usedStorage, common.CopyBytes(key[:])) // Copy needed for closure
} }
for _, key := range deletions {
if err := tr.DeleteStorage(s.address, key[:]); err != nil {
s.db.setError(err)
return nil, err
}
s.db.StorageDeleted += 1
}
if s.db.prefetcher != nil { if s.db.prefetcher != nil {
s.db.prefetcher.used(s.addrHash, s.data.Root, usedStorage) s.db.prefetcher.used(s.addrHash, s.data.Root, usedStorage)
} }
@ -364,7 +382,7 @@ func (s *stateObject) updateTrie() (Trie, error) {
// new storage trie root. // new storage trie root.
func (s *stateObject) updateRoot() { func (s *stateObject) updateRoot() {
// Flush cached storage mutations into trie, short circuit if any error // Flush cached storage mutations into trie, short circuit if any error
// is occurred or there is not change in the trie. // is occurred or there is no change in the trie.
tr, err := s.updateTrie() tr, err := s.updateTrie()
if err != nil || tr == nil { if err != nil || tr == nil {
return return
@ -451,12 +469,12 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject {
obj.trie = db.db.CopyTrie(s.trie) obj.trie = db.db.CopyTrie(s.trie)
} }
obj.code = s.code obj.code = s.code
obj.dirtyStorage = s.dirtyStorage.Copy()
obj.originStorage = s.originStorage.Copy() obj.originStorage = s.originStorage.Copy()
obj.pendingStorage = s.pendingStorage.Copy() obj.pendingStorage = s.pendingStorage.Copy()
obj.selfDestructed = s.selfDestructed obj.dirtyStorage = s.dirtyStorage.Copy()
obj.dirtyCode = s.dirtyCode obj.dirtyCode = s.dirtyCode
obj.deleted = s.deleted obj.selfDestructed = s.selfDestructed
obj.newContract = s.newContract
return obj return obj
} }
@ -471,7 +489,7 @@ func (s *stateObject) Address() common.Address {
// Code returns the contract code associated with this object, if any. // Code returns the contract code associated with this object, if any.
func (s *stateObject) Code() []byte { func (s *stateObject) Code() []byte {
if s.code != nil { if len(s.code) != 0 {
return s.code return s.code
} }
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) { if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {
@ -489,7 +507,7 @@ func (s *stateObject) Code() []byte {
// or zero if none. This method is an almost mirror of Code, but uses a cache // or zero if none. This method is an almost mirror of Code, but uses a cache
// inside the database to avoid loading codes seen recently. // inside the database to avoid loading codes seen recently.
func (s *stateObject) CodeSize() int { func (s *stateObject) CodeSize() int {
if s.code != nil { if len(s.code) != 0 {
return len(s.code) return len(s.code)
} }
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) { if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {

View file

@ -194,106 +194,20 @@ func TestSnapshotEmpty(t *testing.T) {
s.state.RevertToSnapshot(s.state.Snapshot()) s.state.RevertToSnapshot(s.state.Snapshot())
} }
func TestSnapshot2(t *testing.T) { func TestCreateObjectRevert(t *testing.T) {
state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil) state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil)
addr := common.BytesToAddress([]byte("so0"))
snap := state.Snapshot()
stateobjaddr0 := common.BytesToAddress([]byte("so0")) state.CreateAccount(addr)
stateobjaddr1 := common.BytesToAddress([]byte("so1")) so0 := state.getStateObject(addr)
var storageaddr common.Hash
data0 := common.BytesToHash([]byte{17})
data1 := common.BytesToHash([]byte{18})
state.SetState(stateobjaddr0, storageaddr, data0)
state.SetState(stateobjaddr1, storageaddr, data1)
// db, trie are already non-empty values
so0 := state.getStateObject(stateobjaddr0)
so0.SetBalance(uint256.NewInt(42), tracing.BalanceChangeUnspecified) so0.SetBalance(uint256.NewInt(42), tracing.BalanceChangeUnspecified)
so0.SetNonce(43) so0.SetNonce(43)
so0.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e'}), []byte{'c', 'a', 'f', 'e'}) so0.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e'}), []byte{'c', 'a', 'f', 'e'})
so0.selfDestructed = false
so0.deleted = false
state.setStateObject(so0) state.setStateObject(so0)
root, _ := state.Commit(0, false) state.RevertToSnapshot(snap)
state, _ = New(root, state.db, state.snaps) if state.Exist(addr) {
t.Error("Unexpected account after revert")
// and one with deleted == true
so1 := state.getStateObject(stateobjaddr1)
so1.SetBalance(uint256.NewInt(52), tracing.BalanceChangeUnspecified)
so1.SetNonce(53)
so1.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e', '2'}), []byte{'c', 'a', 'f', 'e', '2'})
so1.selfDestructed = true
so1.deleted = true
state.setStateObject(so1)
so1 = state.getStateObject(stateobjaddr1)
if so1 != nil {
t.Fatalf("deleted object not nil when getting")
}
snapshot := state.Snapshot()
state.RevertToSnapshot(snapshot)
so0Restored := state.getStateObject(stateobjaddr0)
// Update lazily-loaded values before comparing.
so0Restored.GetState(storageaddr)
so0Restored.Code()
// non-deleted is equal (restored)
compareStateObjects(so0Restored, so0, t)
// deleted should be nil, both before and after restore of state copy
so1Restored := state.getStateObject(stateobjaddr1)
if so1Restored != nil {
t.Fatalf("deleted object not nil after restoring snapshot: %+v", so1Restored)
}
}
func compareStateObjects(so0, so1 *stateObject, t *testing.T) {
if so0.Address() != so1.Address() {
t.Fatalf("Address mismatch: have %v, want %v", so0.address, so1.address)
}
if so0.Balance().Cmp(so1.Balance()) != 0 {
t.Fatalf("Balance mismatch: have %v, want %v", so0.Balance(), so1.Balance())
}
if so0.Nonce() != so1.Nonce() {
t.Fatalf("Nonce mismatch: have %v, want %v", so0.Nonce(), so1.Nonce())
}
if so0.data.Root != so1.data.Root {
t.Errorf("Root mismatch: have %x, want %x", so0.data.Root[:], so1.data.Root[:])
}
if !bytes.Equal(so0.CodeHash(), so1.CodeHash()) {
t.Fatalf("CodeHash mismatch: have %v, want %v", so0.CodeHash(), so1.CodeHash())
}
if !bytes.Equal(so0.code, so1.code) {
t.Fatalf("Code mismatch: have %v, want %v", so0.code, so1.code)
}
if len(so1.dirtyStorage) != len(so0.dirtyStorage) {
t.Errorf("Dirty storage size mismatch: have %d, want %d", len(so1.dirtyStorage), len(so0.dirtyStorage))
}
for k, v := range so1.dirtyStorage {
if so0.dirtyStorage[k] != v {
t.Errorf("Dirty storage key %x mismatch: have %v, want %v", k, so0.dirtyStorage[k], v)
}
}
for k, v := range so0.dirtyStorage {
if so1.dirtyStorage[k] != v {
t.Errorf("Dirty storage key %x mismatch: have %v, want none.", k, v)
}
}
if len(so1.originStorage) != len(so0.originStorage) {
t.Errorf("Origin storage size mismatch: have %d, want %d", len(so1.originStorage), len(so0.originStorage))
}
for k, v := range so1.originStorage {
if so0.originStorage[k] != v {
t.Errorf("Origin storage key %x mismatch: have %v, want %v", k, so0.originStorage[k], v)
}
}
for k, v := range so0.originStorage {
if so1.originStorage[k] != v {
t.Errorf("Origin storage key %x mismatch: have %v, want none.", k, v)
}
} }
} }

View file

@ -19,7 +19,9 @@ package state
import ( import (
"fmt" "fmt"
"maps"
"math/big" "math/big"
"slices"
"sort" "sort"
"time" "time"
@ -42,6 +44,26 @@ type revision struct {
journalIndex int journalIndex int
} }
type mutationType int
const (
update mutationType = iota
deletion
)
type mutation struct {
typ mutationType
applied bool
}
func (m *mutation) copy() *mutation {
return &mutation{typ: m.typ, applied: m.applied}
}
func (m *mutation) isDelete() bool {
return m.typ == deletion
}
// StateDB structs within the ethereum protocol are used to store anything // StateDB structs within the ethereum protocol are used to store anything
// within the merkle trie. StateDBs take care of caching and storing // within the merkle trie. StateDBs take care of caching and storing
// nested states. It's the general query interface to retrieve: // nested states. It's the general query interface to retrieve:
@ -73,12 +95,22 @@ type StateDB struct {
accountsOrigin map[common.Address][]byte // The original value of mutated accounts in 'slim RLP' encoding accountsOrigin map[common.Address][]byte // The original value of mutated accounts in 'slim RLP' encoding
storagesOrigin map[common.Address]map[common.Hash][]byte // The original value of mutated slots in prefix-zero trimmed rlp format storagesOrigin map[common.Address]map[common.Hash][]byte // The original value of mutated slots in prefix-zero trimmed rlp format
// This map holds 'live' objects, which will get modified while processing // This map holds 'live' objects, which will get modified while
// a state transition. // processing a state transition.
stateObjects map[common.Address]*stateObject stateObjects map[common.Address]*stateObject
stateObjectsPending map[common.Address]struct{} // State objects finalized but not yet written to the trie
stateObjectsDirty map[common.Address]struct{} // State objects modified in the current execution // This map holds 'deleted' objects. An object with the same address
stateObjectsDestruct map[common.Address]*types.StateAccount // State objects destructed in the block along with its previous value // might also occur in the 'stateObjects' map due to account
// resurrection. The account value is tracked as the original value
// before the transition. This map is populated at the transaction
// boundaries.
stateObjectsDestruct map[common.Address]*types.StateAccount
// This map tracks the account mutations that occurred during the
// transition. Uncommitted mutations belonging to the same account
// can be merged into a single one which is equivalent from database's
// perspective. This map is populated at the transaction boundaries.
mutations map[common.Address]*mutation
// DB error. // DB error.
// State objects are used by the consensus core and VM which are // State objects are used by the consensus core and VM which are
@ -152,9 +184,8 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
accountsOrigin: make(map[common.Address][]byte), accountsOrigin: make(map[common.Address][]byte),
storagesOrigin: make(map[common.Address]map[common.Hash][]byte), storagesOrigin: make(map[common.Address]map[common.Hash][]byte),
stateObjects: make(map[common.Address]*stateObject), stateObjects: make(map[common.Address]*stateObject),
stateObjectsPending: make(map[common.Address]struct{}),
stateObjectsDirty: make(map[common.Address]struct{}),
stateObjectsDestruct: make(map[common.Address]*types.StateAccount), stateObjectsDestruct: make(map[common.Address]*types.StateAccount),
mutations: make(map[common.Address]*mutation),
logs: make(map[common.Hash][]*types.Log), logs: make(map[common.Hash][]*types.Log),
preimages: make(map[common.Hash][]byte), preimages: make(map[common.Hash][]byte),
journal: newJournal(), journal: newJournal(),
@ -243,9 +274,7 @@ func (s *StateDB) Logs() []*types.Log {
func (s *StateDB) AddPreimage(hash common.Hash, preimage []byte) { func (s *StateDB) AddPreimage(hash common.Hash, preimage []byte) {
if _, ok := s.preimages[hash]; !ok { if _, ok := s.preimages[hash]; !ok {
s.journal.append(addPreimageChange{hash: hash}) s.journal.append(addPreimageChange{hash: hash})
pi := make([]byte, len(preimage)) s.preimages[hash] = slices.Clone(preimage)
copy(pi, preimage)
s.preimages[hash] = pi
} }
} }
@ -472,8 +501,7 @@ func (s *StateDB) Selfdestruct6780(addr common.Address) {
if stateObject == nil { if stateObject == nil {
return return
} }
if stateObject.newContract {
if stateObject.created {
s.SelfDestruct(addr) s.SelfDestruct(addr)
} }
} }
@ -541,36 +569,27 @@ func (s *StateDB) updateStateObject(obj *stateObject) {
} }
// deleteStateObject removes the given object from the state trie. // deleteStateObject removes the given object from the state trie.
func (s *StateDB) deleteStateObject(obj *stateObject) { func (s *StateDB) deleteStateObject(addr common.Address) {
// Track the amount of time wasted on deleting the account from the trie // Track the amount of time wasted on deleting the account from the trie
defer func(start time.Time) { s.AccountUpdates += time.Since(start) }(time.Now()) defer func(start time.Time) { s.AccountUpdates += time.Since(start) }(time.Now())
// Delete the account from the trie // Delete the account from the trie
addr := obj.Address()
if err := s.trie.DeleteAccount(addr); err != nil { if err := s.trie.DeleteAccount(addr); err != nil {
s.setError(fmt.Errorf("deleteStateObject (%x) error: %v", addr[:], err)) s.setError(fmt.Errorf("deleteStateObject (%x) error: %v", addr[:], err))
} }
} }
// getStateObject retrieves a state object given by the address, returning nil if // getStateObject retrieves a state object given by the address, returning nil if
// the object is not found or was deleted in this execution context. If you need // the object is not found or was deleted in this execution context.
// to differentiate between non-existent/just-deleted, use getDeletedStateObject.
func (s *StateDB) getStateObject(addr common.Address) *stateObject { func (s *StateDB) getStateObject(addr common.Address) *stateObject {
if obj := s.getDeletedStateObject(addr); obj != nil && !obj.deleted {
return obj
}
return nil
}
// getDeletedStateObject is similar to getStateObject, but instead of returning
// nil for a deleted state object, it returns the actual object with the deleted
// flag set. This is needed by the state journal to revert to the correct s-
// destructed object instead of wiping all knowledge about the state object.
func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
// Prefer live objects if any is available // Prefer live objects if any is available
if obj := s.stateObjects[addr]; obj != nil { if obj := s.stateObjects[addr]; obj != nil {
return obj return obj
} }
// Short circuit if the account is already destructed in this block.
if _, ok := s.stateObjectsDestruct[addr]; ok {
return nil
}
// If no live objects are available, attempt to use snapshots // If no live objects are available, attempt to use snapshots
var data *types.StateAccount var data *types.StateAccount
if s.snap != nil { if s.snap != nil {
@ -623,69 +642,40 @@ func (s *StateDB) setStateObject(object *stateObject) {
// getOrNewStateObject retrieves a state object or create a new state object if nil. // getOrNewStateObject retrieves a state object or create a new state object if nil.
func (s *StateDB) getOrNewStateObject(addr common.Address) *stateObject { func (s *StateDB) getOrNewStateObject(addr common.Address) *stateObject {
stateObject := s.getStateObject(addr) obj := s.getStateObject(addr)
if stateObject == nil { if obj == nil {
stateObject, _ = s.createObject(addr) obj = s.createObject(addr)
} }
return stateObject return obj
} }
// createObject creates a new state object. If there is an existing account with // createObject creates a new state object. The assumption is held there is no
// the given address, it is overwritten and returned as the second return value. // existing account with the given address, otherwise it will be silently overwritten.
func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) { func (s *StateDB) createObject(addr common.Address) *stateObject {
prev = s.getDeletedStateObject(addr) // Note, prev might have been deleted, we need that! obj := newObject(s, addr, nil)
newobj = newObject(s, addr, nil) s.journal.append(createObjectChange{account: &addr})
if prev == nil { s.setStateObject(obj)
s.journal.append(createObjectChange{account: &addr}) return obj
} else {
// The original account should be marked as destructed and all cached
// account and storage data should be cleared as well. Note, it must
// be done here, otherwise the destruction event of "original account"
// will be lost.
_, prevdestruct := s.stateObjectsDestruct[prev.address]
if !prevdestruct {
s.stateObjectsDestruct[prev.address] = prev.origin
}
// There may be some cached account/storage data already since IntermediateRoot
// will be called for each transaction before byzantium fork which will always
// cache the latest account/storage data.
prevAccount, ok := s.accountsOrigin[prev.address]
s.journal.append(resetObjectChange{
account: &addr,
prev: prev,
prevdestruct: prevdestruct,
prevAccount: s.accounts[prev.addrHash],
prevStorage: s.storages[prev.addrHash],
prevAccountOriginExist: ok,
prevAccountOrigin: prevAccount,
prevStorageOrigin: s.storagesOrigin[prev.address],
})
delete(s.accounts, prev.addrHash)
delete(s.storages, prev.addrHash)
delete(s.accountsOrigin, prev.address)
delete(s.storagesOrigin, prev.address)
}
s.setStateObject(newobj)
if prev != nil && !prev.deleted {
return newobj, prev
}
return newobj, nil
} }
// CreateAccount explicitly creates a state object. If a state object with the address // CreateAccount explicitly creates a new state object, assuming that the
// already exists the balance is carried over to the new account. // account did not previously exist in the state. If the account already
// // exists, this function will silently overwrite it which might lead to a
// CreateAccount is called during the EVM CREATE operation. The situation might arise that // consensus bug eventually.
// a contract does the following:
//
// 1. sends funds to sha(account ++ (nonce + 1))
// 2. tx_create(sha(account ++ nonce)) (note that this gets the address of 1)
//
// Carrying over the balance ensures that Ether doesn't disappear.
func (s *StateDB) CreateAccount(addr common.Address) { func (s *StateDB) CreateAccount(addr common.Address) {
newObj, prev := s.createObject(addr) s.createObject(addr)
if prev != nil { }
newObj.setBalance(prev.data.Balance)
// CreateContract is used whenever a contract is created. This may be preceded
// by CreateAccount, but that is not required if it already existed in the
// state due to funds sent beforehand.
// This operation sets the 'newContract'-flag, which is required in order to
// correctly handle EIP-6780 'delete-in-same-transaction' logic.
func (s *StateDB) CreateContract(addr common.Address) {
obj := s.getStateObject(addr)
if !obj.newContract {
obj.newContract = true
s.journal.append(createContractChange{account: addr})
} }
} }
@ -696,21 +686,25 @@ func (s *StateDB) Copy() *StateDB {
state := &StateDB{ state := &StateDB{
db: s.db, db: s.db,
trie: s.db.CopyTrie(s.trie), trie: s.db.CopyTrie(s.trie),
hasher: crypto.NewKeccakState(),
originalRoot: s.originalRoot, originalRoot: s.originalRoot,
accounts: make(map[common.Hash][]byte), accounts: copySet(s.accounts),
storages: make(map[common.Hash]map[common.Hash][]byte), storages: copy2DSet(s.storages),
accountsOrigin: make(map[common.Address][]byte), accountsOrigin: copySet(s.accountsOrigin),
storagesOrigin: make(map[common.Address]map[common.Hash][]byte), storagesOrigin: copy2DSet(s.storagesOrigin),
stateObjects: make(map[common.Address]*stateObject, len(s.journal.dirties)), stateObjects: make(map[common.Address]*stateObject, len(s.stateObjects)),
stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)), stateObjectsDestruct: maps.Clone(s.stateObjectsDestruct),
stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)), mutations: make(map[common.Address]*mutation, len(s.mutations)),
stateObjectsDestruct: make(map[common.Address]*types.StateAccount, len(s.stateObjectsDestruct)), dbErr: s.dbErr,
refund: s.refund, refund: s.refund,
thash: s.thash,
txIndex: s.txIndex,
logs: make(map[common.Hash][]*types.Log, len(s.logs)), logs: make(map[common.Hash][]*types.Log, len(s.logs)),
logSize: s.logSize, logSize: s.logSize,
preimages: make(map[common.Hash][]byte, len(s.preimages)), preimages: maps.Clone(s.preimages),
journal: newJournal(), journal: s.journal.copy(),
hasher: crypto.NewKeccakState(), validRevisions: slices.Clone(s.validRevisions),
nextRevisionId: s.nextRevisionId,
// In order for the block producer to be able to use and make additions // In order for the block producer to be able to use and make additions
// to the snapshot tree, we need to copy that as well. Otherwise, any // to the snapshot tree, we need to copy that as well. Otherwise, any
@ -719,49 +713,14 @@ func (s *StateDB) Copy() *StateDB {
snaps: s.snaps, snaps: s.snaps,
snap: s.snap, snap: s.snap,
} }
// Copy the dirty states, logs, and preimages // Deep copy cached state objects.
for addr := range s.journal.dirties { for addr, obj := range s.stateObjects {
// As documented [here](https://github.com/ethereum/go-ethereum/pull/16485#issuecomment-380438527), state.stateObjects[addr] = obj.deepCopy(state)
// and in the Finalise-method, there is a case where an object is in the journal but not
// in the stateObjects: OOG after touch on ripeMD prior to Byzantium. Thus, we need to check for
// nil
if object, exist := s.stateObjects[addr]; exist {
// Even though the original object is dirty, we are not copying the journal,
// so we need to make sure that any side-effect the journal would have caused
// during a commit (or similar op) is already applied to the copy.
state.stateObjects[addr] = object.deepCopy(state)
state.stateObjectsDirty[addr] = struct{}{} // Mark the copy dirty to force internal (code/state) commits
state.stateObjectsPending[addr] = struct{}{} // Mark the copy pending to force external (account) commits
}
} }
// Above, we don't copy the actual journal. This means that if the copy // Deep copy the object state markers.
// is copied, the loop above will be a no-op, since the copy's journal for addr, op := range s.mutations {
// is empty. Thus, here we iterate over stateObjects, to enable copies state.mutations[addr] = op.copy()
// of copies.
for addr := range s.stateObjectsPending {
if _, exist := state.stateObjects[addr]; !exist {
state.stateObjects[addr] = s.stateObjects[addr].deepCopy(state)
}
state.stateObjectsPending[addr] = struct{}{}
} }
for addr := range s.stateObjectsDirty {
if _, exist := state.stateObjects[addr]; !exist {
state.stateObjects[addr] = s.stateObjects[addr].deepCopy(state)
}
state.stateObjectsDirty[addr] = struct{}{}
}
// Deep copy the destruction markers.
for addr, value := range s.stateObjectsDestruct {
state.stateObjectsDestruct[addr] = value
}
// Deep copy the state changes made in the scope of block
// along with their original values.
state.accounts = copySet(s.accounts)
state.storages = copy2DSet(s.storages)
state.accountsOrigin = copySet(state.accountsOrigin)
state.storagesOrigin = copy2DSet(state.storagesOrigin)
// Deep copy the logs occurred in the scope of block // Deep copy the logs occurred in the scope of block
for hash, logs := range s.logs { for hash, logs := range s.logs {
cpy := make([]*types.Log, len(logs)) cpy := make([]*types.Log, len(logs))
@ -771,10 +730,6 @@ func (s *StateDB) Copy() *StateDB {
} }
state.logs[hash] = cpy state.logs[hash] = cpy
} }
// Deep copy the preimages occurred in the scope of block
for hash, preimage := range s.preimages {
state.preimages[hash] = preimage
}
// Do we need to copy the access list and transient storage? // Do we need to copy the access list and transient storage?
// In practice: No. At the start of a transaction, these two lists are empty. // In practice: No. At the start of a transaction, these two lists are empty.
// In practice, we only ever copy state _between_ transactions/blocks, never // In practice, we only ever copy state _between_ transactions/blocks, never
@ -839,7 +794,8 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
continue continue
} }
if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) { if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) {
obj.deleted = true delete(s.stateObjects, obj.address)
s.markDelete(addr)
// If ether was sent to account post-selfdestruct it is burnt. // If ether was sent to account post-selfdestruct it is burnt.
if bal := obj.Balance(); s.logger != nil && s.logger.OnBalanceChange != nil && obj.selfDestructed && bal.Sign() != 0 { if bal := obj.Balance(); s.logger != nil && s.logger.OnBalanceChange != nil && obj.selfDestructed && bal.Sign() != 0 {
@ -860,11 +816,8 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
delete(s.storagesOrigin, obj.address) // Clear out any previously updated storage data (may be recreated via a resurrect) delete(s.storagesOrigin, obj.address) // Clear out any previously updated storage data (may be recreated via a resurrect)
} else { } else {
obj.finalise(true) // Prefetch slots in the background obj.finalise(true) // Prefetch slots in the background
s.markUpdate(addr)
} }
obj.created = false
s.stateObjectsPending[addr] = struct{}{}
s.stateObjectsDirty[addr] = struct{}{}
// At this point, also ship the address off to the precacher. The precacher // At this point, also ship the address off to the precacher. The precacher
// will start loading tries, and when the change is eventually committed, // will start loading tries, and when the change is eventually committed,
// the commit-phase will be a lot faster // the commit-phase will be a lot faster
@ -903,10 +856,14 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
// the account prefetcher. Instead, let's process all the storage updates // the account prefetcher. Instead, let's process all the storage updates
// first, giving the account prefetches just a few more milliseconds of time // first, giving the account prefetches just a few more milliseconds of time
// to pull useful data from disk. // to pull useful data from disk.
for addr := range s.stateObjectsPending { for addr, op := range s.mutations {
if obj := s.stateObjects[addr]; !obj.deleted { if op.applied {
obj.updateRoot() continue
} }
if op.isDelete() {
continue
}
s.stateObjects[addr].updateRoot()
} }
// Now we're about to start to write changes to the trie. The trie is so far // Now we're about to start to write changes to the trie. The trie is so far
// _untouched_. We can check with the prefetcher, if it can give us a trie // _untouched_. We can check with the prefetcher, if it can give us a trie
@ -916,23 +873,41 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
s.trie = trie s.trie = trie
} }
} }
usedAddrs := make([][]byte, 0, len(s.stateObjectsPending)) // Perform updates before deletions. This prevents resolution of unnecessary trie nodes
for addr := range s.stateObjectsPending { // in circumstances similar to the following:
if obj := s.stateObjects[addr]; obj.deleted { //
s.deleteStateObject(obj) // Consider nodes `A` and `B` who share the same full node parent `P` and have no other siblings.
s.AccountDeleted += 1 // During the execution of a block:
// - `A` self-destructs,
// - `C` is created, and also shares the parent `P`.
// If the self-destruct is handled first, then `P` would be left with only one child, thus collapsed
// into a shortnode. This requires `B` to be resolved from disk.
// Whereas if the created node is handled first, then the collapse is avoided, and `B` is not resolved.
var (
usedAddrs [][]byte
deletedAddrs []common.Address
)
for addr, op := range s.mutations {
if op.applied {
continue
}
op.applied = true
if op.isDelete() {
deletedAddrs = append(deletedAddrs, addr)
} else { } else {
s.updateStateObject(obj) s.updateStateObject(s.stateObjects[addr])
s.AccountUpdated += 1 s.AccountUpdated += 1
} }
usedAddrs = append(usedAddrs, common.CopyBytes(addr[:])) // Copy needed for closure usedAddrs = append(usedAddrs, common.CopyBytes(addr[:])) // Copy needed for closure
} }
for _, deletedAddr := range deletedAddrs {
s.deleteStateObject(deletedAddr)
s.AccountDeleted += 1
}
if prefetcher != nil { if prefetcher != nil {
prefetcher.used(common.Hash{}, s.originalRoot, usedAddrs) prefetcher.used(common.Hash{}, s.originalRoot, usedAddrs)
} }
if len(s.stateObjectsPending) > 0 {
s.stateObjectsPending = make(map[common.Address]struct{})
}
// Track the amount of time wasted on hashing the account trie // Track the amount of time wasted on hashing the account trie
defer func(start time.Time) { s.AccountHashes += time.Since(start) }(time.Now()) defer func(start time.Time) { s.AccountHashes += time.Since(start) }(time.Now())
@ -971,12 +946,10 @@ func (s *StateDB) fastDeleteStorage(addrHash common.Hash, root common.Hash) (com
nodes = trienode.NewNodeSet(addrHash) nodes = trienode.NewNodeSet(addrHash)
slots = make(map[common.Hash][]byte) slots = make(map[common.Hash][]byte)
) )
options := trie.NewStackTrieOptions() stack := trie.NewStackTrie(func(path []byte, hash common.Hash, blob []byte) {
options = options.WithWriter(func(path []byte, hash common.Hash, blob []byte) {
nodes.AddNode(path, trienode.NewDeleted()) nodes.AddNode(path, trienode.NewDeleted())
size += common.StorageSize(len(path)) size += common.StorageSize(len(path))
}) })
stack := trie.NewStackTrie(options)
for iter.Next() { for iter.Next() {
slot := common.CopyBytes(iter.Slot()) slot := common.CopyBytes(iter.Slot())
if err := iter.Error(); err != nil { // error might occur after Slot function if err := iter.Error(); err != nil { // error might occur after Slot function
@ -1143,6 +1116,11 @@ func (s *StateDB) handleDestruction(nodes *trienode.MergedNodeSet) error {
return nil return nil
} }
// GetTrie returns the account trie.
func (s *StateDB) GetTrie() Trie {
return s.trie
}
// Commit writes the state to the underlying in-memory trie database. // Commit writes the state to the underlying in-memory trie database.
// Once the state is committed, tries cached in stateDB (including account // Once the state is committed, tries cached in stateDB (including account
// trie, storage tries) will no longer be functional. A new state instance // trie, storage tries) will no longer be functional. A new state instance
@ -1173,11 +1151,12 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
return common.Hash{}, err return common.Hash{}, err
} }
// Handle all state updates afterwards // Handle all state updates afterwards
for addr := range s.stateObjectsDirty { for addr, op := range s.mutations {
obj := s.stateObjects[addr] if op.isDelete() {
if obj.deleted {
continue continue
} }
obj := s.stateObjects[addr]
// Write any contract code associated with the state object // Write any contract code associated with the state object
if obj.code != nil && obj.dirtyCode { if obj.code != nil && obj.dirtyCode {
rawdb.WriteCode(codeWriter, common.BytesToHash(obj.CodeHash()), obj.code) rawdb.WriteCode(codeWriter, common.BytesToHash(obj.CodeHash()), obj.code)
@ -1277,7 +1256,7 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
s.storages = make(map[common.Hash]map[common.Hash][]byte) s.storages = make(map[common.Hash]map[common.Hash][]byte)
s.accountsOrigin = make(map[common.Address][]byte) s.accountsOrigin = make(map[common.Address][]byte)
s.storagesOrigin = make(map[common.Address]map[common.Hash][]byte) s.storagesOrigin = make(map[common.Address]map[common.Hash][]byte)
s.stateObjectsDirty = make(map[common.Address]struct{}) s.mutations = make(map[common.Address]*mutation)
s.stateObjectsDestruct = make(map[common.Address]*types.StateAccount) s.stateObjectsDestruct = make(map[common.Address]*types.StateAccount)
return root, nil return root, nil
} }
@ -1392,3 +1371,19 @@ func copy2DSet[k comparable](set map[k]map[common.Hash][]byte) map[k]map[common.
} }
return copied return copied
} }
func (s *StateDB) markDelete(addr common.Address) {
if _, ok := s.mutations[addr]; !ok {
s.mutations[addr] = &mutation{}
}
s.mutations[addr].applied = false
s.mutations[addr].typ = deletion
}
func (s *StateDB) markUpdate(addr common.Address) {
if _, ok := s.mutations[addr]; !ok {
s.mutations[addr] = &mutation{}
}
s.mutations[addr].applied = false
s.mutations[addr].typ = update
}

View file

@ -96,7 +96,9 @@ func newStateTestAction(addr common.Address, r *rand.Rand, index int) testAction
{ {
name: "CreateAccount", name: "CreateAccount",
fn: func(a testAction, s *StateDB) { fn: func(a testAction, s *StateDB) {
s.CreateAccount(addr) if !s.Exist(addr) {
s.CreateAccount(addr)
}
}, },
}, },
{ {

View file

@ -225,6 +225,78 @@ func TestCopy(t *testing.T) {
} }
} }
// TestCopyWithDirtyJournal tests if Copy can correct create a equal copied
// stateDB with dirty journal present.
func TestCopyWithDirtyJournal(t *testing.T) {
db := NewDatabase(rawdb.NewMemoryDatabase())
orig, _ := New(types.EmptyRootHash, db, nil)
// Fill up the initial states
for i := byte(0); i < 255; i++ {
obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i}))
obj.AddBalance(uint256.NewInt(uint64(i)), tracing.BalanceChangeUnspecified)
obj.data.Root = common.HexToHash("0xdeadbeef")
orig.updateStateObject(obj)
}
root, _ := orig.Commit(0, true)
orig, _ = New(root, db, nil)
// modify all in memory without finalizing
for i := byte(0); i < 255; i++ {
obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i}))
obj.SubBalance(uint256.NewInt(uint64(i)), tracing.BalanceChangeUnspecified)
orig.updateStateObject(obj)
}
cpy := orig.Copy()
orig.Finalise(true)
for i := byte(0); i < 255; i++ {
root := orig.GetStorageRoot(common.BytesToAddress([]byte{i}))
if root != (common.Hash{}) {
t.Errorf("Unexpected storage root %x", root)
}
}
cpy.Finalise(true)
for i := byte(0); i < 255; i++ {
root := cpy.GetStorageRoot(common.BytesToAddress([]byte{i}))
if root != (common.Hash{}) {
t.Errorf("Unexpected storage root %x", root)
}
}
if cpy.IntermediateRoot(true) != orig.IntermediateRoot(true) {
t.Error("State is not equal after copy")
}
}
// TestCopyObjectState creates an original state, S1, and makes a copy S2.
// It then proceeds to make changes to S1. Those changes are _not_ supposed
// to affect S2. This test checks that the copy properly deep-copies the objectstate
func TestCopyObjectState(t *testing.T) {
db := NewDatabase(rawdb.NewMemoryDatabase())
orig, _ := New(types.EmptyRootHash, db, nil)
// Fill up the initial states
for i := byte(0); i < 5; i++ {
obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i}))
obj.AddBalance(uint256.NewInt(uint64(i)), tracing.BalanceChangeUnspecified)
obj.data.Root = common.HexToHash("0xdeadbeef")
orig.updateStateObject(obj)
}
orig.Finalise(true)
cpy := orig.Copy()
for _, op := range cpy.mutations {
if have, want := op.applied, false; have != want {
t.Fatalf("Error in test itself, the 'done' flag should not be set before Commit, have %v want %v", have, want)
}
}
orig.Commit(0, true)
for _, op := range cpy.mutations {
if have, want := op.applied, false; have != want {
t.Fatalf("Error: original state affected copy, have %v want %v", have, want)
}
}
}
func TestSnapshotRandom(t *testing.T) { func TestSnapshotRandom(t *testing.T) {
config := &quick.Config{MaxCount: 1000} config := &quick.Config{MaxCount: 1000}
err := quick.Check((*snapshotTest).run, config) err := quick.Check((*snapshotTest).run, config)
@ -308,7 +380,30 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction {
{ {
name: "CreateAccount", name: "CreateAccount",
fn: func(a testAction, s *StateDB) { fn: func(a testAction, s *StateDB) {
s.CreateAccount(addr) if !s.Exist(addr) {
s.CreateAccount(addr)
}
},
},
{
name: "CreateContract",
fn: func(a testAction, s *StateDB) {
if !s.Exist(addr) {
s.CreateAccount(addr)
}
contractHash := s.GetCodeHash(addr)
emptyCode := contractHash == (common.Hash{}) || contractHash == types.EmptyCodeHash
storageRoot := s.GetStorageRoot(addr)
emptyStorage := storageRoot == (common.Hash{}) || storageRoot == types.EmptyRootHash
if s.GetNonce(addr) == 0 && emptyCode && emptyStorage {
s.CreateContract(addr)
// We also set some code here, to prevent the
// CreateContract action from being performed twice in a row,
// which would cause a difference in state when unrolling
// the journal. (CreateContact assumes created was false prior to
// invocation, and the journal rollback sets it to false).
s.SetCode(addr, []byte{1})
}
}, },
}, },
{ {
@ -709,18 +804,19 @@ func TestCopyCopyCommitCopy(t *testing.T) {
} }
} }
// TestCommitCopy tests the copy from a committed state is not functional. // TestCommitCopy tests the copy from a committed state is not fully functional.
func TestCommitCopy(t *testing.T) { func TestCommitCopy(t *testing.T) {
state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil) db := NewDatabase(rawdb.NewMemoryDatabase())
state, _ := New(types.EmptyRootHash, db, nil)
// Create an account and check if the retrieved balance is correct // Create an account and check if the retrieved balance is correct
addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe") addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe")
skey := common.HexToHash("aaa") skey1, skey2 := common.HexToHash("a1"), common.HexToHash("a2")
sval := common.HexToHash("bbb") 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")) // Change an external metadata
state.SetState(addr, skey, sval) // 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 {
t.Fatalf("initial balance mismatch: have %v, want %v", balance, 42) t.Fatalf("initial balance mismatch: have %v, want %v", balance, 42)
@ -728,25 +824,38 @@ func TestCommitCopy(t *testing.T) {
if code := state.GetCode(addr); !bytes.Equal(code, []byte("hello")) { if code := state.GetCode(addr); !bytes.Equal(code, []byte("hello")) {
t.Fatalf("initial code mismatch: have %x, want %x", code, []byte("hello")) t.Fatalf("initial code mismatch: have %x, want %x", code, []byte("hello"))
} }
if val := state.GetState(addr, skey); val != sval { if val := state.GetState(addr, skey1); val != sval1 {
t.Fatalf("initial non-committed storage slot mismatch: have %x, want %x", val, sval) t.Fatalf("initial non-committed storage slot mismatch: have %x, want %x", val, sval1)
} }
if val := state.GetCommittedState(addr, skey); val != (common.Hash{}) { if val := state.GetCommittedState(addr, skey1); val != (common.Hash{}) {
t.Fatalf("initial committed storage slot mismatch: have %x, want %x", val, common.Hash{}) t.Fatalf("initial committed storage slot mismatch: have %x, want %x", val, common.Hash{})
} }
// Copy the committed state database, the copied one is not functional. root, _ := state.Commit(0, true)
state.Commit(0, true)
state, _ = New(root, db, nil)
state.SetState(addr, skey2, sval2)
state.Commit(1, true)
// Copy the committed state database, the copied one is not fully functional.
copied := state.Copy() copied := state.Copy()
if balance := copied.GetBalance(addr); balance.Cmp(uint256.NewInt(0)) != 0 { if balance := copied.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 {
t.Fatalf("unexpected balance: have %v", balance) t.Fatalf("unexpected balance: have %v", balance)
} }
if code := copied.GetCode(addr); code != nil { if code := copied.GetCode(addr); !bytes.Equal(code, []byte("hello")) {
t.Fatalf("unexpected code: have %x", code) t.Fatalf("unexpected code: have %x", code)
} }
if val := copied.GetState(addr, skey); val != (common.Hash{}) { // Miss slots because of non-functional trie after commit
if val := copied.GetState(addr, skey1); val != (common.Hash{}) {
t.Fatalf("unexpected storage slot: have %x", sval1)
}
if val := copied.GetCommittedState(addr, skey1); val != (common.Hash{}) {
t.Fatalf("unexpected storage slot: have %x", val) t.Fatalf("unexpected storage slot: have %x", val)
} }
if val := copied.GetCommittedState(addr, skey); val != (common.Hash{}) { // Slots cached in the stateDB, available after commit
if val := copied.GetState(addr, skey2); val != sval2 {
t.Fatalf("unexpected storage slot: have %x", sval1)
}
if val := copied.GetCommittedState(addr, skey2); val != sval2 {
t.Fatalf("unexpected storage slot: have %x", val) t.Fatalf("unexpected storage slot: have %x", val)
} }
if !errors.Is(copied.Error(), trie.ErrCommitted) { if !errors.Is(copied.Error(), trie.ErrCommitted) {
@ -1103,40 +1212,6 @@ func TestStateDBTransientStorage(t *testing.T) {
} }
} }
func TestResetObject(t *testing.T) {
var (
disk = rawdb.NewMemoryDatabase()
tdb = triedb.NewDatabase(disk, nil)
db = NewDatabaseWithNodeDB(disk, tdb)
snaps, _ = snapshot.New(snapshot.Config{CacheSize: 10}, disk, tdb, types.EmptyRootHash)
state, _ = New(types.EmptyRootHash, db, snaps)
addr = common.HexToAddress("0x1")
slotA = common.HexToHash("0x1")
slotB = common.HexToHash("0x2")
)
// Initialize account with balance and storage in first transaction.
state.SetBalance(addr, uint256.NewInt(1), tracing.BalanceChangeUnspecified)
state.SetState(addr, slotA, common.BytesToHash([]byte{0x1}))
state.IntermediateRoot(true)
// Reset account and mutate balance and storages
state.CreateAccount(addr)
state.SetBalance(addr, uint256.NewInt(2), tracing.BalanceChangeUnspecified)
state.SetState(addr, slotB, common.BytesToHash([]byte{0x2}))
root, _ := state.Commit(0, true)
// Ensure the original account is wiped properly
snap := snaps.Snapshot(root)
slot, _ := snap.Storage(crypto.Keccak256Hash(addr.Bytes()), crypto.Keccak256Hash(slotA.Bytes()))
if len(slot) != 0 {
t.Fatalf("Unexpected storage slot")
}
slot, _ = snap.Storage(crypto.Keccak256Hash(addr.Bytes()), crypto.Keccak256Hash(slotB.Bytes()))
if !bytes.Equal(slot, []byte{0x2}) {
t.Fatalf("Unexpected storage slot value %v", slot)
}
}
func TestDeleteStorage(t *testing.T) { func TestDeleteStorage(t *testing.T) {
var ( var (
disk = rawdb.NewMemoryDatabase() disk = rawdb.NewMemoryDatabase()
@ -1178,7 +1253,7 @@ func TestDeleteStorage(t *testing.T) {
t.Fatal("delete should have empty hashes") t.Fatal("delete should have empty hashes")
} }
if len(n.Blob) != 0 { if len(n.Blob) != 0 {
t.Fatal("delete should have have empty blobs") t.Fatal("delete should have empty blobs")
} }
a = append(a, fmt.Sprintf("%x", path)) a = append(a, fmt.Sprintf("%x", path))
}) })

View file

@ -422,3 +422,108 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr
} }
return types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil)) return types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil))
} }
var (
code = common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`)
intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, true, true, true, true)
// A contract creation that calls EXTCODECOPY in the constructor. Used to ensure that the witness
// will not contain that copied data.
// Source: https://gist.github.com/gballet/a23db1e1cb4ed105616b5920feb75985
codeWithExtCodeCopy = common.FromHex(`0x60806040526040516100109061017b565b604051809103906000f08015801561002c573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561007857600080fd5b5060008067ffffffffffffffff8111156100955761009461024a565b5b6040519080825280601f01601f1916602001820160405280156100c75781602001600182028036833780820191505090505b50905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690506020600083833c81610101906101e3565b60405161010d90610187565b61011791906101a3565b604051809103906000f080158015610133573d6000803e3d6000fd5b50600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505061029b565b60d58061046783390190565b6102068061053c83390190565b61019d816101d9565b82525050565b60006020820190506101b86000830184610194565b92915050565b6000819050602082019050919050565b600081519050919050565b6000819050919050565b60006101ee826101ce565b826101f8846101be565b905061020381610279565b925060208210156102435761023e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8360200360080261028e565b831692505b5050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600061028582516101d9565b80915050919050565b600082821b905092915050565b6101bd806102aa6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f566852414610030575b600080fd5b61003861004e565b6040516100459190610146565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381ca91d36040518163ffffffff1660e01b815260040160206040518083038186803b1580156100b857600080fd5b505afa1580156100cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f0919061010a565b905090565b60008151905061010481610170565b92915050565b6000602082840312156101205761011f61016b565b5b600061012e848285016100f5565b91505092915050565b61014081610161565b82525050565b600060208201905061015b6000830184610137565b92915050565b6000819050919050565b600080fd5b61017981610161565b811461018457600080fd5b5056fea2646970667358221220a6a0e11af79f176f9c421b7b12f441356b25f6489b83d38cc828a701720b41f164736f6c63430008070033608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063ab5ed15014602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60006001905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea26469706673582212203a14eb0d5cd07c277d3e24912f110ddda3e553245a99afc4eeefb2fbae5327aa64736f6c63430008070033608060405234801561001057600080fd5b5060405161020638038061020683398181016040528101906100329190610063565b60018160001c6100429190610090565b60008190555050610145565b60008151905061005d8161012e565b92915050565b60006020828403121561007957610078610129565b5b60006100878482850161004e565b91505092915050565b600061009b826100f0565b91506100a6836100f0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156100db576100da6100fa565b5b828201905092915050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b610137816100e6565b811461014257600080fd5b50565b60b3806101536000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806381ca91d314602d575b600080fd5b60336047565b604051603e9190605a565b60405180910390f35b60005481565b6054816073565b82525050565b6000602082019050606d6000830184604d565b92915050565b600081905091905056fea26469706673582212209bff7098a2f526de1ad499866f27d6d0d6f17b74a413036d6063ca6a0998ca4264736f6c63430008070033`)
intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, true, true, true, true)
)
func TestProcessVerkle(t *testing.T) {
var (
config = &params.ChainConfig{
ChainID: big.NewInt(1),
HomesteadBlock: big.NewInt(0),
EIP150Block: big.NewInt(0),
EIP155Block: big.NewInt(0),
EIP158Block: big.NewInt(0),
ByzantiumBlock: big.NewInt(0),
ConstantinopleBlock: big.NewInt(0),
PetersburgBlock: big.NewInt(0),
IstanbulBlock: big.NewInt(0),
MuirGlacierBlock: big.NewInt(0),
BerlinBlock: big.NewInt(0),
LondonBlock: big.NewInt(0),
Ethash: new(params.EthashConfig),
ShanghaiTime: u64(0),
VerkleTime: u64(0),
TerminalTotalDifficulty: common.Big0,
TerminalTotalDifficultyPassed: true,
// TODO uncomment when proof generation is merged
// ProofInBlocks: true,
}
signer = types.LatestSigner(config)
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
bcdb = rawdb.NewMemoryDatabase() // Database for the blockchain
coinbase = common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7")
gspec = &Genesis{
Config: config,
Alloc: GenesisAlloc{
coinbase: GenesisAccount{
Balance: big.NewInt(1000000000000000000), // 1 ether
Nonce: 0,
},
},
}
)
// Verkle trees use the snapshot, which must be enabled before the
// data is saved into the tree+database.
// genesis := gspec.MustCommit(bcdb, triedb)
cacheConfig := DefaultCacheConfigWithScheme("path")
cacheConfig.SnapshotLimit = 0
blockchain, _ := NewBlockChain(bcdb, cacheConfig, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil, nil)
defer blockchain.Stop()
txCost1 := params.TxGas
txCost2 := params.TxGas
contractCreationCost := intrinsicContractCreationGas + uint64(2039 /* execution costs */)
codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + uint64(293644 /* execution costs */)
blockGasUsagesExpected := []uint64{
txCost1*2 + txCost2,
txCost1*2 + txCost2 + contractCreationCost + codeWithExtCodeCopyGas,
}
_, chain, _, _, _ := GenerateVerkleChainWithGenesis(gspec, beacon.New(ethash.NewFaker()), 2, func(i int, gen *BlockGen) {
gen.SetPoS()
// TODO need to check that the tx cost provided is the exact amount used (no remaining left-over)
tx, _ := types.SignTx(types.NewTransaction(uint64(i)*3, common.Address{byte(i), 2, 3}, big.NewInt(999), txCost1, big.NewInt(875000000), nil), signer, testKey)
gen.AddTx(tx)
tx, _ = types.SignTx(types.NewTransaction(uint64(i)*3+1, common.Address{}, big.NewInt(999), txCost1, big.NewInt(875000000), nil), signer, testKey)
gen.AddTx(tx)
tx, _ = types.SignTx(types.NewTransaction(uint64(i)*3+2, common.Address{}, big.NewInt(0), txCost2, big.NewInt(875000000), nil), signer, testKey)
gen.AddTx(tx)
// Add two contract creations in block #2
if i == 1 {
tx, _ = types.SignTx(types.NewContractCreation(6, big.NewInt(16), 3000000, big.NewInt(875000000), code), signer, testKey)
gen.AddTx(tx)
tx, _ = types.SignTx(types.NewContractCreation(7, big.NewInt(0), 3000000, big.NewInt(875000000), codeWithExtCodeCopy), signer, testKey)
gen.AddTx(tx)
}
})
t.Log("inserting blocks into the chain")
endnum, err := blockchain.InsertChain(chain)
if err != nil {
t.Fatalf("block %d imported with error: %v", endnum, err)
}
for i := 0; i < 2; i++ {
b := blockchain.GetBlockByNumber(uint64(i) + 1)
if b == nil {
t.Fatalf("expected block %d to be present in chain", i+1)
}
if b.Hash() != chain[i].Hash() {
t.Fatalf("block #%d not found at expected height", b.NumberU64())
}
if b.GasUsed() != blockGasUsagesExpected[i] {
t.Fatalf("expected block #%d txs to use %d, got %d\n", b.NumberU64(), blockGasUsagesExpected[i], b.GasUsed())
}
}
}

69
core/tracing/CHANGELOG.md Normal file
View file

@ -0,0 +1,69 @@
# Changelog
All notable changes to the tracing interface will be documented in this file.
## [Unreleased]
There has been a major breaking change in the tracing interface for custom native tracers. JS and built-in tracers are not affected by this change and tracing API methods may be used as before. This overhaul has been done as part of the new live tracing feature ([#29189](https://github.com/ethereum/go-ethereum/pull/29189)). To learn more about live tracing please refer to the [docs](https://geth.ethereum.org/docs/developers/evm-tracing/live-tracing).
**The `EVMLogger` interface which the tracers implemented has been removed.** It has been replaced by a new struct `tracing.Hooks`. `Hooks` keeps pointers to event listening functions. Internally the EVM will use these function pointers to emit events and can skip an event if the tracer has opted not to implement it. In fact this is the main reason for this change of approach. Another benefit is the ease of adding new hooks in future, and dynamically assigning event receivers.
The consequence of this change can be seen in the constructor of a tracer. Let's take the 4byte tracer as an example. Previously the constructor return an instance which satisfied the interface. Now it should return a pointer to `tracers.Tracer` (which is now also a struct as opposed to an interface) and explicitly assign the event listeners. As a side-benefit the tracers will not have to provide empty implementation of methods just to satisfy the interface:
```go
func newFourByteTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) {
t := &fourByteTracer{
ids: make(map[string]int),
}
return t, nil
}
```
And now:
```go
func newFourByteTracer(ctx *tracers.Context, _ json.RawMessage) (*tracers.Tracer, error) {
t := &fourByteTracer{
ids: make(map[string]int),
}
return &tracers.Tracer{
Hooks: &tracing.Hooks{
OnTxStart: t.onTxStart,
OnEnter: t.onEnter,
},
GetResult: t.getResult,
Stop: t.stop,
}, nil
}
```
### Event listeners
If you have sharp eyes you might have noticed the new names for `OnTxStart` and `OnEnter`, previously called `CaptureTxStart` and `CaptureEnter`. Indeed there have been various modifications to the signatures of the event listeners. All method names now follow the `On*` pattern instead of `Capture*`. However the modifications are not limited to the names.
#### New methods
The live tracing feature was half about adding more observability into the state of the blockchain. As such there have been a host of method additions. Please consult the [Hooks](./hooks.go) struct for the full list of methods. Custom tracers which are invoked through the API (as opposed to "live" tracers) can benefit from the following new methods:
- `OnGasChange(old, new uint64, reason GasChangeReason)`: This hook tracks the lifetime of gas within a transaction and its subcalls. It will first track the initial purchase of gas with ether, then the following consumptions and refunds of gas until at the end the rest is returned.
- `OnBalanceChange(addr common.Address, prev, new *big.Int, reason BalanceChangeReason)`: This hook tracks the balance changes of accounts. Where possible a reason is provided for the change (e.g. a transfer, gas purchase, withdrawal deposit etc).
- `OnNonceChange(addr common.Address, prev, new uint64)`: This hook tracks the nonce changes of accounts.
- `OnCodeChange(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte)`: This hook tracks the code changes of accounts.
- `OnStorageChange(addr common.Address, slot common.Hash, prev, new common.Hash)`: This hook tracks the storage changes of accounts.
- `OnLogChange(log *types.Log)`: This hook tracks the logs emitted by the EVM.
#### Removed methods
The hooks `CaptureStart` and `CaptureEnd` have been removed. These hooks signaled the top-level call frame of a transaction. The relevant info will be now emitted by `OnEnter` and `OnExit` which are emitted for every call frame. They now contain a `depth` parameter which can be used to distinguish the top-level call frame when necessary. The `create bool` parameter to `CaptureStart` can now be inferred from `typ byte` in `OnEnter`, i.e. `vm.OpCode(typ) == vm.CREATE`.
#### Modified methods
- `CaptureTxStart` -> `OnTxStart(vm *VMContext, tx *types.Transaction, from common.Address)`. It now emits the full transaction object as well as `from` which should be used to get the sender address. The `*VMContext` is a replacement for the `*vm.EVM` object previously passed to `CaptureStart`.
- `CaptureTxEnd` -> `OnTxEnd(receipt *types.Receipt, err error)`. It now returns the full receipt object.
- `CaptureEnter` -> `OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int)`. The new `depth int` parameter indicates the call stack depth. It is 0 for the top-level call. Furthermore, the location where `OnEnter` is called in the EVM is now made a soon as a call is started. This means some specific error cases that were not before calling `OnEnter/OnExit` will now do so, leading some transaction to have an extra call traced.
- `CaptureExit` -> `OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool)`. It has the new `depth` parameter, same as `OnEnter`. The new `reverted` parameter indicates whether the call frame was reverted.
- `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.
[unreleased]: https://github.com/ethereum/go-ethereum/compare/v1.13.14...master

View file

@ -107,8 +107,8 @@ type (
// BlockchainInitHook is called when the blockchain is initialized. // BlockchainInitHook is called when the blockchain is initialized.
BlockchainInitHook = func(chainConfig *params.ChainConfig) BlockchainInitHook = func(chainConfig *params.ChainConfig)
// BlockchainTerminateHook is called when the tracer is terminated. // CloseHook is called when the blockchain closes.
BlockchainTerminateHook = func() CloseHook = func()
// BlockStartHook is called before executing `block`. // BlockStartHook is called before executing `block`.
// `td` is the total difficulty prior to `block`. // `td` is the total difficulty prior to `block`.
@ -155,12 +155,12 @@ type Hooks struct {
OnFault FaultHook OnFault FaultHook
OnGasChange GasChangeHook OnGasChange GasChangeHook
// Chain events // Chain events
OnBlockchainInit BlockchainInitHook OnBlockchainInit BlockchainInitHook
OnBlockchainTerminate BlockchainTerminateHook OnClose CloseHook
OnBlockStart BlockStartHook OnBlockStart BlockStartHook
OnBlockEnd BlockEndHook OnBlockEnd BlockEndHook
OnSkippedBlock SkippedBlockHook OnSkippedBlock SkippedBlockHook
OnGenesisBlock GenesisBlockHook OnGenesisBlock GenesisBlockHook
// State events // State events
OnBalanceChange BalanceChangeHook OnBalanceChange BalanceChangeHook
OnNonceChange NonceChangeHook OnNonceChange NonceChangeHook

View file

@ -1226,7 +1226,7 @@ func (p *BlobPool) Add(txs []*types.Transaction, local bool, sync bool) []error
return errs return errs
} }
// Add inserts a new blob transaction into the pool if it passes validation (both // add inserts a new blob transaction into the pool if it passes validation (both
// consensus validity and pool restrictions). // consensus validity and pool restrictions).
func (p *BlobPool) add(tx *types.Transaction) (err error) { func (p *BlobPool) add(tx *types.Transaction) (err error) {
// The blob pool blocks on adding a transaction. This is because blob txs are // The blob pool blocks on adding a transaction. This is because blob txs are

View file

@ -60,7 +60,7 @@ func newLimbo(datadir string) (*limbo, error) {
fails = append(fails, id) fails = append(fails, id)
} }
} }
store, err := billy.Open(billy.Options{Path: datadir}, newSlotter(), index) store, err := billy.Open(billy.Options{Path: datadir, Repair: true}, newSlotter(), index)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -197,8 +197,8 @@ type Block struct {
withdrawals Withdrawals withdrawals Withdrawals
// caches // caches
hash atomic.Value hash atomic.Pointer[common.Hash]
size atomic.Value size atomic.Uint64
// These fields are used by package eth to track // These fields are used by package eth to track
// inter-peer block relay. // inter-peer block relay.
@ -406,8 +406,8 @@ func (b *Block) BlobGasUsed() *uint64 {
// Size returns the true RLP encoded storage size of the block, either by encoding // Size returns the true RLP encoded storage size of the block, either by encoding
// and returning it, or returning a previously cached value. // and returning it, or returning a previously cached value.
func (b *Block) Size() uint64 { func (b *Block) Size() uint64 {
if size := b.size.Load(); size != nil { if size := b.size.Load(); size > 0 {
return size.(uint64) return size
} }
c := writeCounter(0) c := writeCounter(0)
rlp.Encode(&c, b) rlp.Encode(&c, b)
@ -486,11 +486,11 @@ func (b *Block) WithWithdrawals(withdrawals []*Withdrawal) *Block {
// The hash is computed on the first call and cached thereafter. // The hash is computed on the first call and cached thereafter.
func (b *Block) Hash() common.Hash { func (b *Block) Hash() common.Hash {
if hash := b.hash.Load(); hash != nil { if hash := b.hash.Load(); hash != nil {
return hash.(common.Hash) return *hash
} }
v := b.header.Hash() h := b.header.Hash()
b.hash.Store(v) b.hash.Store(&h)
return v return h
} }
type Blocks []*Block type Blocks []*Block

View file

@ -57,9 +57,9 @@ type Transaction struct {
time time.Time // Time first seen locally (spam avoidance) time time.Time // Time first seen locally (spam avoidance)
// caches // caches
hash atomic.Value hash atomic.Pointer[common.Hash]
size atomic.Value size atomic.Uint64
from atomic.Value from atomic.Pointer[sigCache]
} }
// NewTx creates a new transaction. // NewTx creates a new transaction.
@ -446,6 +446,26 @@ func (tx *Transaction) WithoutBlobTxSidecar() *Transaction {
return cpy return cpy
} }
// WithBlobTxSidecar returns a copy of tx with the blob sidecar added.
func (tx *Transaction) WithBlobTxSidecar(sideCar *BlobTxSidecar) *Transaction {
blobtx, ok := tx.inner.(*BlobTx)
if !ok {
return tx
}
cpy := &Transaction{
inner: blobtx.withSidecar(sideCar),
time: tx.time,
}
// Note: tx.size cache not carried over because the sidecar is included in size!
if h := tx.hash.Load(); h != nil {
cpy.hash.Store(h)
}
if f := tx.from.Load(); f != nil {
cpy.from.Store(f)
}
return cpy
}
// SetTime sets the decoding time of a transaction. This is used by tests to set // SetTime sets the decoding time of a transaction. This is used by tests to set
// arbitrary times and by persistent transaction pools when loading old txs from // arbitrary times and by persistent transaction pools when loading old txs from
// disk. // disk.
@ -462,7 +482,7 @@ func (tx *Transaction) Time() time.Time {
// Hash returns the transaction hash. // Hash returns the transaction hash.
func (tx *Transaction) Hash() common.Hash { func (tx *Transaction) Hash() common.Hash {
if hash := tx.hash.Load(); hash != nil { if hash := tx.hash.Load(); hash != nil {
return hash.(common.Hash) return *hash
} }
var h common.Hash var h common.Hash
@ -471,15 +491,15 @@ func (tx *Transaction) Hash() common.Hash {
} else { } else {
h = prefixedRlpHash(tx.Type(), tx.inner) h = prefixedRlpHash(tx.Type(), tx.inner)
} }
tx.hash.Store(h) tx.hash.Store(&h)
return h return h
} }
// Size returns the true encoded storage size of the transaction, either by encoding // Size returns the true encoded storage size of the transaction, either by encoding
// and returning it, or returning a previously cached value. // and returning it, or returning a previously cached value.
func (tx *Transaction) Size() uint64 { func (tx *Transaction) Size() uint64 {
if size := tx.size.Load(); size != nil { if size := tx.size.Load(); size > 0 {
return size.(uint64) return size
} }
// Cache miss, encode and cache. // Cache miss, encode and cache.

View file

@ -128,8 +128,7 @@ func MustSignNewTx(prv *ecdsa.PrivateKey, s Signer, txdata TxData) *Transaction
// signing method. The cache is invalidated if the cached signer does // signing method. The cache is invalidated if the cached signer does
// not match the signer used in the current call. // not match the signer used in the current call.
func Sender(signer Signer, tx *Transaction) (common.Address, error) { func Sender(signer Signer, tx *Transaction) (common.Address, error) {
if sc := tx.from.Load(); sc != nil { if sigCache := tx.from.Load(); sigCache != nil {
sigCache := sc.(sigCache)
// If the signer used to derive from in a previous // If the signer used to derive from in a previous
// call is not the same as used current, invalidate // call is not the same as used current, invalidate
// the cache. // the cache.
@ -142,7 +141,7 @@ func Sender(signer Signer, tx *Transaction) (common.Address, error) {
if err != nil { if err != nil {
return common.Address{}, err return common.Address{}, err
} }
tx.from.Store(sigCache{signer: signer, from: addr}) tx.from.Store(&sigCache{signer: signer, from: addr})
return addr, nil return addr, nil
} }

View file

@ -22,6 +22,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"maps"
"math/big" "math/big"
"reflect" "reflect"
"testing" "testing"
@ -515,10 +516,7 @@ func TestYParityJSONUnmarshalling(t *testing.T) {
test := test test := test
t.Run(fmt.Sprintf("txType=%d: %s", txType, test.name), func(t *testing.T) { t.Run(fmt.Sprintf("txType=%d: %s", txType, test.name), func(t *testing.T) {
// Copy the base json // Copy the base json
testJson := make(map[string]interface{}) testJson := maps.Clone(baseJson)
for k, v := range baseJson {
testJson[k] = v
}
// Set v, yParity and type // Set v, yParity and type
if test.v != "" { if test.v != "" {

View file

@ -191,6 +191,12 @@ func (tx *BlobTx) withoutSidecar() *BlobTx {
return &cpy return &cpy
} }
func (tx *BlobTx) withSidecar(sideCar *BlobTxSidecar) *BlobTx {
cpy := *tx
cpy.Sidecar = sideCar
return &cpy
}
func (tx *BlobTx) encode(b *bytes.Buffer) error { func (tx *BlobTx) encode(b *bytes.Buffer) error {
if tx.Sidecar == nil { if tx.Sidecar == nil {
return rlp.Encode(b, tx) return rlp.Encode(b, tx)

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