mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 18:32:23 +00:00
Merge branch 'master' into supply-delta-live-tracer
# Conflicts: # core/tracing/hooks.go
This commit is contained in:
commit
a6541d2e57
314 changed files with 5429 additions and 12471 deletions
|
|
@ -127,7 +127,7 @@ func (arguments Arguments) Copy(v interface{}, values []interface{}) error {
|
|||
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 {
|
||||
dst := reflect.ValueOf(v).Elem()
|
||||
src := reflect.ValueOf(marshalledValues)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import (
|
|||
"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:
|
||||
//
|
||||
// var fields []reflect.StructField
|
||||
|
|
@ -33,7 +33,7 @@ import (
|
|||
// Name: "X",
|
||||
// Type: reflect.TypeOf(new(big.Int)),
|
||||
// Tag: reflect.StructTag("json:\"" + "x" + "\""),
|
||||
// }
|
||||
// })
|
||||
//
|
||||
// into:
|
||||
//
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import (
|
|||
"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.
|
||||
type typeWithoutStringer Type
|
||||
|
||||
|
|
|
|||
15
accounts/external/backend.go
vendored
15
accounts/external/backend.go
vendored
|
|
@ -205,7 +205,7 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio
|
|||
to = &t
|
||||
}
|
||||
args := &apitypes.SendTxArgs{
|
||||
Data: &data,
|
||||
Input: &data,
|
||||
Nonce: hexutil.Uint64(tx.Nonce()),
|
||||
Value: hexutil.Big(*tx.Value()),
|
||||
Gas: hexutil.Uint64(tx.Gas()),
|
||||
|
|
@ -215,7 +215,7 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio
|
|||
switch tx.Type() {
|
||||
case types.LegacyTxType, types.AccessListTxType:
|
||||
args.GasPrice = (*hexutil.Big)(tx.GasPrice())
|
||||
case types.DynamicFeeTxType:
|
||||
case types.DynamicFeeTxType, types.BlobTxType:
|
||||
args.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap())
|
||||
args.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap())
|
||||
default:
|
||||
|
|
@ -235,6 +235,17 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio
|
|||
accessList := tx.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
|
||||
if err := api.client.Call(&res, "account_signTransaction", args); err != nil {
|
||||
return nil, err
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
// On systems where file watch is not supported, just return "ok".
|
||||
if !ks.cache.watcher.enabled() {
|
||||
|
|
|
|||
|
|
@ -343,7 +343,7 @@ func TestWalletNotifications(t *testing.T) {
|
|||
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) {
|
||||
t.Parallel()
|
||||
_, 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) {
|
||||
t.Parallel()
|
||||
_, ks := tmpKeyStore(t)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package blsync
|
|||
import (
|
||||
"github.com/ethereum/go-ethereum/beacon/light/request"
|
||||
"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/common"
|
||||
"github.com/ethereum/go-ethereum/common/lru"
|
||||
|
|
@ -40,7 +41,7 @@ type beaconBlockSync struct {
|
|||
|
||||
type headTracker interface {
|
||||
PrefetchHead() types.HeadInfo
|
||||
ValidatedHead() (types.SignedHeader, bool)
|
||||
ValidatedOptimistic() (types.OptimisticUpdate, 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:
|
||||
sid, req, resp := event.RequestInfo()
|
||||
blockRoot := common.Hash(req.(sync.ReqBeaconBlock))
|
||||
log.Debug("Beacon block event", "type", event.Type.Name, "hash", blockRoot)
|
||||
if resp != nil {
|
||||
s.recentBlocks.Add(blockRoot, resp.(*types.BeaconBlock))
|
||||
}
|
||||
|
|
@ -79,8 +81,8 @@ func (s *beaconBlockSync) Process(requester request.Requester, events []request.
|
|||
}
|
||||
s.updateEventFeed()
|
||||
// request validated head block if unavailable and not yet requested
|
||||
if vh, ok := s.headTracker.ValidatedHead(); ok {
|
||||
s.tryRequestBlock(requester, vh.Header.Hash(), false)
|
||||
if vh, ok := s.headTracker.ValidatedOptimistic(); ok {
|
||||
s.tryRequestBlock(requester, vh.Attested.Hash(), false)
|
||||
}
|
||||
// request prefetch head if the given server has announced it
|
||||
if prefetchHead := s.headTracker.PrefetchHead().BlockRoot; prefetchHead != (common.Hash{}) {
|
||||
|
|
@ -113,19 +115,34 @@ func blockHeadInfo(block *types.BeaconBlock) types.HeadInfo {
|
|||
}
|
||||
|
||||
func (s *beaconBlockSync) updateEventFeed() {
|
||||
head, ok := s.headTracker.ValidatedHead()
|
||||
optimistic, ok := s.headTracker.ValidatedOptimistic()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
finality, ok := s.headTracker.ValidatedFinality() //TODO fetch directly if subscription does not deliver
|
||||
if !ok || head.Header.Epoch() != finality.Attested.Header.Epoch() {
|
||||
return
|
||||
}
|
||||
validatedHead := head.Header.Hash()
|
||||
|
||||
validatedHead := optimistic.Attested.Hash()
|
||||
headBlock, ok := s.recentBlocks.Get(validatedHead)
|
||||
if !ok {
|
||||
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)
|
||||
if headInfo == s.lastHeadInfo {
|
||||
return
|
||||
|
|
@ -139,8 +156,8 @@ func (s *beaconBlockSync) updateEventFeed() {
|
|||
return
|
||||
}
|
||||
s.chainHeadFeed.Send(types.ChainHeadEvent{
|
||||
BeaconHead: head.Header,
|
||||
BeaconHead: optimistic.Attested.Header,
|
||||
Block: execBlock,
|
||||
Finalized: finality.Finalized.PayloadHeader.BlockHash(),
|
||||
Finalized: finalizedHash,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
testServer1 = "testServer1"
|
||||
testServer2 = "testServer2"
|
||||
testServer1 = testServer("testServer1")
|
||||
testServer2 = testServer("testServer2")
|
||||
|
||||
testBlock1 = types.NewBeaconBlock(&deneb.BeaconBlock{
|
||||
Slot: 123,
|
||||
|
|
@ -51,6 +51,12 @@ var (
|
|||
})
|
||||
)
|
||||
|
||||
type testServer string
|
||||
|
||||
func (t testServer) Name() string {
|
||||
return string(t)
|
||||
}
|
||||
|
||||
func TestBlockSync(t *testing.T) {
|
||||
ht := &testHeadTracker{}
|
||||
blockSync := newBeaconBlockSync(ht)
|
||||
|
|
@ -134,8 +140,12 @@ func (h *testHeadTracker) PrefetchHead() types.HeadInfo {
|
|||
return h.prefetch
|
||||
}
|
||||
|
||||
func (h *testHeadTracker) ValidatedHead() (types.SignedHeader, bool) {
|
||||
return h.validated, h.validated.Header != (types.Header{})
|
||||
func (h *testHeadTracker) ValidatedOptimistic() (types.OptimisticUpdate, bool) {
|
||||
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
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ func (ec *engineClient) updateLoop(headCh <-chan types.ChainHeadEvent) {
|
|||
for {
|
||||
select {
|
||||
case <-ec.rootCtx.Done():
|
||||
log.Debug("Stopping engine API update loop")
|
||||
return
|
||||
|
||||
case event := <-headCh:
|
||||
|
|
@ -73,12 +74,14 @@ func (ec *engineClient) updateLoop(headCh <-chan types.ChainHeadEvent) {
|
|||
fork := ec.config.ForkAtEpoch(event.BeaconHead.Epoch())
|
||||
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 {
|
||||
log.Info("Successful NewPayload", "number", event.Block.NumberU64(), "hash", event.Block.Hash(), "status", status)
|
||||
} else {
|
||||
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 {
|
||||
log.Info("Successful ForkchoiceUpdated", "head", event.Block.Hash(), "status", status)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package engine
|
|||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"slices"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"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.
|
||||
func (b PayloadID) Is(versions ...PayloadVersion) bool {
|
||||
for _, v := range versions {
|
||||
if v == b.Version() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return slices.Contains(versions, b.Version())
|
||||
}
|
||||
|
||||
func (b PayloadID) String() string {
|
||||
|
|
@ -313,7 +309,7 @@ const (
|
|||
// ClientVersionV1 contains information which identifies a client implementation.
|
||||
type ClientVersionV1 struct {
|
||||
Code string `json:"code"`
|
||||
Name string `json:"clientName"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Commit string `json:"commit"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,13 +46,13 @@ func (s *ApiServer) Subscribe(eventCallback func(event request.Event)) {
|
|||
log.Debug("New head received", "slot", slot, "blockRoot", blockRoot)
|
||||
eventCallback(request.Event{Type: sync.EvNewHead, Data: types.HeadInfo{Slot: slot, BlockRoot: blockRoot}})
|
||||
},
|
||||
OnSignedHead: func(head types.SignedHeader) {
|
||||
log.Debug("New signed head received", "slot", head.Header.Slot, "blockRoot", head.Header.Hash(), "signerCount", head.Signature.SignerCount())
|
||||
eventCallback(request.Event{Type: sync.EvNewSignedHead, Data: head})
|
||||
OnOptimistic: func(update types.OptimisticUpdate) {
|
||||
log.Debug("New optimistic update received", "slot", update.Attested.Slot, "blockRoot", update.Attested.Hash(), "signerCount", update.Signature.SignerCount())
|
||||
eventCallback(request.Event{Type: sync.EvNewOptimisticUpdate, Data: update})
|
||||
},
|
||||
OnFinality: func(head types.FinalityUpdate) {
|
||||
log.Debug("New finality update received", "slot", head.Attested.Slot, "blockRoot", head.Attested.Hash(), "signerCount", head.Signature.SignerCount())
|
||||
eventCallback(request.Event{Type: sync.EvNewFinalityUpdate, Data: head})
|
||||
OnFinality: func(update types.FinalityUpdate) {
|
||||
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: update})
|
||||
},
|
||||
OnError: func(err error) {
|
||||
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)
|
||||
resp = r
|
||||
case sync.ReqHeader:
|
||||
var r sync.RespHeader
|
||||
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:
|
||||
log.Debug("Beacon API: requesting checkpoint data", "reqid", id, "hash", common.Hash(data))
|
||||
resp, err = s.api.GetCheckpointData(common.Hash(data))
|
||||
case sync.ReqBeaconBlock:
|
||||
log.Debug("Beacon API: requesting block", "reqid", id, "hash", 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:
|
||||
}
|
||||
|
||||
|
|
@ -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)
|
||||
s.eventCallback(request.Event{Type: request.EvFail, Data: request.RequestResponse{ID: id, Request: req}})
|
||||
} 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}})
|
||||
}
|
||||
}()
|
||||
|
|
@ -101,3 +107,8 @@ func (s *ApiServer) Unsubscribe() {
|
|||
s.unsubscribe = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Name implements request.Server
|
||||
func (s *ApiServer) Name() string {
|
||||
return s.api.url
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/beacon/types"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -184,46 +185,56 @@ func (api *BeaconLightApi) GetBestUpdatesAndCommittees(firstPeriod, count uint64
|
|||
return updates, committees, nil
|
||||
}
|
||||
|
||||
// GetOptimisticHeadUpdate fetches a signed header based on the latest available
|
||||
// optimistic update. Note that the signature should be verified by the caller
|
||||
// as its validity depends on the update chain.
|
||||
// GetOptimisticUpdate fetches the latest available optimistic update.
|
||||
// Note that the signature should be verified by the caller as its validity
|
||||
// depends on the update chain.
|
||||
//
|
||||
// See data structure definition here:
|
||||
// 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")
|
||||
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 {
|
||||
Data struct {
|
||||
Header jsonBeaconHeader `json:"attested_header"`
|
||||
Aggregate types.SyncAggregate `json:"sync_aggregate"`
|
||||
SignatureSlot common.Decimal `json:"signature_slot"`
|
||||
Version string
|
||||
Data struct {
|
||||
Attested jsonHeaderWithExecProof `json:"attested_header"`
|
||||
Aggregate types.SyncAggregate `json:"sync_aggregate"`
|
||||
SignatureSlot common.Decimal `json:"signature_slot"`
|
||||
} `json:"data"`
|
||||
}
|
||||
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
|
||||
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 {
|
||||
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 {
|
||||
return types.SignedHeader{}, errors.New("invalid sync_committee_signature length")
|
||||
return types.OptimisticUpdate{}, errors.New("invalid sync_committee_signature length")
|
||||
}
|
||||
return types.SignedHeader{
|
||||
Header: data.Data.Header.Beacon,
|
||||
return types.OptimisticUpdate{
|
||||
Attested: types.HeaderWithExecProof{
|
||||
Header: data.Data.Attested.Beacon,
|
||||
PayloadHeader: attestedExecHeader,
|
||||
PayloadBranch: data.Data.Attested.ExecutionBranch,
|
||||
},
|
||||
Signature: data.Data.Aggregate,
|
||||
SignatureSlot: uint64(data.Data.SignatureSlot),
|
||||
}, nil
|
||||
|
|
@ -289,9 +300,11 @@ func decodeFinalityUpdate(enc []byte) (types.FinalityUpdate, error) {
|
|||
}, 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.
|
||||
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
|
||||
if blockRoot == (common.Hash{}) {
|
||||
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)
|
||||
if err != nil {
|
||||
return types.Header{}, err
|
||||
return types.Header{}, false, false, err
|
||||
}
|
||||
|
||||
var data struct {
|
||||
Data struct {
|
||||
Finalized bool `json:"finalized"`
|
||||
Data struct {
|
||||
Root common.Hash `json:"root"`
|
||||
Canonical bool `json:"canonical"`
|
||||
Header struct {
|
||||
|
|
@ -314,16 +328,16 @@ func (api *BeaconLightApi) GetHeader(blockRoot common.Hash) (types.Header, error
|
|||
} `json:"data"`
|
||||
}
|
||||
if err := json.Unmarshal(resp, &data); err != nil {
|
||||
return types.Header{}, err
|
||||
return types.Header{}, false, false, err
|
||||
}
|
||||
header := data.Data.Header.Message
|
||||
if blockRoot == (common.Hash{}) {
|
||||
blockRoot = data.Data.Root
|
||||
}
|
||||
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.
|
||||
|
|
@ -408,7 +422,7 @@ func decodeHeadEvent(enc []byte) (uint64, common.Hash, error) {
|
|||
|
||||
type HeadEventListener struct {
|
||||
OnNewHead func(slot uint64, blockRoot common.Hash)
|
||||
OnSignedHead func(head types.SignedHeader)
|
||||
OnOptimistic func(head types.OptimisticUpdate)
|
||||
OnFinality func(head types.FinalityUpdate)
|
||||
OnError func(err error)
|
||||
}
|
||||
|
|
@ -446,21 +460,35 @@ func (api *BeaconLightApi) StartHeadListener(listener HeadEventListener) func()
|
|||
defer wg.Done()
|
||||
|
||||
// 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())
|
||||
} else {
|
||||
log.Debug("Failed to retrieve initial head header", "error", err)
|
||||
}
|
||||
if signedHead, err := api.GetOptimisticHeadUpdate(); err == nil {
|
||||
listener.OnSignedHead(signedHead)
|
||||
log.Trace("Requesting initial optimistic update")
|
||||
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 {
|
||||
log.Trace("Retrieved initial finality update", "slot", finalityUpdate.Finalized.Slot, "hash", finalityUpdate.Finalized.Hash())
|
||||
listener.OnFinality(finalityUpdate)
|
||||
} else {
|
||||
log.Debug("Failed to retrieve initial finality update", "error", err)
|
||||
}
|
||||
|
||||
log.Trace("Starting event stream processing loop")
|
||||
// Receive the stream.
|
||||
var stream *eventsource.Stream
|
||||
select {
|
||||
case stream = <-streamCh:
|
||||
case <-ctx.Done():
|
||||
log.Trace("Stopping event stream processing loop")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -471,8 +499,10 @@ func (api *BeaconLightApi) StartHeadListener(listener HeadEventListener) func()
|
|||
|
||||
case event, ok := <-stream.Events:
|
||||
if !ok {
|
||||
log.Trace("Event stream closed")
|
||||
return
|
||||
}
|
||||
log.Trace("New event received from event stream", "type", event.Event())
|
||||
switch event.Event() {
|
||||
case "head":
|
||||
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))
|
||||
}
|
||||
case "light_client_optimistic_update":
|
||||
signedHead, err := decodeOptimisticHeadUpdate([]byte(event.Data()))
|
||||
optimisticUpdate, err := decodeOptimisticUpdate([]byte(event.Data()))
|
||||
if err == nil {
|
||||
listener.OnSignedHead(signedHead)
|
||||
listener.OnOptimistic(optimisticUpdate)
|
||||
} else {
|
||||
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.
|
||||
func (api *BeaconLightApi) startEventStream(ctx context.Context, listener *HeadEventListener) *eventsource.Stream {
|
||||
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)
|
||||
if err != nil {
|
||||
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))
|
||||
continue
|
||||
}
|
||||
log.Trace("Successfully created event stream")
|
||||
return stream
|
||||
}
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -29,15 +29,15 @@ import (
|
|||
// which is the (not necessarily validated) head announced by the majority of
|
||||
// servers.
|
||||
type HeadTracker struct {
|
||||
lock sync.RWMutex
|
||||
committeeChain *CommitteeChain
|
||||
minSignerCount int
|
||||
signedHead types.SignedHeader
|
||||
hasSignedHead bool
|
||||
finalityUpdate types.FinalityUpdate
|
||||
hasFinalityUpdate bool
|
||||
prefetchHead types.HeadInfo
|
||||
changeCounter uint64
|
||||
lock sync.RWMutex
|
||||
committeeChain *CommitteeChain
|
||||
minSignerCount int
|
||||
optimisticUpdate types.OptimisticUpdate
|
||||
hasOptimisticUpdate bool
|
||||
finalityUpdate types.FinalityUpdate
|
||||
hasFinalityUpdate bool
|
||||
prefetchHead types.HeadInfo
|
||||
changeCounter uint64
|
||||
}
|
||||
|
||||
// NewHeadTracker creates a new HeadTracker.
|
||||
|
|
@ -48,15 +48,15 @@ func NewHeadTracker(committeeChain *CommitteeChain, minSignerCount int) *HeadTra
|
|||
}
|
||||
}
|
||||
|
||||
// ValidatedHead returns the latest validated head.
|
||||
func (h *HeadTracker) ValidatedHead() (types.SignedHeader, bool) {
|
||||
// ValidatedOptimistic returns the latest validated optimistic update.
|
||||
func (h *HeadTracker) ValidatedOptimistic() (types.OptimisticUpdate, bool) {
|
||||
h.lock.RLock()
|
||||
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) {
|
||||
h.lock.RLock()
|
||||
defer h.lock.RUnlock()
|
||||
|
|
@ -64,26 +64,36 @@ func (h *HeadTracker) ValidatedFinality() (types.FinalityUpdate, bool) {
|
|||
return h.finalityUpdate, h.hasFinalityUpdate
|
||||
}
|
||||
|
||||
// Validate validates the given signed head. If the head is successfully validated
|
||||
// and it is better than the old validated head (higher slot or same slot and more
|
||||
// signers) then ValidatedHead is updated. The boolean return flag signals if
|
||||
// ValidatedHead has been changed.
|
||||
func (h *HeadTracker) ValidateHead(head types.SignedHeader) (bool, error) {
|
||||
// ValidateOptimistic validates the given optimistic 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 ValidatedOptimistic is updated.
|
||||
// The boolean return flag signals if ValidatedOptimistic has been changed.
|
||||
func (h *HeadTracker) ValidateOptimistic(update types.OptimisticUpdate) (bool, error) {
|
||||
h.lock.Lock()
|
||||
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 {
|
||||
h.signedHead, h.hasSignedHead = head, true
|
||||
h.optimisticUpdate, h.hasOptimisticUpdate = update, true
|
||||
h.changeCounter++
|
||||
}
|
||||
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) {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
|
||||
if err := update.Validate(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
replace, err := h.validate(update.SignedHeader(), h.finalityUpdate.SignedHeader())
|
||||
if replace {
|
||||
h.finalityUpdate, h.hasFinalityUpdate = update, true
|
||||
|
|
@ -142,6 +152,7 @@ func (h *HeadTracker) SetPrefetchHead(head types.HeadInfo) {
|
|||
h.changeCounter++
|
||||
}
|
||||
|
||||
// ChangeCounter implements request.targetData
|
||||
func (h *HeadTracker) ChangeCounter() uint64 {
|
||||
h.lock.RLock()
|
||||
defer h.lock.RUnlock()
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ type Requester interface {
|
|||
// allow new operations.
|
||||
type Scheduler struct {
|
||||
lock sync.Mutex
|
||||
modules []Module // first has highest priority
|
||||
modules []Module // first has the highest priority
|
||||
names map[Module]string
|
||||
servers map[server]struct{}
|
||||
targets map[targetData]uint64
|
||||
|
|
@ -93,7 +93,9 @@ type (
|
|||
// the modules that do not interact with them directly.
|
||||
// In order to make module testing easier, Server interface is used in
|
||||
// events and modules.
|
||||
Server any
|
||||
Server interface {
|
||||
Name() string
|
||||
}
|
||||
Request any
|
||||
Response any
|
||||
ID uint64
|
||||
|
|
|
|||
|
|
@ -70,6 +70,10 @@ type testServer struct {
|
|||
canRequest int
|
||||
}
|
||||
|
||||
func (s *testServer) Name() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *testServer) subscribe(eventCb func(Event)) {
|
||||
s.eventCb = eventCb
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ const (
|
|||
// EvResponse or EvFail. Additionally, it may also send application-defined
|
||||
// events that the Modules can interpret.
|
||||
type requestServer interface {
|
||||
Name() string
|
||||
Subscribe(eventCallback func(Event))
|
||||
SendRequest(ID, Request)
|
||||
Unsubscribe()
|
||||
|
|
@ -69,6 +70,7 @@ type requestServer interface {
|
|||
// limit the number of parallel in-flight requests and temporarily disable
|
||||
// new requests based on timeouts and response failures.
|
||||
type server interface {
|
||||
Server
|
||||
subscribe(eventCallback func(Event))
|
||||
canRequestNow() bool
|
||||
sendRequest(Request) ID
|
||||
|
|
@ -138,6 +140,11 @@ type serverWithTimeout struct {
|
|||
lastID ID
|
||||
}
|
||||
|
||||
// Name implements request.Server
|
||||
func (s *serverWithTimeout) Name() string {
|
||||
return s.parent.Name()
|
||||
}
|
||||
|
||||
// init initializes serverWithTimeout
|
||||
func (s *serverWithTimeout) init(clock mclock.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() {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
|
@ -337,7 +344,7 @@ func (s *serverWithLimits) sendRequest(request Request) (reqId ID) {
|
|||
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() {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
|
|
|||
|
|
@ -153,6 +153,7 @@ type testRequestServer struct {
|
|||
eventCb func(Event)
|
||||
}
|
||||
|
||||
func (rs *testRequestServer) Name() string { return "" }
|
||||
func (rs *testRequestServer) Subscribe(eventCb func(Event)) { rs.eventCb = eventCb }
|
||||
func (rs *testRequestServer) SendRequest(ID, Request) {}
|
||||
func (rs *testRequestServer) Unsubscribe() {}
|
||||
|
|
|
|||
|
|
@ -19,11 +19,13 @@ package sync
|
|||
import (
|
||||
"github.com/ethereum/go-ethereum/beacon/light/request"
|
||||
"github.com/ethereum/go-ethereum/beacon/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
type headTracker interface {
|
||||
ValidateHead(head types.SignedHeader) (bool, error)
|
||||
ValidateOptimistic(update types.OptimisticUpdate) (bool, error)
|
||||
ValidateFinality(head types.FinalityUpdate) (bool, error)
|
||||
ValidatedFinality() (types.FinalityUpdate, bool)
|
||||
SetPrefetchHead(head types.HeadInfo)
|
||||
}
|
||||
|
||||
|
|
@ -33,16 +35,17 @@ type headTracker interface {
|
|||
// 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.
|
||||
type HeadSync struct {
|
||||
headTracker headTracker
|
||||
chain committeeChain
|
||||
nextSyncPeriod uint64
|
||||
chainInit bool
|
||||
unvalidatedHeads map[request.Server]types.SignedHeader
|
||||
unvalidatedFinality map[request.Server]types.FinalityUpdate
|
||||
serverHeads map[request.Server]types.HeadInfo
|
||||
headServerCount map[types.HeadInfo]headServerCount
|
||||
headCounter uint64
|
||||
prefetchHead types.HeadInfo
|
||||
headTracker headTracker
|
||||
chain committeeChain
|
||||
nextSyncPeriod uint64
|
||||
chainInit bool
|
||||
unvalidatedOptimistic map[request.Server]types.OptimisticUpdate
|
||||
unvalidatedFinality map[request.Server]types.FinalityUpdate
|
||||
serverHeads map[request.Server]types.HeadInfo
|
||||
reqFinalityEpoch map[request.Server]uint64 // next epoch to request finality update
|
||||
headServerCount map[types.HeadInfo]headServerCount
|
||||
headCounter uint64
|
||||
prefetchHead types.HeadInfo
|
||||
}
|
||||
|
||||
// headServerCount is associated with most recently seen head infos; it counts
|
||||
|
|
@ -57,75 +60,98 @@ type headServerCount struct {
|
|||
// NewHeadSync creates a new HeadSync.
|
||||
func NewHeadSync(headTracker headTracker, chain committeeChain) *HeadSync {
|
||||
s := &HeadSync{
|
||||
headTracker: headTracker,
|
||||
chain: chain,
|
||||
unvalidatedHeads: make(map[request.Server]types.SignedHeader),
|
||||
unvalidatedFinality: make(map[request.Server]types.FinalityUpdate),
|
||||
serverHeads: make(map[request.Server]types.HeadInfo),
|
||||
headServerCount: make(map[types.HeadInfo]headServerCount),
|
||||
headTracker: headTracker,
|
||||
chain: chain,
|
||||
unvalidatedOptimistic: make(map[request.Server]types.OptimisticUpdate),
|
||||
unvalidatedFinality: make(map[request.Server]types.FinalityUpdate),
|
||||
serverHeads: make(map[request.Server]types.HeadInfo),
|
||||
headServerCount: make(map[types.HeadInfo]headServerCount),
|
||||
reqFinalityEpoch: make(map[request.Server]uint64),
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Process implements request.Module.
|
||||
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 {
|
||||
switch event.Type {
|
||||
case EvNewHead:
|
||||
s.setServerHead(event.Server, event.Data.(types.HeadInfo))
|
||||
case EvNewSignedHead:
|
||||
s.newSignedHead(event.Server, event.Data.(types.SignedHeader))
|
||||
case EvNewOptimisticUpdate:
|
||||
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:
|
||||
s.newFinalityUpdate(event.Server, event.Data.(types.FinalityUpdate))
|
||||
case request.EvResponse:
|
||||
_, _, resp := event.RequestInfo()
|
||||
s.newFinalityUpdate(event.Server, resp.(types.FinalityUpdate))
|
||||
case request.EvUnregistered:
|
||||
s.setServerHead(event.Server, types.HeadInfo{})
|
||||
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
|
||||
// is properly synced or stores it for further validation.
|
||||
func (s *HeadSync) newSignedHead(server request.Server, signedHead types.SignedHeader) {
|
||||
if !s.chainInit || types.SyncPeriod(signedHead.SignatureSlot) > s.nextSyncPeriod {
|
||||
s.unvalidatedHeads[server] = signedHead
|
||||
// newOptimisticUpdate handles received optimistic update; either validates it if
|
||||
// the chain is properly synced or stores it for further validation.
|
||||
func (s *HeadSync) newOptimisticUpdate(server request.Server, optimisticUpdate types.OptimisticUpdate) {
|
||||
if !s.chainInit || types.SyncPeriod(optimisticUpdate.SignatureSlot) > s.nextSyncPeriod {
|
||||
s.unvalidatedOptimistic[server] = optimisticUpdate
|
||||
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
|
||||
// is properly synced or stores it for further validation.
|
||||
// newFinalityUpdate handles received finality update; either validates it if
|
||||
// the chain is properly synced or stores it for further validation.
|
||||
func (s *HeadSync) newFinalityUpdate(server request.Server, finalityUpdate types.FinalityUpdate) {
|
||||
if !s.chainInit || types.SyncPeriod(finalityUpdate.SignatureSlot) > s.nextSyncPeriod {
|
||||
s.unvalidatedFinality[server] = finalityUpdate
|
||||
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.
|
||||
func (s *HeadSync) processUnvalidated() {
|
||||
func (s *HeadSync) processUnvalidatedUpdates() {
|
||||
if !s.chainInit {
|
||||
return
|
||||
}
|
||||
for server, signedHead := range s.unvalidatedHeads {
|
||||
if types.SyncPeriod(signedHead.SignatureSlot) <= s.nextSyncPeriod {
|
||||
s.headTracker.ValidateHead(signedHead)
|
||||
delete(s.unvalidatedHeads, server)
|
||||
for server, optimisticUpdate := range s.unvalidatedOptimistic {
|
||||
if types.SyncPeriod(optimisticUpdate.SignatureSlot) <= s.nextSyncPeriod {
|
||||
if _, err := s.headTracker.ValidateOptimistic(optimisticUpdate); err != nil {
|
||||
log.Debug("Error validating deferred optimistic update", "error", err)
|
||||
}
|
||||
delete(s.unvalidatedOptimistic, server)
|
||||
}
|
||||
}
|
||||
for server, finalityUpdate := range s.unvalidatedFinality {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,15 +19,17 @@ package sync
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/beacon/light/request"
|
||||
"github.com/ethereum/go-ethereum/beacon/types"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
var (
|
||||
testServer1 = "testServer1"
|
||||
testServer2 = "testServer2"
|
||||
testServer3 = "testServer3"
|
||||
testServer4 = "testServer4"
|
||||
testServer1 = testServer("testServer1")
|
||||
testServer2 = testServer("testServer2")
|
||||
testServer3 = testServer("testServer3")
|
||||
testServer4 = testServer("testServer4")
|
||||
testServer5 = testServer("testServer5")
|
||||
|
||||
testHead0 = types.HeadInfo{}
|
||||
testHead1 = types.HeadInfo{Slot: 123, BlockRoot: common.Hash{1}}
|
||||
|
|
@ -35,13 +37,27 @@ var (
|
|||
testHead3 = types.HeadInfo{Slot: 124, BlockRoot: common.Hash{3}}
|
||||
testHead4 = types.HeadInfo{Slot: 125, BlockRoot: common.Hash{4}}
|
||||
|
||||
testSHead1 = types.SignedHeader{SignatureSlot: 0x0124, Header: types.Header{Slot: 0x0123, StateRoot: common.Hash{1}}}
|
||||
testSHead2 = types.SignedHeader{SignatureSlot: 0x2010, Header: types.Header{Slot: 0x200e, StateRoot: common.Hash{2}}}
|
||||
// testSHead3 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}}}
|
||||
testSHead4 = types.SignedHeader{SignatureSlot: 0x6444, Header: types.Header{Slot: 0x6443, StateRoot: common.Hash{4}}}
|
||||
testOptUpdate1 = types.OptimisticUpdate{SignatureSlot: 0x0124, Attested: types.HeaderWithExecProof{Header: types.Header{Slot: 0x0123, StateRoot: common.Hash{1}}}}
|
||||
testOptUpdate2 = types.OptimisticUpdate{SignatureSlot: 0x2010, Attested: types.HeaderWithExecProof{Header: types.Header{Slot: 0x200e, StateRoot: common.Hash{2}}}}
|
||||
// testOptUpdate3 is at the end of period 1 but signed in period 2
|
||||
testOptUpdate3 = types.OptimisticUpdate{SignatureSlot: 0x4000, Attested: types.HeaderWithExecProof{Header: types.Header{Slot: 0x3fff, StateRoot: common.Hash{3}}}}
|
||||
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) {
|
||||
chain := &TestCommitteeChain{}
|
||||
ht := &TestHeadTracker{}
|
||||
|
|
@ -51,50 +67,66 @@ func TestValidatedHead(t *testing.T) {
|
|||
ht.ExpValidated(t, 0, nil)
|
||||
|
||||
ts.AddServer(testServer1, 1)
|
||||
ts.ServerEvent(EvNewSignedHead, testServer1, testSHead1)
|
||||
ts.Run(1)
|
||||
ts.ServerEvent(EvNewOptimisticUpdate, testServer1, testOptUpdate1)
|
||||
ts.Run(1, testServer1, ReqFinality{})
|
||||
// announced head should be queued because of uninitialized chain
|
||||
ht.ExpValidated(t, 1, nil)
|
||||
|
||||
chain.SetNextSyncPeriod(0) // initialize chain
|
||||
ts.Run(2)
|
||||
// expect previously queued head to be validated
|
||||
ht.ExpValidated(t, 2, []types.SignedHeader{testSHead1})
|
||||
ht.ExpValidated(t, 2, []types.OptimisticUpdate{testOptUpdate1})
|
||||
|
||||
chain.SetNextSyncPeriod(1)
|
||||
ts.ServerEvent(EvNewSignedHead, testServer1, testSHead2)
|
||||
ts.ServerEvent(EvNewFinalityUpdate, testServer1, finality(testOptUpdate2))
|
||||
ts.ServerEvent(EvNewOptimisticUpdate, testServer1, testOptUpdate2)
|
||||
ts.AddServer(testServer2, 1)
|
||||
ts.ServerEvent(EvNewSignedHead, testServer2, testSHead2)
|
||||
ts.ServerEvent(EvNewOptimisticUpdate, testServer2, testOptUpdate2)
|
||||
ts.Run(3)
|
||||
// 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.ServerEvent(EvNewSignedHead, testServer3, testSHead4)
|
||||
ts.Run(4)
|
||||
// future period announced heads should be queued
|
||||
ts.ServerEvent(EvNewOptimisticUpdate, testServer3, testOptUpdate4)
|
||||
// finality should be requested from both servers
|
||||
ts.Run(4, testServer1, ReqFinality{}, testServer3, ReqFinality{})
|
||||
// future period annonced heads should be queued
|
||||
ht.ExpValidated(t, 4, nil)
|
||||
|
||||
chain.SetNextSyncPeriod(2)
|
||||
ts.Run(5)
|
||||
// testSHead3 can be validated now but not testSHead4
|
||||
ht.ExpValidated(t, 5, []types.SignedHeader{testSHead3})
|
||||
// testOptUpdate3 can be validated now but not testOptUpdate4
|
||||
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
|
||||
ts.RemoveServer(testServer3)
|
||||
ts.Run(6)
|
||||
ht.ExpValidated(t, 6, nil)
|
||||
ts.Run(8)
|
||||
ht.ExpValidated(t, 8, nil)
|
||||
|
||||
chain.SetNextSyncPeriod(3)
|
||||
ts.Run(7)
|
||||
// testSHead4 could be validated now but it's not queued by any registered server
|
||||
ht.ExpValidated(t, 7, nil)
|
||||
ts.Run(9)
|
||||
// testOptUpdate4 could be validated now but it's not queued by any registered server
|
||||
ht.ExpValidated(t, 9, nil)
|
||||
|
||||
ts.ServerEvent(EvNewSignedHead, testServer2, testSHead4)
|
||||
ts.Run(8)
|
||||
// now testSHead4 should be validated
|
||||
ht.ExpValidated(t, 8, []types.SignedHeader{testSHead4})
|
||||
ts.ServerEvent(EvNewFinalityUpdate, testServer2, finality(testOptUpdate4))
|
||||
ts.ServerEvent(EvNewOptimisticUpdate, testServer2, testOptUpdate4)
|
||||
ts.Run(10)
|
||||
// now testOptUpdate4 should be validated
|
||||
ht.ExpValidated(t, 10, []types.OptimisticUpdate{testOptUpdate4})
|
||||
}
|
||||
|
||||
func TestPrefetchHead(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ func (ts *TestScheduler) Run(testIndex int, exp ...any) {
|
|||
if count == 0 {
|
||||
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) {
|
||||
|
|
@ -104,7 +104,7 @@ func (ts *TestScheduler) Send(server request.Server, req request.Request) reques
|
|||
|
||||
func (ts *TestScheduler) Fail(server request.Server, desc string) {
|
||||
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
|
||||
}
|
||||
ts.expFail[server]--
|
||||
|
|
@ -212,32 +212,37 @@ func (tc *TestCommitteeChain) ExpNextSyncPeriod(t *testing.T, expNsp uint64) {
|
|||
|
||||
type TestHeadTracker struct {
|
||||
phead types.HeadInfo
|
||||
validated []types.SignedHeader
|
||||
validated []types.OptimisticUpdate
|
||||
finality types.FinalityUpdate
|
||||
}
|
||||
|
||||
func (ht *TestHeadTracker) ValidateHead(head types.SignedHeader) (bool, error) {
|
||||
ht.validated = append(ht.validated, head)
|
||||
func (ht *TestHeadTracker) ValidateOptimistic(update types.OptimisticUpdate) (bool, error) {
|
||||
ht.validated = append(ht.validated, update)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// TODO add test case for finality
|
||||
func (ht *TestHeadTracker) ValidateFinality(head types.FinalityUpdate) (bool, error) {
|
||||
func (ht *TestHeadTracker) ValidateFinality(update types.FinalityUpdate) (bool, error) {
|
||||
ht.finality = update
|
||||
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 {
|
||||
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
|
||||
}
|
||||
if ht.validated[i] != expHead {
|
||||
vhead := ht.validated[i].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())
|
||||
if !reflect.DeepEqual(ht.validated[i], expHead) {
|
||||
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.Attested.Header.Slot, expHead.Attested.Header.Hash(), vhead.Slot, vhead.Hash())
|
||||
}
|
||||
}
|
||||
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())
|
||||
}
|
||||
ht.validated = nil
|
||||
|
|
|
|||
|
|
@ -23,9 +23,9 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
EvNewHead = &request.EventType{Name: "newHead"} // data: types.HeadInfo
|
||||
EvNewSignedHead = &request.EventType{Name: "newSignedHead"} // data: types.SignedHeader
|
||||
EvNewFinalityUpdate = &request.EventType{Name: "newFinalityUpdate"} // data: types.FinalityUpdate
|
||||
EvNewHead = &request.EventType{Name: "newHead"} // data: types.HeadInfo
|
||||
EvNewOptimisticUpdate = &request.EventType{Name: "newOptimisticUpdate"} // data: types.OptimisticUpdate
|
||||
EvNewFinalityUpdate = &request.EventType{Name: "newFinalityUpdate"} // data: types.FinalityUpdate
|
||||
)
|
||||
|
||||
type (
|
||||
|
|
@ -36,7 +36,12 @@ type (
|
|||
Updates []*types.LightClientUpdate
|
||||
Committees []*types.SerializedSyncCommittee
|
||||
}
|
||||
ReqHeader common.Hash
|
||||
ReqHeader common.Hash
|
||||
RespHeader struct {
|
||||
Header types.Header
|
||||
Canonical, Finalized bool
|
||||
}
|
||||
ReqCheckpointData common.Hash
|
||||
ReqBeaconBlock common.Hash
|
||||
ReqFinality struct{}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/beacon/light"
|
||||
"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/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
|
|
@ -42,6 +43,31 @@ type CheckpointInit struct {
|
|||
checkpointHash common.Hash
|
||||
locked request.ServerAndID
|
||||
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.
|
||||
|
|
@ -49,40 +75,113 @@ func NewCheckpointInit(chain committeeChain, checkpointHash common.Hash) *Checkp
|
|||
return &CheckpointInit{
|
||||
chain: chain,
|
||||
checkpointHash: checkpointHash,
|
||||
serverState: make(map[request.Server]serverState),
|
||||
}
|
||||
}
|
||||
|
||||
// Process implements request.Module.
|
||||
func (s *CheckpointInit) Process(requester request.Requester, events []request.Event) {
|
||||
if s.initialized {
|
||||
return
|
||||
}
|
||||
|
||||
for _, event := range events {
|
||||
if !event.IsRequestEvent() {
|
||||
continue
|
||||
}
|
||||
sid, req, resp := event.RequestInfo()
|
||||
if s.locked == sid {
|
||||
s.locked = request.ServerAndID{}
|
||||
}
|
||||
if resp != nil {
|
||||
if checkpoint := resp.(*types.BootstrapData); checkpoint.Header.Hash() == common.Hash(req.(ReqCheckpointData)) {
|
||||
s.chain.CheckpointInit(*checkpoint)
|
||||
s.initialized = true
|
||||
return
|
||||
switch event.Type {
|
||||
case request.EvResponse, request.EvFail, request.EvTimeout:
|
||||
sid, req, resp := event.RequestInfo()
|
||||
if s.locked == sid {
|
||||
s.locked = request.ServerAndID{}
|
||||
}
|
||||
if event.Type == request.EvTimeout {
|
||||
continue
|
||||
}
|
||||
switch s.serverState[sid.Server].state {
|
||||
case ssDefault:
|
||||
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
|
||||
if s.initialized || s.locked != (request.ServerAndID{}) {
|
||||
return
|
||||
for _, server := range requester.CanSendTo() {
|
||||
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 {
|
||||
return
|
||||
|
||||
// print log message if necessary
|
||||
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
|
||||
|
|
@ -221,9 +320,9 @@ func (s *ForwardUpdateSync) Process(requester request.Requester, events []reques
|
|||
if !queued {
|
||||
s.unlockRange(sid, req)
|
||||
}
|
||||
case EvNewSignedHead:
|
||||
signedHead := event.Data.(types.SignedHeader)
|
||||
s.nextSyncPeriod[event.Server] = types.SyncPeriod(signedHead.SignatureSlot + 256)
|
||||
case EvNewOptimisticUpdate:
|
||||
update := event.Data.(types.OptimisticUpdate)
|
||||
s.nextSyncPeriod[event.Server] = types.SyncPeriod(update.SignatureSlot + 256)
|
||||
case request.EvUnregistered:
|
||||
delete(s.nextSyncPeriod, event.Server)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,9 +68,9 @@ func TestUpdateSyncParallel(t *testing.T) {
|
|||
ts := NewTestScheduler(t, updateSync)
|
||||
// add 2 servers, head at period 100; allow 3-3 parallel requests for each
|
||||
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.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
|
||||
ts.Run(1,
|
||||
|
|
@ -150,11 +150,11 @@ func TestUpdateSyncDifferentHeads(t *testing.T) {
|
|||
ts := NewTestScheduler(t, updateSync)
|
||||
// add 3 servers with different announced head periods
|
||||
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.ServerEvent(EvNewSignedHead, testServer2, types.SignedHeader{SignatureSlot: 0x2000*16 + 0x1000})
|
||||
ts.ServerEvent(EvNewOptimisticUpdate, testServer2, types.OptimisticUpdate{SignatureSlot: 0x2000*16 + 0x1000})
|
||||
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
|
||||
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
|
||||
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
|
||||
ts.Run(7, testServer4, ReqUpdates{FirstPeriod: 16, Count: 1})
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@ package types
|
|||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -27,6 +29,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/beacon/merkle"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
|
|
@ -34,6 +37,8 @@ import (
|
|||
// across signing different data structures.
|
||||
const syncCommitteeDomain = 7
|
||||
|
||||
var knownForks = []string{"GENESIS", "ALTAIR", "BELLATRIX", "CAPELLA", "DENEB"}
|
||||
|
||||
// Fork describes a single beacon chain fork and also stores the calculated
|
||||
// signature domain used after this fork.
|
||||
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
|
||||
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
|
||||
domain merkle.Value
|
||||
}
|
||||
|
|
@ -99,9 +107,14 @@ func (f Forks) SigningRoot(header Header) (common.Hash, error) {
|
|||
return signingRoot, nil
|
||||
}
|
||||
|
||||
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) Less(i, j int) bool { return f[i].Epoch < f[j].Epoch }
|
||||
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) 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.
|
||||
type ChainConfig struct {
|
||||
|
|
@ -122,16 +135,22 @@ func (c *ChainConfig) ForkAtEpoch(epoch uint64) Fork {
|
|||
|
||||
// AddFork adds a new item to the list of forks.
|
||||
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{
|
||||
Name: name,
|
||||
Epoch: epoch,
|
||||
Version: version,
|
||||
Name: name,
|
||||
Epoch: epoch,
|
||||
Version: version,
|
||||
knownIndex: knownIndex,
|
||||
}
|
||||
fork.computeDomain(c.GenesisValidatorsRoot)
|
||||
|
||||
c.Forks = append(c.Forks, fork)
|
||||
sort.Sort(c.Forks)
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
|
|
@ -181,6 +200,5 @@ func (c *ChainConfig) LoadForks(path string) error {
|
|||
for name := range versions {
|
||||
return fmt.Errorf("epoch number missing for fork %q in beacon chain config file", name)
|
||||
}
|
||||
sort.Sort(c.Forks)
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ type ExecutionHeader struct {
|
|||
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.
|
||||
func ExecutionHeaderFromJSON(forkName string, data []byte) (*ExecutionHeader, error) {
|
||||
var obj headerObject
|
||||
|
|
|
|||
|
|
@ -66,9 +66,8 @@ func convertPayload[T payloadType](payload T, parentRoot *zrntcommon.Root) (*typ
|
|||
block := types.NewBlockWithHeader(&header)
|
||||
block = block.WithBody(transactions, nil)
|
||||
block = block.WithWithdrawals(withdrawals)
|
||||
hash := block.Hash()
|
||||
if hash != expectedHash {
|
||||
return block, fmt.Errorf("Sanity check failed, payload hash does not match (expected %x, got %x)", expectedHash, hash)
|
||||
if hash := block.Hash(); hash != expectedHash {
|
||||
return nil, fmt.Errorf("Sanity check failed, payload hash does not match (expected %x, got %x)", expectedHash, hash)
|
||||
}
|
||||
return block, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/beacon/merkle"
|
||||
"github.com/ethereum/go-ethereum/beacon/params"
|
||||
"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.
|
||||
|
|
@ -142,17 +142,57 @@ func (u UpdateScore) BetterThan(w UpdateScore) bool {
|
|||
return u.SignerCount > w.SignerCount
|
||||
}
|
||||
|
||||
// HeaderWithExecProof contains a beacon header and proves the belonging execution
|
||||
// payload header with a Merkle proof.
|
||||
type HeaderWithExecProof struct {
|
||||
Header
|
||||
PayloadHeader *ExecutionHeader
|
||||
PayloadBranch merkle.Values
|
||||
}
|
||||
|
||||
// Validate verifies the Merkle proof of the execution payload header.
|
||||
func (h *HeaderWithExecProof) Validate() error {
|
||||
payloadRoot := h.PayloadHeader.PayloadRoot()
|
||||
return merkle.VerifyProof(h.BodyRoot, params.BodyIndexExecPayload, h.PayloadBranch, payloadRoot)
|
||||
return merkle.VerifyProof(h.BodyRoot, params.BodyIndexExecPayload, h.PayloadBranch, h.PayloadHeader.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 {
|
||||
Attested, Finalized HeaderWithExecProof
|
||||
FinalityBranch merkle.Values
|
||||
|
|
@ -163,6 +203,7 @@ type FinalityUpdate struct {
|
|||
SignatureSlot uint64
|
||||
}
|
||||
|
||||
// SignedHeader returns the signed attested header of the update.
|
||||
func (u *FinalityUpdate) SignedHeader() SignedHeader {
|
||||
return SignedHeader{
|
||||
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 {
|
||||
if err := u.Attested.Validate(); err != nil {
|
||||
return err
|
||||
|
|
@ -186,6 +231,6 @@ func (u *FinalityUpdate) Validate() error {
|
|||
// finalized execution block.
|
||||
type ChainHeadEvent struct {
|
||||
BeaconHead Header
|
||||
Block *types.Block
|
||||
Block *ctypes.Block
|
||||
Finalized common.Hash
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,22 +5,22 @@
|
|||
# https://github.com/ethereum/execution-spec-tests/releases/download/v2.1.0/
|
||||
ca89c76851b0900bfcc3cbb9a26cbece1f3d7c64a3bed38723e914713290df6c fixtures_develop.tar.gz
|
||||
|
||||
# version:golang 1.22.1
|
||||
# version:golang 1.22.2
|
||||
# https://go.dev/dl/
|
||||
79c9b91d7f109515a25fc3ecdaad125d67e6bdb54f6d4d98580f46799caea321 go1.22.1.src.tar.gz
|
||||
3bc971772f4712fec0364f4bc3de06af22a00a12daab10b6f717fdcd13156cc0 go1.22.1.darwin-amd64.tar.gz
|
||||
f6a9cec6b8a002fcc9c0ee24ec04d67f430a52abc3cfd613836986bcc00d8383 go1.22.1.darwin-arm64.tar.gz
|
||||
99f81c10d5a3f8a886faf8fa86aaa2aaf929fbed54a972ae5eec3c5e0bdb961a go1.22.1.freebsd-386.tar.gz
|
||||
51c614ddd92ee4a9913a14c39bf80508d9cfba08561f24d2f075fd00f3cfb067 go1.22.1.freebsd-amd64.tar.gz
|
||||
8484df36d3d40139eaf0fe5e647b006435d826cc12f9ae72973bf7ec265e0ae4 go1.22.1.linux-386.tar.gz
|
||||
aab8e15785c997ae20f9c88422ee35d962c4562212bb0f879d052a35c8307c7f go1.22.1.linux-amd64.tar.gz
|
||||
e56685a245b6a0c592fc4a55f0b7803af5b3f827aaa29feab1f40e491acf35b8 go1.22.1.linux-arm64.tar.gz
|
||||
8cb7a90e48c20daed39a6ac8b8a40760030ba5e93c12274c42191d868687c281 go1.22.1.linux-armv6l.tar.gz
|
||||
ac775e19d93cc1668999b77cfe8c8964abfbc658718feccfe6e0eb87663cd668 go1.22.1.linux-ppc64le.tar.gz
|
||||
7bb7dd8e10f95c9a4cc4f6bef44c816a6e7c9e03f56ac6af6efbb082b19b379f go1.22.1.linux-s390x.tar.gz
|
||||
0c5ebb7eb39b7884ec99f92b425d4c03a96a72443562aafbf6e7d15c42a3108a go1.22.1.windows-386.zip
|
||||
cf9c66a208a106402a527f5b956269ca506cfe535fc388e828d249ea88ed28ba go1.22.1.windows-amd64.zip
|
||||
85b8511b298c9f4199ecae26afafcc3d46155bac934d43f2357b9224bcaa310f go1.22.1.windows-arm64.zip
|
||||
374ea82b289ec738e968267cac59c7d5ff180f9492250254784b2044e90df5a9 go1.22.2.src.tar.gz
|
||||
33e7f63077b1c5bce4f1ecadd4d990cf229667c40bfb00686990c950911b7ab7 go1.22.2.darwin-amd64.tar.gz
|
||||
660298be38648723e783ba0398e90431de1cb288c637880cdb124f39bd977f0d go1.22.2.darwin-arm64.tar.gz
|
||||
efc7162b0cad2f918ac566a923d4701feb29dc9c0ab625157d49b1cbcbba39da go1.22.2.freebsd-386.tar.gz
|
||||
d753428296e6709527e291fd204700a587ffef2c0a472b21aebea11618245929 go1.22.2.freebsd-amd64.tar.gz
|
||||
586d9eb7fe0489ab297ad80dd06414997df487c5cf536c490ffeaa8d8f1807a7 go1.22.2.linux-386.tar.gz
|
||||
5901c52b7a78002aeff14a21f93e0f064f74ce1360fce51c6ee68cd471216a17 go1.22.2.linux-amd64.tar.gz
|
||||
36e720b2d564980c162a48c7e97da2e407dfcc4239e1e58d98082dfa2486a0c1 go1.22.2.linux-arm64.tar.gz
|
||||
9243dfafde06e1efe24d59df6701818e6786b4adfdf1191098050d6d023c5369 go1.22.2.linux-armv6l.tar.gz
|
||||
251a8886c5113be6490bdbb955ddee98763b49c9b1bf4c8364c02d3b482dab00 go1.22.2.linux-ppc64le.tar.gz
|
||||
2b39019481c28c560d65e9811a478ae10e3ef765e0f59af362031d386a71bfef go1.22.2.linux-s390x.tar.gz
|
||||
651753c06df037020ef4d162c5b273452e9ba976ed17ae39e66ef7ee89d8147e go1.22.2.windows-386.zip
|
||||
8e581cf330f49d3266e936521a2d8263679ef7e2fc2cbbceb85659122d883596 go1.22.2.windows-amd64.zip
|
||||
ddfca5beb9a0c62254266c3090c2555d899bf3e7aa26243e7de3621108f06875 go1.22.2.windows-arm64.zip
|
||||
|
||||
# version:golangci 1.55.2
|
||||
# https://github.com/golangci/golangci-lint/releases/
|
||||
|
|
|
|||
|
|
@ -291,8 +291,8 @@ func writeAuthors(files []string) {
|
|||
}
|
||||
}
|
||||
// Write sorted list of authors back to the file.
|
||||
slices.SortFunc(list, func(a, b string) bool {
|
||||
return strings.ToLower(a) < strings.ToLower(b)
|
||||
slices.SortFunc(list, func(a, b string) int {
|
||||
return strings.Compare(strings.ToLower(a), strings.ToLower(b))
|
||||
})
|
||||
content := new(bytes.Buffer)
|
||||
content.WriteString(authorsFileHeader)
|
||||
|
|
|
|||
|
|
@ -9,14 +9,14 @@ It enables usecases like the following:
|
|||
|
||||
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
|
||||
2. Credential managements and credentials; how to provide auto-unlock without exposing keys unnecessarily.
|
||||
1. Rule Implementation: how to create, manage, and interpret rules in a flexible but secure manner
|
||||
2. Credential management and credentials; how to provide auto-unlock without exposing keys unnecessarily.
|
||||
|
||||
The section below deals with both of them
|
||||
|
||||
## 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:
|
||||
|
||||
```js
|
||||
|
|
@ -27,7 +27,7 @@ function asBig(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) {
|
||||
var limit = big.Newint("0xb1a2bc2ec50000")
|
||||
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
|
||||
|
||||
* 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.
|
||||
* 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`.
|
||||
|
|
@ -88,8 +88,8 @@ Some security precautions can be made, such as:
|
|||
|
||||
##### 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
|
||||
implemented for `geth`. There are no known security vulnerabilities in, nor have we had any security-problems with it so far.
|
||||
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 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
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
|
|
@ -127,8 +127,8 @@ The `vault.dat` would be an encrypted container storing the following informatio
|
|||
|
||||
### 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
|
||||
imagine leveraging OS-level keychains where supported. The setup is however in general similar to how ssh-keys are stored in `.ssh/`.
|
||||
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/`.
|
||||
|
||||
|
||||
# Implementation status
|
||||
|
|
@ -149,7 +149,7 @@ function big(str) {
|
|||
// Time window: 1 week
|
||||
var window = 1000* 3600*24*7;
|
||||
|
||||
// Limit : 1 ether
|
||||
// Limit: 1 ether
|
||||
var limit = new BigNumber("1e18");
|
||||
|
||||
function isLimitOk(transaction) {
|
||||
|
|
@ -163,7 +163,7 @@ function isLimitOk(transaction) {
|
|||
if (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});
|
||||
console.log(txs, newtxs.length);
|
||||
|
||||
|
|
@ -174,7 +174,7 @@ function isLimitOk(transaction) {
|
|||
console.log("ApproveTx > Sum so far", sum);
|
||||
console.log("ApproveTx > Requested", value.toNumber());
|
||||
|
||||
// Would we exceed weekly limit ?
|
||||
// Would we exceed the weekly limit ?
|
||||
return sum.plus(value).lt(limit)
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package v5test
|
|||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"slices"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
|
@ -266,7 +267,7 @@ func (s *Suite) TestFindnodeResults(t *utesting.T) {
|
|||
n := bn.conn.localNode.Node()
|
||||
expect[n.ID()] = n
|
||||
d := uint(enode.LogDist(n.ID(), s.Dest.ID()))
|
||||
if !containsUint(dists, d) {
|
||||
if !slices.Contains(dists, d) {
|
||||
dists = append(dists, d)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -252,12 +252,3 @@ func checkRecords(records []*enr.Record) ([]*enode.Node, error) {
|
|||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
func containsUint(ints []uint, x uint) bool {
|
||||
for i := range ints {
|
||||
if ints[i] == x {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,4 +50,4 @@ contains the password.
|
|||
|
||||
## 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.
|
||||
|
|
|
|||
|
|
@ -50,6 +50,10 @@ var (
|
|||
Name: "trace.returndata",
|
||||
Usage: "Enable return data output in traces",
|
||||
}
|
||||
TraceEnableCallFramesFlag = &cli.BoolFlag{
|
||||
Name: "trace.callframes",
|
||||
Usage: "Enable call frames output in traces",
|
||||
}
|
||||
OutputBasedir = &cli.StringFlag{
|
||||
Name: "output.basedir",
|
||||
Usage: "Specifies where output files are placed. Will be created if it does not exist.",
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
|
||||
"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/vm"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
|
|
@ -101,9 +102,14 @@ func Transition(ctx *cli.Context) error {
|
|||
if err != nil {
|
||||
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{
|
||||
Hooks: logger,
|
||||
Hooks: l,
|
||||
// jsonLogger streams out result to file.
|
||||
GetResult: func() (json.RawMessage, error) { return nil, nil },
|
||||
Stop: func(err error) {},
|
||||
|
|
|
|||
|
|
@ -152,6 +152,7 @@ var stateTransitionCommand = &cli.Command{
|
|||
t8ntool.TraceEnableMemoryFlag,
|
||||
t8ntool.TraceDisableStackFlag,
|
||||
t8ntool.TraceEnableReturnDataFlag,
|
||||
t8ntool.TraceEnableCallFramesFlag,
|
||||
t8ntool.OutputBasedir,
|
||||
t8ntool.OutputAllocFlag,
|
||||
t8ntool.OutputResultFlag,
|
||||
|
|
|
|||
|
|
@ -272,8 +272,17 @@ func runCmd(ctx *cli.Context) error {
|
|||
output, leftOverGas, stats, err := timedExec(bench, execFunc)
|
||||
|
||||
if ctx.Bool(DumpFlag.Name) {
|
||||
statedb.Commit(genesisConfig.Number, true)
|
||||
fmt.Println(string(statedb.Dump(nil)))
|
||||
root, err := statedb.Commit(genesisConfig.Number, true)
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -375,6 +375,14 @@ func TestT8nTracing(t *testing.T) {
|
|||
}`},
|
||||
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 = append(args, tc.input.get(tc.base)...)
|
||||
|
|
|
|||
1
cmd/evm/testdata/32/README.md
vendored
Normal file
1
cmd/evm/testdata/32/README.md
vendored
Normal 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
30
cmd/evm/testdata/32/alloc.json
vendored
Normal 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
12
cmd/evm/testdata/32/env.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"currentCoinbase": "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
|
||||
"currentGasLimit": "71794957647893862",
|
||||
"currentNumber": "1",
|
||||
"currentTimestamp": "1000",
|
||||
"currentRandom": "0",
|
||||
"currentDifficulty": "0",
|
||||
"blockHashes": {},
|
||||
"ommers": [],
|
||||
"currentBaseFee": "7",
|
||||
"parentUncleHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||
}
|
||||
|
|
@ -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
17
cmd/evm/testdata/32/txs.json
vendored
Normal 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"
|
||||
}
|
||||
]
|
||||
|
|
@ -48,7 +48,7 @@ func TestAttachWithHeaders(t *testing.T) {
|
|||
// 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.
|
||||
func TestRemoteDbWithHeaders(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ if one is set. Otherwise it prints the genesis from the datadir.`,
|
|||
utils.MetricsInfluxDBOrganizationFlag,
|
||||
utils.TxLookupLimitFlag,
|
||||
utils.VMTraceFlag,
|
||||
utils.VMTraceConfigFlag,
|
||||
utils.VMTraceJsonConfigFlag,
|
||||
utils.TransactionHistoryFlag,
|
||||
utils.StateHistoryFlag,
|
||||
}, utils.DatabaseFlags),
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/eth/catalyst"
|
||||
"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/version"
|
||||
"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.
|
||||
func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
|
||||
func makeFullNode(ctx *cli.Context) *node.Node {
|
||||
stack, cfg := makeConfigNode(ctx)
|
||||
if ctx.IsSet(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)
|
||||
}
|
||||
}
|
||||
return stack, backend
|
||||
return stack
|
||||
}
|
||||
|
||||
// dumpConfig is the dumpconfig command.
|
||||
|
|
|
|||
|
|
@ -70,8 +70,8 @@ JavaScript API. See https://geth.ethereum.org/docs/interacting-with-geth/javascr
|
|||
func localConsole(ctx *cli.Context) error {
|
||||
// Create and start the node based on the CLI flags
|
||||
prepare(ctx)
|
||||
stack, backend := makeFullNode(ctx)
|
||||
startNode(ctx, stack, backend, true)
|
||||
stack := makeFullNode(ctx)
|
||||
startNode(ctx, stack, true)
|
||||
defer stack.Close()
|
||||
|
||||
// Attach to the newly started node and create the JavaScript console.
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"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/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
|
|
@ -138,7 +137,7 @@ var (
|
|||
utils.DeveloperPeriodFlag,
|
||||
utils.VMEnableDebugFlag,
|
||||
utils.VMTraceFlag,
|
||||
utils.VMTraceConfigFlag,
|
||||
utils.VMTraceJsonConfigFlag,
|
||||
utils.NetworkIdFlag,
|
||||
utils.EthStatsURLFlag,
|
||||
utils.NoCompactionFlag,
|
||||
|
|
@ -348,10 +347,10 @@ func geth(ctx *cli.Context) error {
|
|||
}
|
||||
|
||||
prepare(ctx)
|
||||
stack, backend := makeFullNode(ctx)
|
||||
stack := makeFullNode(ctx)
|
||||
defer stack.Close()
|
||||
|
||||
startNode(ctx, stack, backend, false)
|
||||
startNode(ctx, stack, false)
|
||||
stack.Wait()
|
||||
return nil
|
||||
}
|
||||
|
|
@ -359,7 +358,7 @@ func geth(ctx *cli.Context) error {
|
|||
// 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
|
||||
// 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)
|
||||
|
||||
// Start up the node itself
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
f := fmt.Sprintf("%v/tempdump", os.TempDir())
|
||||
defer func() {
|
||||
|
|
|
|||
|
|
@ -544,8 +544,8 @@ var (
|
|||
Usage: "Name of tracer which should record internal VM operations (costly)",
|
||||
Category: flags.VMCategory,
|
||||
}
|
||||
VMTraceConfigFlag = &cli.StringFlag{
|
||||
Name: "vmtrace.config",
|
||||
VMTraceJsonConfigFlag = &cli.StringFlag{
|
||||
Name: "vmtrace.jsonconfig",
|
||||
Usage: "Tracer configuration (JSON)",
|
||||
Category: flags.VMCategory,
|
||||
}
|
||||
|
|
@ -661,7 +661,7 @@ var (
|
|||
}
|
||||
HTTPPathPrefixFlag = &cli.StringFlag{
|
||||
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: "",
|
||||
Category: flags.APICategory,
|
||||
}
|
||||
|
|
@ -1903,12 +1903,12 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
|||
if ctx.IsSet(VMTraceFlag.Name) {
|
||||
if name := ctx.String(VMTraceFlag.Name); name != "" {
|
||||
var config string
|
||||
if ctx.IsSet(VMTraceConfigFlag.Name) {
|
||||
config = ctx.String(VMTraceConfigFlag.Name)
|
||||
if ctx.IsSet(VMTraceJsonConfigFlag.Name) {
|
||||
config = ctx.String(VMTraceJsonConfigFlag.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{{
|
||||
Namespace: "eth",
|
||||
Service: filters.NewFilterAPI(filterSystem, false),
|
||||
Service: filters.NewFilterAPI(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 name := ctx.String(VMTraceFlag.Name); name != "" {
|
||||
var config json.RawMessage
|
||||
if ctx.IsSet(VMTraceConfigFlag.Name) {
|
||||
config = json.RawMessage(ctx.String(VMTraceConfigFlag.Name))
|
||||
if ctx.IsSet(VMTraceJsonConfigFlag.Name) {
|
||||
config = json.RawMessage(ctx.String(VMTraceJsonConfigFlag.Name))
|
||||
}
|
||||
t, err := tracers.LiveDirectory.New(name, config)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ func TestHistoryImportAndExport(t *testing.T) {
|
|||
t.Fatalf("unable to initialize chain: %v", err)
|
||||
}
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -115,9 +115,7 @@ func (c *BasicLRU[K, V]) Peek(key K) (value V, ok bool) {
|
|||
// Purge empties the cache.
|
||||
func (c *BasicLRU[K, V]) Purge() {
|
||||
c.list.init()
|
||||
for k := range c.items {
|
||||
delete(c.items, k)
|
||||
}
|
||||
clear(c.items)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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]) {
|
||||
e.prev = &l.root
|
||||
e.next = l.root.next
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import (
|
|||
"container/heap"
|
||||
)
|
||||
|
||||
// Priority queue data structure.
|
||||
// Prque is a priority queue data structure.
|
||||
type Prque[P cmp.Ordered, V any] struct {
|
||||
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)}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (p *Prque[P, V]) Pop() (V, P) {
|
||||
item := heap.Pop(p.cont).(*item[P, V])
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
// Checks whether the priority queue is empty.
|
||||
// Empty checks whether the priority queue is empty.
|
||||
func (p *Prque[P, V]) Empty() bool {
|
||||
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 {
|
||||
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() {
|
||||
*p = *New[P, V](p.cont.setIndex)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ func newSstack[P cmp.Ordered, V any](setIndex SetIndexCallback[V]) *sstack[P, V]
|
|||
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.
|
||||
func (s *sstack[P, V]) Push(data any) {
|
||||
if s.size == s.capacity {
|
||||
|
|
@ -69,7 +69,7 @@ func (s *sstack[P, V]) Push(data any) {
|
|||
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.
|
||||
func (s *sstack[P, V]) Pop() (res any) {
|
||||
s.size--
|
||||
|
|
@ -85,18 +85,18 @@ func (s *sstack[P, V]) Pop() (res any) {
|
|||
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 {
|
||||
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.
|
||||
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
|
||||
}
|
||||
|
||||
// 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) {
|
||||
ib, io, jb, jo := i/blockSize, i%blockSize, j/blockSize, j%blockSize
|
||||
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
|
||||
}
|
||||
|
||||
// Resets the stack, effectively clearing its contents.
|
||||
// Reset the stack, effectively clearing its contents.
|
||||
func (s *sstack[P, V]) Reset() {
|
||||
*s = *newSstack[P, V](s.setIndex)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package clique
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"maps"
|
||||
"slices"
|
||||
"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.
|
||||
func (s *Snapshot) copy() *Snapshot {
|
||||
cpy := &Snapshot{
|
||||
return &Snapshot{
|
||||
config: s.config,
|
||||
sigcache: s.sigcache,
|
||||
Number: s.Number,
|
||||
Hash: s.Hash,
|
||||
Signers: make(map[common.Address]struct{}),
|
||||
Recents: make(map[uint64]common.Address),
|
||||
Votes: make([]*Vote, len(s.Votes)),
|
||||
Tally: make(map[common.Address]Tally),
|
||||
Signers: maps.Clone(s.Signers),
|
||||
Recents: maps.Clone(s.Recents),
|
||||
Votes: slices.Clone(s.Votes),
|
||||
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
|
||||
|
|
|
|||
|
|
@ -568,7 +568,7 @@ var (
|
|||
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
|
||||
// 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) {
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ func (l *lexer) ignore() {
|
|||
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 {
|
||||
if strings.ContainsRune(valid, l.next()) {
|
||||
return true
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD
|
|||
if rbloom != header.Bloom {
|
||||
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))
|
||||
if receiptSha != header.ReceiptHash {
|
||||
return fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha)
|
||||
|
|
|
|||
|
|
@ -147,8 +147,11 @@ type CacheConfig struct {
|
|||
}
|
||||
|
||||
// triedbConfig derives the configures for trie database.
|
||||
func (c *CacheConfig) triedbConfig() *triedb.Config {
|
||||
config := &triedb.Config{Preimages: c.Preimages}
|
||||
func (c *CacheConfig) triedbConfig(isVerkle bool) *triedb.Config {
|
||||
config := &triedb.Config{
|
||||
Preimages: c.Preimages,
|
||||
IsVerkle: isVerkle,
|
||||
}
|
||||
if c.StateScheme == rawdb.HashScheme {
|
||||
config.HashDB = &hashdb.Config{
|
||||
CleanCacheSize: c.TrieCleanLimit * 1024 * 1024,
|
||||
|
|
@ -241,6 +244,8 @@ type BlockChain struct {
|
|||
bodyRLPCache *lru.Cache[common.Hash, rlp.RawValue]
|
||||
receiptsCache *lru.Cache[common.Hash, []*types.Receipt]
|
||||
blockCache *lru.Cache[common.Hash, *types.Block]
|
||||
|
||||
txLookupLock sync.RWMutex
|
||||
txLookupCache *lru.Cache[common.Hash, txLookup]
|
||||
|
||||
wg sync.WaitGroup
|
||||
|
|
@ -265,7 +270,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
|
|||
cacheConfig = defaultCacheConfig
|
||||
}
|
||||
// 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
|
||||
// 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.
|
||||
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 {
|
||||
bc.logger.OnBlockchainInit(chainConfig)
|
||||
}
|
||||
|
||||
if bc.logger != nil && bc.logger.OnGenesisBlock != nil {
|
||||
if block := bc.CurrentBlock(); block.Number.Uint64() == 0 {
|
||||
alloc, err := getGenesisState(bc.db, block.Hash())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get genesis state: %w", err)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
var (
|
||||
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.
|
||||
if err := bc.triedb.Close(); err != nil {
|
||||
log.Error("Failed to close trie database", "err", err)
|
||||
|
|
@ -1553,17 +1543,6 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
|
|||
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.
|
||||
// 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) {
|
||||
|
|
@ -1772,11 +1751,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
|||
log.Debug("Abort during block processing")
|
||||
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
|
||||
// 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,
|
||||
|
|
@ -2302,14 +2276,14 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error {
|
|||
// 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))
|
||||
}
|
||||
// Reset the tx lookup cache in case to clear stale txlookups.
|
||||
// This is done before writing any new chain data to avoid the
|
||||
// weird scenario that canonical chain is changed while the
|
||||
// stale lookups are still cached.
|
||||
bc.txLookupCache.Purge()
|
||||
// Acquire the tx-lookup lock before mutation. This step is essential
|
||||
// as the txlookups should be changed atomically, and all subsequent
|
||||
// reads should be blocked until the mutation is complete.
|
||||
bc.txLookupLock.Lock()
|
||||
|
||||
// Insert the new chain(except the head block(reverse order)),
|
||||
// taking care of the proper incremental order.
|
||||
// Insert the new chain segment in incremental order, from the old
|
||||
// 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-- {
|
||||
// Insert the block in the canonical way, re-writing history
|
||||
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 {
|
||||
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'
|
||||
// logs from the new canon chain. The number of logs can be very
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ func (it *insertIterator) current() *types.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 {
|
||||
return it.chain[0]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
// from the node's perspective.
|
||||
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
|
||||
if item, exist := bc.txLookupCache.Get(hash); exist {
|
||||
return item.lookup, item.transaction, nil
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ package core
|
|||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
|
@ -1966,7 +1966,7 @@ func testSetHeadWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme
|
|||
|
||||
// Create a temporary persistent database
|
||||
datadir := t.TempDir()
|
||||
ancient := path.Join(datadir, "ancient")
|
||||
ancient := filepath.Join(datadir, "ancient")
|
||||
|
||||
db, err := rawdb.Open(rawdb.OpenOptions{
|
||||
Directory: datadir,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
func TestHeadersInsertNonceError(t *testing.T) {
|
||||
testInsertNonceError(t, false, rawdb.HashScheme)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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
|
||||
// are fine though in that case.
|
||||
func (s *MatcherSession) Multiplex(batch int, wait time.Duration, mux chan chan *Retrieval) {
|
||||
waitTimer := time.NewTimer(wait)
|
||||
defer waitTimer.Stop()
|
||||
|
||||
for {
|
||||
// Allocate a new bloom bit index to retrieve data for, stopping when done
|
||||
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
|
||||
if s.pendingSections(bit) < batch {
|
||||
waitTimer.Reset(wait)
|
||||
select {
|
||||
case <-s.quit:
|
||||
// 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{})
|
||||
return
|
||||
|
||||
case <-time.After(wait):
|
||||
case <-waitTimer.C:
|
||||
// Throttling up, fetch whatever is available
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/triedb"
|
||||
"github.com/gballet/go-verkle"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
|
|
@ -418,6 +419,112 @@ func GenerateChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int,
|
|||
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 {
|
||||
time := parent.Time() + 10 // block time is fixed at 10 seconds
|
||||
header := &types.Header{
|
||||
|
|
@ -482,7 +589,7 @@ func makeBlockChain(chainConfig *params.ChainConfig, parent *types.Block, n int,
|
|||
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) {
|
||||
db, blocks, _ := GenerateChainWithGenesis(genesis, engine, n, func(i int, b *BlockGen) {
|
||||
b.SetCoinbase(common.Address{0: byte(seed), 19: byte(i)})
|
||||
|
|
|
|||
|
|
@ -26,9 +26,6 @@ var (
|
|||
// ErrKnownBlock is returned when a block to import is already known locally.
|
||||
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 = errors.New("genesis not found in chain")
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
|
|||
if header.ExcessBlobGas != nil {
|
||||
blobBaseFee = eip4844.CalcBlobFee(*header.ExcessBlobGas)
|
||||
}
|
||||
if header.Difficulty.Cmp(common.Big0) == 0 {
|
||||
if header.Difficulty.Sign() == 0 {
|
||||
random = &header.MixDigest
|
||||
}
|
||||
return vm.BlockContext{
|
||||
|
|
|
|||
|
|
@ -19,21 +19,21 @@ var _ = (*genesisSpecMarshaling)(nil)
|
|||
// MarshalJSON marshals as JSON.
|
||||
func (g Genesis) MarshalJSON() ([]byte, error) {
|
||||
type Genesis struct {
|
||||
Config *params.ChainConfig `json:"config"`
|
||||
Nonce math.HexOrDecimal64 `json:"nonce"`
|
||||
Timestamp math.HexOrDecimal64 `json:"timestamp"`
|
||||
ExtraData hexutil.Bytes `json:"extraData"`
|
||||
GasLimit math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"`
|
||||
Difficulty *math.HexOrDecimal256 `json:"difficulty" gencodec:"required"`
|
||||
Mixhash common.Hash `json:"mixHash"`
|
||||
Coinbase common.Address `json:"coinbase"`
|
||||
Config *params.ChainConfig `json:"config"`
|
||||
Nonce math.HexOrDecimal64 `json:"nonce"`
|
||||
Timestamp math.HexOrDecimal64 `json:"timestamp"`
|
||||
ExtraData hexutil.Bytes `json:"extraData"`
|
||||
GasLimit math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"`
|
||||
Difficulty *math.HexOrDecimal256 `json:"difficulty" gencodec:"required"`
|
||||
Mixhash common.Hash `json:"mixHash"`
|
||||
Coinbase common.Address `json:"coinbase"`
|
||||
Alloc map[common.UnprefixedAddress]types.Account `json:"alloc" gencodec:"required"`
|
||||
Number math.HexOrDecimal64 `json:"number"`
|
||||
GasUsed math.HexOrDecimal64 `json:"gasUsed"`
|
||||
ParentHash common.Hash `json:"parentHash"`
|
||||
BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"`
|
||||
ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"`
|
||||
BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"`
|
||||
Number math.HexOrDecimal64 `json:"number"`
|
||||
GasUsed math.HexOrDecimal64 `json:"gasUsed"`
|
||||
ParentHash common.Hash `json:"parentHash"`
|
||||
BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"`
|
||||
ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"`
|
||||
BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"`
|
||||
}
|
||||
var enc Genesis
|
||||
enc.Config = g.Config
|
||||
|
|
@ -62,21 +62,21 @@ func (g Genesis) MarshalJSON() ([]byte, error) {
|
|||
// UnmarshalJSON unmarshals from JSON.
|
||||
func (g *Genesis) UnmarshalJSON(input []byte) error {
|
||||
type Genesis struct {
|
||||
Config *params.ChainConfig `json:"config"`
|
||||
Nonce *math.HexOrDecimal64 `json:"nonce"`
|
||||
Timestamp *math.HexOrDecimal64 `json:"timestamp"`
|
||||
ExtraData *hexutil.Bytes `json:"extraData"`
|
||||
GasLimit *math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"`
|
||||
Difficulty *math.HexOrDecimal256 `json:"difficulty" gencodec:"required"`
|
||||
Mixhash *common.Hash `json:"mixHash"`
|
||||
Coinbase *common.Address `json:"coinbase"`
|
||||
Config *params.ChainConfig `json:"config"`
|
||||
Nonce *math.HexOrDecimal64 `json:"nonce"`
|
||||
Timestamp *math.HexOrDecimal64 `json:"timestamp"`
|
||||
ExtraData *hexutil.Bytes `json:"extraData"`
|
||||
GasLimit *math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"`
|
||||
Difficulty *math.HexOrDecimal256 `json:"difficulty" gencodec:"required"`
|
||||
Mixhash *common.Hash `json:"mixHash"`
|
||||
Coinbase *common.Address `json:"coinbase"`
|
||||
Alloc map[common.UnprefixedAddress]types.Account `json:"alloc" gencodec:"required"`
|
||||
Number *math.HexOrDecimal64 `json:"number"`
|
||||
GasUsed *math.HexOrDecimal64 `json:"gasUsed"`
|
||||
ParentHash *common.Hash `json:"parentHash"`
|
||||
BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"`
|
||||
ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"`
|
||||
BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"`
|
||||
Number *math.HexOrDecimal64 `json:"number"`
|
||||
GasUsed *math.HexOrDecimal64 `json:"gasUsed"`
|
||||
ParentHash *common.Hash `json:"parentHash"`
|
||||
BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"`
|
||||
ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"`
|
||||
BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"`
|
||||
}
|
||||
var dec Genesis
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
|
|
|
|||
|
|
@ -582,7 +582,7 @@ func DeveloperGenesisBlock(gasLimit uint64, faucet *common.Address) *Genesis {
|
|||
Config: &config,
|
||||
GasLimit: gasLimit,
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
Difficulty: big.NewInt(1),
|
||||
Difficulty: big.NewInt(0),
|
||||
Alloc: map[common.Address]types.Account{
|
||||
common.BytesToAddress([]byte{1}): {Balance: big.NewInt(1)}, // ECRecover
|
||||
common.BytesToAddress([]byte{2}): {Balance: big.NewInt(1)}, // SHA256
|
||||
|
|
|
|||
|
|
@ -17,12 +17,9 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
crand "crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
mrand "math/rand"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
|
|
@ -43,45 +40,41 @@ const (
|
|||
numberCacheLimit = 2048
|
||||
)
|
||||
|
||||
// HeaderChain implements the basic block header chain logic that is shared by
|
||||
// core.BlockChain and light.LightChain. It is not usable in itself, only as
|
||||
// a part of either structure.
|
||||
// HeaderChain implements the basic block header chain logic. It is not usable
|
||||
// in itself, but rather an internal structure of core.Blockchain.
|
||||
//
|
||||
// HeaderChain is responsible for maintaining the header chain including the
|
||||
// header query and updating.
|
||||
//
|
||||
// The components maintained by headerchain includes: (1) total difficulty
|
||||
// (2) header (3) block hash -> number mapping (4) canonical number -> hash mapping
|
||||
// and (5) head header flag.
|
||||
// The data components maintained by HeaderChain include:
|
||||
//
|
||||
// It is not thread safe either, the encapsulating chain structures should do
|
||||
// the necessary mutex locking/unlocking.
|
||||
// - total difficulty
|
||||
// - 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 {
|
||||
config *params.ChainConfig
|
||||
chainDb ethdb.Database
|
||||
genesisHeader *types.Header
|
||||
|
||||
currentHeader atomic.Value // Current head of the header chain (may be above the block chain!)
|
||||
currentHeaderHash common.Hash // Hash of the current head of the header chain (prevent recomputing all the time)
|
||||
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)
|
||||
|
||||
headerCache *lru.Cache[common.Hash, *types.Header]
|
||||
tdCache *lru.Cache[common.Hash, *big.Int] // most recent total difficulties
|
||||
numberCache *lru.Cache[common.Hash, uint64] // most recent block numbers
|
||||
|
||||
procInterrupt func() bool
|
||||
|
||||
rand *mrand.Rand
|
||||
engine consensus.Engine
|
||||
engine consensus.Engine
|
||||
}
|
||||
|
||||
// NewHeaderChain creates a new HeaderChain structure. ProcInterrupt points
|
||||
// to the parent's interrupt semaphore.
|
||||
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{
|
||||
config: config,
|
||||
chainDb: chainDb,
|
||||
|
|
@ -89,7 +82,6 @@ func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine c
|
|||
tdCache: lru.NewCache[common.Hash, *big.Int](tdCacheLimit),
|
||||
numberCache: lru.NewCache[common.Hash, uint64](numberCacheLimit),
|
||||
procInterrupt: procInterrupt,
|
||||
rand: mrand.New(mrand.NewSource(seed.Int64())),
|
||||
engine: engine,
|
||||
}
|
||||
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,
|
||||
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
|
||||
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
|
||||
// header is retrieved from the HeaderChain's internal cache.
|
||||
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
|
||||
|
|
|
|||
|
|
@ -316,8 +316,8 @@ func ReadHeaderRange(db ethdb.Reader, number uint64, count uint64) []rlp.RawValu
|
|||
if count == 0 {
|
||||
return rlpHeaders
|
||||
}
|
||||
// read remaining from ancients
|
||||
data, err := db.AncientRange(ChainFreezerHeaderTable, i+1-count, count, 0)
|
||||
// read remaining from ancients, cap at 2M
|
||||
data, err := db.AncientRange(ChainFreezerHeaderTable, i+1-count, count, 2*1024*1024)
|
||||
if err != nil {
|
||||
log.Error("Failed to read headers from freezer", "err", err)
|
||||
return rlpHeaders
|
||||
|
|
@ -695,7 +695,7 @@ func (r *receiptLogs) DecodeRLP(s *rlp.Stream) error {
|
|||
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 {
|
||||
logIndex := uint(0)
|
||||
if len(txs) != len(receipts) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
data, _ := db.Get(storageSnapshotKey(accountHash, storageHash))
|
||||
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) {
|
||||
if err := db.Put(storageSnapshotKey(accountHash, storageHash), entry); err != nil {
|
||||
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) {
|
||||
if err := db.Delete(storageSnapshotKey(accountHash, storageHash)); err != nil {
|
||||
log.Crit("Failed to delete storage snapshot", "err", err)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
|
@ -172,7 +171,7 @@ func resolveChainFreezerDir(ancient string) string {
|
|||
// sub folder, if not then two possibilities:
|
||||
// - chain freezer is not initialized
|
||||
// - 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(ancient) {
|
||||
// The entire ancient store is not initialized, still use the sub
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import (
|
|||
"math/big"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
|
|
@ -398,11 +397,11 @@ func TestRenameWindows(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f2, err := os.Create(path.Join(dir1, fname2))
|
||||
f2, err := os.Create(filepath.Join(dir1, fname2))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f3, err := os.Create(path.Join(dir2, fname2))
|
||||
f3, err := os.Create(filepath.Join(dir2, fname2))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -424,15 +423,15 @@ func TestRenameWindows(t *testing.T) {
|
|||
if err := f3.Close(); err != nil {
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
// Check file contents
|
||||
f, err = os.Open(path.Join(dir2, fname))
|
||||
f, err = os.Open(filepath.Join(dir2, fname))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -446,7 +445,7 @@ func TestRenameWindows(t *testing.T) {
|
|||
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 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"maps"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
|
|
@ -57,16 +59,10 @@ func newAccessList() *accessList {
|
|||
// Copy creates an independent copy of an accessList.
|
||||
func (a *accessList) Copy() *accessList {
|
||||
cp := newAccessList()
|
||||
for k, v := range a.addresses {
|
||||
cp.addresses[k] = v
|
||||
}
|
||||
cp.addresses = maps.Clone(a.addresses)
|
||||
cp.slots = make([]map[common.Hash]struct{}, len(a.slots))
|
||||
for i, slotMap := range a.slots {
|
||||
newSlotmap := make(map[common.Hash]struct{}, len(slotMap))
|
||||
for k := range slotMap {
|
||||
newSlotmap[k] = struct{}{}
|
||||
}
|
||||
cp.slots[i] = newSlotmap
|
||||
cp.slots[i] = maps.Clone(slotMap)
|
||||
}
|
||||
return cp
|
||||
}
|
||||
|
|
|
|||
|
|
@ -209,6 +209,8 @@ func (db *cachingDB) CopyTrie(t Trie) Trie {
|
|||
switch t := t.(type) {
|
||||
case *trie.StateTrie:
|
||||
return t.Copy()
|
||||
case *trie.VerkleTrie:
|
||||
return t.Copy()
|
||||
default:
|
||||
panic(fmt.Errorf("unknown trie type %T", t))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ type nodeIterator struct {
|
|||
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 {
|
||||
return &nodeIterator{
|
||||
state: state,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"maps"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
|
@ -29,6 +31,9 @@ type journalEntry interface {
|
|||
|
||||
// dirtied returns the Ethereum address modified by this journal entry.
|
||||
dirtied() *common.Address
|
||||
|
||||
// copy returns a deep-copied journal entry.
|
||||
copy() journalEntry
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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 (
|
||||
// Changes to the account trie.
|
||||
createObjectChange struct {
|
||||
account *common.Address
|
||||
}
|
||||
resetObjectChange struct {
|
||||
account *common.Address
|
||||
prev *stateObject
|
||||
prevdestruct bool
|
||||
prevAccount []byte
|
||||
prevStorage map[common.Hash][]byte
|
||||
|
||||
prevAccountOriginExist bool
|
||||
prevAccountOrigin []byte
|
||||
prevStorageOrigin map[common.Hash][]byte
|
||||
// createContractChange represents an account becoming a contract-account.
|
||||
// This event happens prior to executing initcode. The journal-event simply
|
||||
// manages the created-flag, in order to allow same-tx destruction.
|
||||
createContractChange struct {
|
||||
account common.Address
|
||||
}
|
||||
|
||||
selfDestructChange struct {
|
||||
account *common.Address
|
||||
prev bool // whether account had already self-destructed
|
||||
|
|
@ -115,8 +129,9 @@ type (
|
|||
prev uint64
|
||||
}
|
||||
storageChange struct {
|
||||
account *common.Address
|
||||
key, prevalue common.Hash
|
||||
account *common.Address
|
||||
key common.Hash
|
||||
prevvalue *common.Hash
|
||||
}
|
||||
codeChange struct {
|
||||
account *common.Address
|
||||
|
|
@ -136,6 +151,7 @@ type (
|
|||
touchChange struct {
|
||||
account *common.Address
|
||||
}
|
||||
|
||||
// Changes to the access list
|
||||
accessListAddAccountChange struct {
|
||||
address *common.Address
|
||||
|
|
@ -145,6 +161,7 @@ type (
|
|||
slot *common.Hash
|
||||
}
|
||||
|
||||
// Changes to transient storage
|
||||
transientStorageChange struct {
|
||||
account *common.Address
|
||||
key, prevalue common.Hash
|
||||
|
|
@ -153,34 +170,30 @@ type (
|
|||
|
||||
func (ch createObjectChange) revert(s *StateDB) {
|
||||
delete(s.stateObjects, *ch.account)
|
||||
delete(s.stateObjectsDirty, *ch.account)
|
||||
}
|
||||
|
||||
func (ch createObjectChange) dirtied() *common.Address {
|
||||
return ch.account
|
||||
}
|
||||
|
||||
func (ch resetObjectChange) revert(s *StateDB) {
|
||||
s.setStateObject(ch.prev)
|
||||
if !ch.prevdestruct {
|
||||
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 createObjectChange) copy() journalEntry {
|
||||
return createObjectChange{
|
||||
account: ch.account,
|
||||
}
|
||||
}
|
||||
|
||||
func (ch resetObjectChange) dirtied() *common.Address {
|
||||
return ch.account
|
||||
func (ch createContractChange) revert(s *StateDB) {
|
||||
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) {
|
||||
|
|
@ -195,6 +208,14 @@ func (ch selfDestructChange) dirtied() *common.Address {
|
|||
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")
|
||||
|
||||
func (ch touchChange) revert(s *StateDB) {
|
||||
|
|
@ -204,6 +225,12 @@ func (ch touchChange) dirtied() *common.Address {
|
|||
return ch.account
|
||||
}
|
||||
|
||||
func (ch touchChange) copy() journalEntry {
|
||||
return touchChange{
|
||||
account: ch.account,
|
||||
}
|
||||
}
|
||||
|
||||
func (ch balanceChange) revert(s *StateDB) {
|
||||
s.getStateObject(*ch.account).setBalance(ch.prev)
|
||||
}
|
||||
|
|
@ -212,6 +239,13 @@ func (ch balanceChange) dirtied() *common.Address {
|
|||
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) {
|
||||
s.getStateObject(*ch.account).setNonce(ch.prev)
|
||||
}
|
||||
|
|
@ -220,6 +254,13 @@ func (ch nonceChange) dirtied() *common.Address {
|
|||
return ch.account
|
||||
}
|
||||
|
||||
func (ch nonceChange) copy() journalEntry {
|
||||
return nonceChange{
|
||||
account: ch.account,
|
||||
prev: ch.prev,
|
||||
}
|
||||
}
|
||||
|
||||
func (ch codeChange) revert(s *StateDB) {
|
||||
s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode)
|
||||
}
|
||||
|
|
@ -228,14 +269,30 @@ func (ch codeChange) dirtied() *common.Address {
|
|||
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) {
|
||||
s.getStateObject(*ch.account).setState(ch.key, ch.prevalue)
|
||||
s.getStateObject(*ch.account).setState(ch.key, ch.prevvalue)
|
||||
}
|
||||
|
||||
func (ch storageChange) dirtied() *common.Address {
|
||||
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) {
|
||||
s.setTransientState(*ch.account, ch.key, ch.prevalue)
|
||||
}
|
||||
|
|
@ -244,6 +301,14 @@ func (ch transientStorageChange) dirtied() *common.Address {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (ch transientStorageChange) copy() journalEntry {
|
||||
return transientStorageChange{
|
||||
account: ch.account,
|
||||
key: ch.key,
|
||||
prevalue: ch.prevalue,
|
||||
}
|
||||
}
|
||||
|
||||
func (ch refundChange) revert(s *StateDB) {
|
||||
s.refund = ch.prev
|
||||
}
|
||||
|
|
@ -252,6 +317,12 @@ func (ch refundChange) dirtied() *common.Address {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (ch refundChange) copy() journalEntry {
|
||||
return refundChange{
|
||||
prev: ch.prev,
|
||||
}
|
||||
}
|
||||
|
||||
func (ch addLogChange) revert(s *StateDB) {
|
||||
logs := s.logs[ch.txhash]
|
||||
if len(logs) == 1 {
|
||||
|
|
@ -266,6 +337,12 @@ func (ch addLogChange) dirtied() *common.Address {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (ch addLogChange) copy() journalEntry {
|
||||
return addLogChange{
|
||||
txhash: ch.txhash,
|
||||
}
|
||||
}
|
||||
|
||||
func (ch addPreimageChange) revert(s *StateDB) {
|
||||
delete(s.preimages, ch.hash)
|
||||
}
|
||||
|
|
@ -274,6 +351,12 @@ func (ch addPreimageChange) dirtied() *common.Address {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (ch addPreimageChange) copy() journalEntry {
|
||||
return addPreimageChange{
|
||||
hash: ch.hash,
|
||||
}
|
||||
}
|
||||
|
||||
func (ch accessListAddAccountChange) revert(s *StateDB) {
|
||||
/*
|
||||
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
|
||||
}
|
||||
|
||||
func (ch accessListAddAccountChange) copy() journalEntry {
|
||||
return accessListAddAccountChange{
|
||||
address: ch.address,
|
||||
}
|
||||
}
|
||||
|
||||
func (ch accessListAddSlotChange) revert(s *StateDB) {
|
||||
s.accessList.DeleteSlot(*ch.address, *ch.slot)
|
||||
}
|
||||
|
|
@ -298,3 +387,10 @@ func (ch accessListAddSlotChange) revert(s *StateDB) {
|
|||
func (ch accessListAddSlotChange) dirtied() *common.Address {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ch accessListAddSlotChange) copy() journalEntry {
|
||||
return accessListAddSlotChange{
|
||||
address: ch.address,
|
||||
slot: ch.slot,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ type generatorStats struct {
|
|||
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.
|
||||
func (gs *generatorStats) Log(msg string, root common.Hash, marker []byte) {
|
||||
var ctx []interface{}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
options := trie.NewStackTrieOptions()
|
||||
var onTrieNode trie.OnTrieNode
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
t := trie.NewStackTrie(options)
|
||||
t := trie.NewStackTrie(onTrieNode)
|
||||
for leaf := range in {
|
||||
t.Update(leaf.key[:], leaf.value)
|
||||
}
|
||||
out <- t.Commit()
|
||||
out <- t.Hash()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ func (dl *diffLayer) initBinaryStorageIterator(account common.Hash) Iterator {
|
|||
parent, ok := dl.parent.(*diffLayer)
|
||||
if !ok {
|
||||
// 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{})
|
||||
if destructed {
|
||||
l := &binaryIterator{
|
||||
|
|
@ -92,7 +92,7 @@ func (dl *diffLayer) initBinaryStorageIterator(account common.Hash) Iterator {
|
|||
return l
|
||||
}
|
||||
// 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{})
|
||||
if destructed {
|
||||
l := &binaryIterator{
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import (
|
|||
"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
|
||||
// same one (modified in multiple consecutive blocks).
|
||||
type weightedIterator struct {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
func (t *Tree) diskRoot() common.Hash {
|
||||
disklayer := t.disklayer()
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
|
@ -31,27 +32,10 @@ import (
|
|||
"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
|
||||
|
||||
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 {
|
||||
cpy := make(Storage, len(s))
|
||||
for key, value := range s {
|
||||
cpy[key] = value
|
||||
}
|
||||
return cpy
|
||||
return maps.Clone(s)
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// Write caches.
|
||||
trie Trie // storage trie, which becomes non-nil on first access
|
||||
code Code // contract bytecode, which gets set when code is loaded
|
||||
trie Trie // storage trie, which becomes non-nil on first access
|
||||
code []byte // contract bytecode, which gets set when code is loaded
|
||||
|
||||
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
|
||||
|
|
@ -78,17 +62,16 @@ type stateObject struct {
|
|||
// Cache flags.
|
||||
dirtyCode bool // true if the code was updated
|
||||
|
||||
// Flag whether the account was marked as self-destructed. The self-destructed account
|
||||
// is still accessible in the scope of same transaction.
|
||||
// Flag whether the account was marked as self-destructed. The self-destructed
|
||||
// account is still accessible in the scope of same transaction.
|
||||
selfDestructed bool
|
||||
|
||||
// Flag whether the account was marked as deleted. A self-destructed account
|
||||
// or an account that is considered as empty will be marked as deleted at
|
||||
// the end of transaction and no longer accessible anymore.
|
||||
deleted bool
|
||||
|
||||
// Flag whether the object was created in the current transaction
|
||||
created bool
|
||||
// This is an EIP-6780 flag indicating whether the object is eligible for
|
||||
// self-destruct according to EIP-6780. The flag could be set either when
|
||||
// the contract is just created within the current transaction, or when the
|
||||
// object was previously existent and is being deployed as a contract within
|
||||
// the current transaction.
|
||||
newContract bool
|
||||
}
|
||||
|
||||
// empty returns whether the account is considered empty.
|
||||
|
|
@ -98,10 +81,7 @@ func (s *stateObject) empty() bool {
|
|||
|
||||
// newObject creates a state object.
|
||||
func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *stateObject {
|
||||
var (
|
||||
origin = acct
|
||||
created = acct == nil // true if the account was not existent
|
||||
)
|
||||
origin := acct
|
||||
if acct == nil {
|
||||
acct = types.NewEmptyStateAccount()
|
||||
}
|
||||
|
|
@ -114,7 +94,6 @@ func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *s
|
|||
originStorage: make(Storage),
|
||||
pendingStorage: 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.
|
||||
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
|
||||
value, dirty := s.dirtyStorage[key]
|
||||
if dirty {
|
||||
return value
|
||||
return value, true
|
||||
}
|
||||
// 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.
|
||||
|
|
@ -230,25 +216,38 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
|
|||
|
||||
// SetState updates a value in account storage.
|
||||
func (s *stateObject) SetState(key, value common.Hash) {
|
||||
// If the new value is the same as old, don't set
|
||||
prev := s.GetState(key)
|
||||
// If the new value is the same as old, don't set. Otherwise, track only the
|
||||
// dirty changes, supporting reverting all of it back to no change.
|
||||
prev, dirty := s.getState(key)
|
||||
if prev == value {
|
||||
return
|
||||
}
|
||||
var prevvalue *common.Hash
|
||||
if dirty {
|
||||
prevvalue = &prev
|
||||
}
|
||||
// New value is different, update and journal the change
|
||||
s.db.journal.append(storageChange{
|
||||
account: &s.address,
|
||||
key: key,
|
||||
prevalue: prev,
|
||||
account: &s.address,
|
||||
key: key,
|
||||
prevvalue: prevvalue,
|
||||
})
|
||||
if s.db.logger != nil && s.db.logger.OnStorageChange != nil {
|
||||
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) {
|
||||
s.dirtyStorage[key] = value
|
||||
// setState updates a value in account dirty storage. If the value being set is
|
||||
// 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
|
||||
|
|
@ -267,6 +266,10 @@ func (s *stateObject) finalise(prefetch bool) {
|
|||
if len(s.dirtyStorage) > 0 {
|
||||
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
|
||||
|
|
@ -298,6 +301,18 @@ func (s *stateObject) updateTrie() (Trie, error) {
|
|||
}
|
||||
// Insert all the pending storage updates into the trie
|
||||
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 {
|
||||
// Skip noop changes, persist actual changes
|
||||
if value == s.originStorage[key] {
|
||||
|
|
@ -307,13 +322,7 @@ func (s *stateObject) updateTrie() (Trie, error) {
|
|||
s.originStorage[key] = value
|
||||
|
||||
var encoded []byte // rlp-encoded value to be used by the snapshot
|
||||
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 {
|
||||
if (value != common.Hash{}) {
|
||||
// Encoding []byte cannot fail, ok to ignore the error.
|
||||
trimmed := common.TrimLeftZeroes(value[:])
|
||||
encoded, _ = rlp.EncodeToBytes(trimmed)
|
||||
|
|
@ -322,6 +331,8 @@ func (s *stateObject) updateTrie() (Trie, error) {
|
|||
return nil, err
|
||||
}
|
||||
s.db.StorageUpdated += 1
|
||||
} else {
|
||||
deletions = append(deletions, key)
|
||||
}
|
||||
// Cache the mutated storage slots until commit
|
||||
if storage == nil {
|
||||
|
|
@ -353,6 +364,13 @@ func (s *stateObject) updateTrie() (Trie, error) {
|
|||
// Cache the items for preloading
|
||||
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 {
|
||||
s.db.prefetcher.used(s.addrHash, s.data.Root, usedStorage)
|
||||
}
|
||||
|
|
@ -364,7 +382,7 @@ func (s *stateObject) updateTrie() (Trie, error) {
|
|||
// new storage trie root.
|
||||
func (s *stateObject) updateRoot() {
|
||||
// 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()
|
||||
if err != nil || tr == nil {
|
||||
return
|
||||
|
|
@ -451,12 +469,12 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject {
|
|||
obj.trie = db.db.CopyTrie(s.trie)
|
||||
}
|
||||
obj.code = s.code
|
||||
obj.dirtyStorage = s.dirtyStorage.Copy()
|
||||
obj.originStorage = s.originStorage.Copy()
|
||||
obj.pendingStorage = s.pendingStorage.Copy()
|
||||
obj.selfDestructed = s.selfDestructed
|
||||
obj.dirtyStorage = s.dirtyStorage.Copy()
|
||||
obj.dirtyCode = s.dirtyCode
|
||||
obj.deleted = s.deleted
|
||||
obj.selfDestructed = s.selfDestructed
|
||||
obj.newContract = s.newContract
|
||||
return obj
|
||||
}
|
||||
|
||||
|
|
@ -471,7 +489,7 @@ func (s *stateObject) Address() common.Address {
|
|||
|
||||
// Code returns the contract code associated with this object, if any.
|
||||
func (s *stateObject) Code() []byte {
|
||||
if s.code != nil {
|
||||
if len(s.code) != 0 {
|
||||
return s.code
|
||||
}
|
||||
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
|
||||
// inside the database to avoid loading codes seen recently.
|
||||
func (s *stateObject) CodeSize() int {
|
||||
if s.code != nil {
|
||||
if len(s.code) != 0 {
|
||||
return len(s.code)
|
||||
}
|
||||
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {
|
||||
|
|
|
|||
|
|
@ -194,106 +194,20 @@ func TestSnapshotEmpty(t *testing.T) {
|
|||
s.state.RevertToSnapshot(s.state.Snapshot())
|
||||
}
|
||||
|
||||
func TestSnapshot2(t *testing.T) {
|
||||
func TestCreateObjectRevert(t *testing.T) {
|
||||
state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
||||
addr := common.BytesToAddress([]byte("so0"))
|
||||
snap := state.Snapshot()
|
||||
|
||||
stateobjaddr0 := common.BytesToAddress([]byte("so0"))
|
||||
stateobjaddr1 := common.BytesToAddress([]byte("so1"))
|
||||
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)
|
||||
state.CreateAccount(addr)
|
||||
so0 := state.getStateObject(addr)
|
||||
so0.SetBalance(uint256.NewInt(42), tracing.BalanceChangeUnspecified)
|
||||
so0.SetNonce(43)
|
||||
so0.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e'}), []byte{'c', 'a', 'f', 'e'})
|
||||
so0.selfDestructed = false
|
||||
so0.deleted = false
|
||||
state.setStateObject(so0)
|
||||
|
||||
root, _ := state.Commit(0, false)
|
||||
state, _ = New(root, state.db, state.snaps)
|
||||
|
||||
// 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)
|
||||
}
|
||||
state.RevertToSnapshot(snap)
|
||||
if state.Exist(addr) {
|
||||
t.Error("Unexpected account after revert")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@ package state
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"math/big"
|
||||
"slices"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
|
|
@ -42,6 +44,26 @@ type revision struct {
|
|||
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
|
||||
// within the merkle trie. StateDBs take care of caching and storing
|
||||
// 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
|
||||
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
|
||||
// a state transition.
|
||||
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
|
||||
stateObjectsDestruct map[common.Address]*types.StateAccount // State objects destructed in the block along with its previous value
|
||||
// This map holds 'live' objects, which will get modified while
|
||||
// processing a state transition.
|
||||
stateObjects map[common.Address]*stateObject
|
||||
|
||||
// This map holds 'deleted' objects. An object with the same address
|
||||
// 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.
|
||||
// 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),
|
||||
storagesOrigin: make(map[common.Address]map[common.Hash][]byte),
|
||||
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),
|
||||
mutations: make(map[common.Address]*mutation),
|
||||
logs: make(map[common.Hash][]*types.Log),
|
||||
preimages: make(map[common.Hash][]byte),
|
||||
journal: newJournal(),
|
||||
|
|
@ -243,9 +274,7 @@ func (s *StateDB) Logs() []*types.Log {
|
|||
func (s *StateDB) AddPreimage(hash common.Hash, preimage []byte) {
|
||||
if _, ok := s.preimages[hash]; !ok {
|
||||
s.journal.append(addPreimageChange{hash: hash})
|
||||
pi := make([]byte, len(preimage))
|
||||
copy(pi, preimage)
|
||||
s.preimages[hash] = pi
|
||||
s.preimages[hash] = slices.Clone(preimage)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -472,8 +501,7 @@ func (s *StateDB) Selfdestruct6780(addr common.Address) {
|
|||
if stateObject == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if stateObject.created {
|
||||
if stateObject.newContract {
|
||||
s.SelfDestruct(addr)
|
||||
}
|
||||
}
|
||||
|
|
@ -541,36 +569,27 @@ func (s *StateDB) updateStateObject(obj *stateObject) {
|
|||
}
|
||||
|
||||
// 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
|
||||
defer func(start time.Time) { s.AccountUpdates += time.Since(start) }(time.Now())
|
||||
|
||||
// Delete the account from the trie
|
||||
addr := obj.Address()
|
||||
if err := s.trie.DeleteAccount(addr); err != nil {
|
||||
s.setError(fmt.Errorf("deleteStateObject (%x) error: %v", addr[:], err))
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// to differentiate between non-existent/just-deleted, use getDeletedStateObject.
|
||||
// the object is not found or was deleted in this execution context.
|
||||
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
|
||||
if obj := s.stateObjects[addr]; obj != nil {
|
||||
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
|
||||
var data *types.StateAccount
|
||||
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.
|
||||
func (s *StateDB) getOrNewStateObject(addr common.Address) *stateObject {
|
||||
stateObject := s.getStateObject(addr)
|
||||
if stateObject == nil {
|
||||
stateObject, _ = s.createObject(addr)
|
||||
obj := s.getStateObject(addr)
|
||||
if obj == nil {
|
||||
obj = s.createObject(addr)
|
||||
}
|
||||
return stateObject
|
||||
return obj
|
||||
}
|
||||
|
||||
// createObject creates a new state object. If there is an existing account with
|
||||
// the given address, it is overwritten and returned as the second return value.
|
||||
func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) {
|
||||
prev = s.getDeletedStateObject(addr) // Note, prev might have been deleted, we need that!
|
||||
newobj = newObject(s, addr, nil)
|
||||
if prev == nil {
|
||||
s.journal.append(createObjectChange{account: &addr})
|
||||
} 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
|
||||
// createObject creates a new state object. The assumption is held there is no
|
||||
// existing account with the given address, otherwise it will be silently overwritten.
|
||||
func (s *StateDB) createObject(addr common.Address) *stateObject {
|
||||
obj := newObject(s, addr, nil)
|
||||
s.journal.append(createObjectChange{account: &addr})
|
||||
s.setStateObject(obj)
|
||||
return obj
|
||||
}
|
||||
|
||||
// CreateAccount explicitly creates a state object. If a state object with the address
|
||||
// already exists the balance is carried over to the new account.
|
||||
//
|
||||
// CreateAccount is called during the EVM CREATE operation. The situation might arise that
|
||||
// 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.
|
||||
// CreateAccount explicitly creates a new state object, assuming that the
|
||||
// account did not previously exist in the state. If the account already
|
||||
// exists, this function will silently overwrite it which might lead to a
|
||||
// consensus bug eventually.
|
||||
func (s *StateDB) CreateAccount(addr common.Address) {
|
||||
newObj, prev := s.createObject(addr)
|
||||
if prev != nil {
|
||||
newObj.setBalance(prev.data.Balance)
|
||||
s.createObject(addr)
|
||||
}
|
||||
|
||||
// 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{
|
||||
db: s.db,
|
||||
trie: s.db.CopyTrie(s.trie),
|
||||
hasher: crypto.NewKeccakState(),
|
||||
originalRoot: s.originalRoot,
|
||||
accounts: make(map[common.Hash][]byte),
|
||||
storages: make(map[common.Hash]map[common.Hash][]byte),
|
||||
accountsOrigin: make(map[common.Address][]byte),
|
||||
storagesOrigin: make(map[common.Address]map[common.Hash][]byte),
|
||||
stateObjects: make(map[common.Address]*stateObject, len(s.journal.dirties)),
|
||||
stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)),
|
||||
stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)),
|
||||
stateObjectsDestruct: make(map[common.Address]*types.StateAccount, len(s.stateObjectsDestruct)),
|
||||
accounts: copySet(s.accounts),
|
||||
storages: copy2DSet(s.storages),
|
||||
accountsOrigin: copySet(s.accountsOrigin),
|
||||
storagesOrigin: copy2DSet(s.storagesOrigin),
|
||||
stateObjects: make(map[common.Address]*stateObject, len(s.stateObjects)),
|
||||
stateObjectsDestruct: maps.Clone(s.stateObjectsDestruct),
|
||||
mutations: make(map[common.Address]*mutation, len(s.mutations)),
|
||||
dbErr: s.dbErr,
|
||||
refund: s.refund,
|
||||
thash: s.thash,
|
||||
txIndex: s.txIndex,
|
||||
logs: make(map[common.Hash][]*types.Log, len(s.logs)),
|
||||
logSize: s.logSize,
|
||||
preimages: make(map[common.Hash][]byte, len(s.preimages)),
|
||||
journal: newJournal(),
|
||||
hasher: crypto.NewKeccakState(),
|
||||
preimages: maps.Clone(s.preimages),
|
||||
journal: s.journal.copy(),
|
||||
validRevisions: slices.Clone(s.validRevisions),
|
||||
nextRevisionId: s.nextRevisionId,
|
||||
|
||||
// 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
|
||||
|
|
@ -719,49 +713,14 @@ func (s *StateDB) Copy() *StateDB {
|
|||
snaps: s.snaps,
|
||||
snap: s.snap,
|
||||
}
|
||||
// Copy the dirty states, logs, and preimages
|
||||
for addr := range s.journal.dirties {
|
||||
// As documented [here](https://github.com/ethereum/go-ethereum/pull/16485#issuecomment-380438527),
|
||||
// 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
|
||||
}
|
||||
// Deep copy cached state objects.
|
||||
for addr, obj := range s.stateObjects {
|
||||
state.stateObjects[addr] = obj.deepCopy(state)
|
||||
}
|
||||
// Above, we don't copy the actual journal. This means that if the copy
|
||||
// is copied, the loop above will be a no-op, since the copy's journal
|
||||
// is empty. Thus, here we iterate over stateObjects, to enable copies
|
||||
// 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{}{}
|
||||
// Deep copy the object state markers.
|
||||
for addr, op := range s.mutations {
|
||||
state.mutations[addr] = op.copy()
|
||||
}
|
||||
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
|
||||
for hash, logs := range s.logs {
|
||||
cpy := make([]*types.Log, len(logs))
|
||||
|
|
@ -771,10 +730,6 @@ func (s *StateDB) Copy() *StateDB {
|
|||
}
|
||||
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?
|
||||
// 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
|
||||
|
|
@ -839,7 +794,8 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
|
|||
continue
|
||||
}
|
||||
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 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)
|
||||
} else {
|
||||
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
|
||||
// will start loading tries, and when the change is eventually committed,
|
||||
// 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
|
||||
// first, giving the account prefetches just a few more milliseconds of time
|
||||
// to pull useful data from disk.
|
||||
for addr := range s.stateObjectsPending {
|
||||
if obj := s.stateObjects[addr]; !obj.deleted {
|
||||
obj.updateRoot()
|
||||
for addr, op := range s.mutations {
|
||||
if op.applied {
|
||||
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
|
||||
// _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
|
||||
}
|
||||
}
|
||||
usedAddrs := make([][]byte, 0, len(s.stateObjectsPending))
|
||||
for addr := range s.stateObjectsPending {
|
||||
if obj := s.stateObjects[addr]; obj.deleted {
|
||||
s.deleteStateObject(obj)
|
||||
s.AccountDeleted += 1
|
||||
// Perform 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` 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 {
|
||||
s.updateStateObject(obj)
|
||||
s.updateStateObject(s.stateObjects[addr])
|
||||
s.AccountUpdated += 1
|
||||
}
|
||||
usedAddrs = append(usedAddrs, common.CopyBytes(addr[:])) // Copy needed for closure
|
||||
}
|
||||
for _, deletedAddr := range deletedAddrs {
|
||||
s.deleteStateObject(deletedAddr)
|
||||
s.AccountDeleted += 1
|
||||
}
|
||||
if prefetcher != nil {
|
||||
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
|
||||
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)
|
||||
slots = make(map[common.Hash][]byte)
|
||||
)
|
||||
options := trie.NewStackTrieOptions()
|
||||
options = options.WithWriter(func(path []byte, hash common.Hash, blob []byte) {
|
||||
stack := trie.NewStackTrie(func(path []byte, hash common.Hash, blob []byte) {
|
||||
nodes.AddNode(path, trienode.NewDeleted())
|
||||
size += common.StorageSize(len(path))
|
||||
})
|
||||
stack := trie.NewStackTrie(options)
|
||||
for iter.Next() {
|
||||
slot := common.CopyBytes(iter.Slot())
|
||||
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
|
||||
}
|
||||
|
||||
// GetTrie returns the account trie.
|
||||
func (s *StateDB) GetTrie() Trie {
|
||||
return s.trie
|
||||
}
|
||||
|
||||
// Commit writes the state to the underlying in-memory trie database.
|
||||
// Once the state is committed, tries cached in stateDB (including account
|
||||
// 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
|
||||
}
|
||||
// Handle all state updates afterwards
|
||||
for addr := range s.stateObjectsDirty {
|
||||
obj := s.stateObjects[addr]
|
||||
if obj.deleted {
|
||||
for addr, op := range s.mutations {
|
||||
if op.isDelete() {
|
||||
continue
|
||||
}
|
||||
obj := s.stateObjects[addr]
|
||||
|
||||
// Write any contract code associated with the state object
|
||||
if obj.code != nil && obj.dirtyCode {
|
||||
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.accountsOrigin = make(map[common.Address][]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)
|
||||
return root, nil
|
||||
}
|
||||
|
|
@ -1392,3 +1371,19 @@ func copy2DSet[k comparable](set map[k]map[common.Hash][]byte) map[k]map[common.
|
|||
}
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,7 +96,9 @@ func newStateTestAction(addr common.Address, r *rand.Rand, index int) testAction
|
|||
{
|
||||
name: "CreateAccount",
|
||||
fn: func(a testAction, s *StateDB) {
|
||||
s.CreateAccount(addr)
|
||||
if !s.Exist(addr) {
|
||||
s.CreateAccount(addr)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
config := &quick.Config{MaxCount: 1000}
|
||||
err := quick.Check((*snapshotTest).run, config)
|
||||
|
|
@ -308,7 +380,30 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction {
|
|||
{
|
||||
name: "CreateAccount",
|
||||
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) {
|
||||
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
|
||||
addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe")
|
||||
skey := common.HexToHash("aaa")
|
||||
sval := common.HexToHash("bbb")
|
||||
skey1, skey2 := common.HexToHash("a1"), common.HexToHash("a2")
|
||||
sval1, sval2 := common.HexToHash("b1"), common.HexToHash("b2")
|
||||
|
||||
state.SetBalance(addr, uint256.NewInt(42), tracing.BalanceChangeUnspecified) // Change the account trie
|
||||
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 {
|
||||
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")) {
|
||||
t.Fatalf("initial code mismatch: have %x, want %x", code, []byte("hello"))
|
||||
}
|
||||
if val := state.GetState(addr, skey); val != sval {
|
||||
t.Fatalf("initial non-committed storage slot mismatch: have %x, want %x", val, sval)
|
||||
if val := state.GetState(addr, skey1); val != sval1 {
|
||||
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{})
|
||||
}
|
||||
// Copy the committed state database, the copied one is not functional.
|
||||
state.Commit(0, true)
|
||||
root, _ := 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()
|
||||
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)
|
||||
}
|
||||
if code := copied.GetCode(addr); code != nil {
|
||||
if code := copied.GetCode(addr); !bytes.Equal(code, []byte("hello")) {
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
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) {
|
||||
var (
|
||||
disk = rawdb.NewMemoryDatabase()
|
||||
|
|
@ -1178,7 +1253,7 @@ func TestDeleteStorage(t *testing.T) {
|
|||
t.Fatal("delete should have empty hashes")
|
||||
}
|
||||
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))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
||||
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 = ¶ms.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
69
core/tracing/CHANGELOG.md
Normal 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
|
||||
|
|
@ -107,8 +107,8 @@ type (
|
|||
// BlockchainInitHook is called when the blockchain is initialized.
|
||||
BlockchainInitHook = func(chainConfig *params.ChainConfig)
|
||||
|
||||
// BlockchainTerminateHook is called when the tracer is terminated.
|
||||
BlockchainTerminateHook = func()
|
||||
// CloseHook is called when the blockchain closes.
|
||||
CloseHook = func()
|
||||
|
||||
// BlockStartHook is called before executing `block`.
|
||||
// `td` is the total difficulty prior to `block`.
|
||||
|
|
@ -155,12 +155,12 @@ type Hooks struct {
|
|||
OnFault FaultHook
|
||||
OnGasChange GasChangeHook
|
||||
// Chain events
|
||||
OnBlockchainInit BlockchainInitHook
|
||||
OnBlockchainTerminate BlockchainTerminateHook
|
||||
OnBlockStart BlockStartHook
|
||||
OnBlockEnd BlockEndHook
|
||||
OnSkippedBlock SkippedBlockHook
|
||||
OnGenesisBlock GenesisBlockHook
|
||||
OnBlockchainInit BlockchainInitHook
|
||||
OnClose CloseHook
|
||||
OnBlockStart BlockStartHook
|
||||
OnBlockEnd BlockEndHook
|
||||
OnSkippedBlock SkippedBlockHook
|
||||
OnGenesisBlock GenesisBlockHook
|
||||
// State events
|
||||
OnBalanceChange BalanceChangeHook
|
||||
OnNonceChange NonceChangeHook
|
||||
|
|
|
|||
|
|
@ -1226,7 +1226,7 @@ func (p *BlobPool) Add(txs []*types.Transaction, local bool, sync bool) []error
|
|||
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).
|
||||
func (p *BlobPool) add(tx *types.Transaction) (err error) {
|
||||
// The blob pool blocks on adding a transaction. This is because blob txs are
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ func newLimbo(datadir string) (*limbo, error) {
|
|||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -197,8 +197,8 @@ type Block struct {
|
|||
withdrawals Withdrawals
|
||||
|
||||
// caches
|
||||
hash atomic.Value
|
||||
size atomic.Value
|
||||
hash atomic.Pointer[common.Hash]
|
||||
size atomic.Uint64
|
||||
|
||||
// These fields are used by package eth to track
|
||||
// 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
|
||||
// and returning it, or returning a previously cached value.
|
||||
func (b *Block) Size() uint64 {
|
||||
if size := b.size.Load(); size != nil {
|
||||
return size.(uint64)
|
||||
if size := b.size.Load(); size > 0 {
|
||||
return size
|
||||
}
|
||||
c := writeCounter(0)
|
||||
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.
|
||||
func (b *Block) Hash() common.Hash {
|
||||
if hash := b.hash.Load(); hash != nil {
|
||||
return hash.(common.Hash)
|
||||
return *hash
|
||||
}
|
||||
v := b.header.Hash()
|
||||
b.hash.Store(v)
|
||||
return v
|
||||
h := b.header.Hash()
|
||||
b.hash.Store(&h)
|
||||
return h
|
||||
}
|
||||
|
||||
type Blocks []*Block
|
||||
|
|
|
|||
|
|
@ -57,9 +57,9 @@ type Transaction struct {
|
|||
time time.Time // Time first seen locally (spam avoidance)
|
||||
|
||||
// caches
|
||||
hash atomic.Value
|
||||
size atomic.Value
|
||||
from atomic.Value
|
||||
hash atomic.Pointer[common.Hash]
|
||||
size atomic.Uint64
|
||||
from atomic.Pointer[sigCache]
|
||||
}
|
||||
|
||||
// NewTx creates a new transaction.
|
||||
|
|
@ -446,6 +446,26 @@ func (tx *Transaction) WithoutBlobTxSidecar() *Transaction {
|
|||
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
|
||||
// arbitrary times and by persistent transaction pools when loading old txs from
|
||||
// disk.
|
||||
|
|
@ -462,7 +482,7 @@ func (tx *Transaction) Time() time.Time {
|
|||
// Hash returns the transaction hash.
|
||||
func (tx *Transaction) Hash() common.Hash {
|
||||
if hash := tx.hash.Load(); hash != nil {
|
||||
return hash.(common.Hash)
|
||||
return *hash
|
||||
}
|
||||
|
||||
var h common.Hash
|
||||
|
|
@ -471,15 +491,15 @@ func (tx *Transaction) Hash() common.Hash {
|
|||
} else {
|
||||
h = prefixedRlpHash(tx.Type(), tx.inner)
|
||||
}
|
||||
tx.hash.Store(h)
|
||||
tx.hash.Store(&h)
|
||||
return h
|
||||
}
|
||||
|
||||
// Size returns the true encoded storage size of the transaction, either by encoding
|
||||
// and returning it, or returning a previously cached value.
|
||||
func (tx *Transaction) Size() uint64 {
|
||||
if size := tx.size.Load(); size != nil {
|
||||
return size.(uint64)
|
||||
if size := tx.size.Load(); size > 0 {
|
||||
return size
|
||||
}
|
||||
|
||||
// Cache miss, encode and cache.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
// not match the signer used in the current call.
|
||||
func Sender(signer Signer, tx *Transaction) (common.Address, error) {
|
||||
if sc := tx.from.Load(); sc != nil {
|
||||
sigCache := sc.(sigCache)
|
||||
if sigCache := tx.from.Load(); sigCache != nil {
|
||||
// If the signer used to derive from in a previous
|
||||
// call is not the same as used current, invalidate
|
||||
// the cache.
|
||||
|
|
@ -142,7 +141,7 @@ func Sender(signer Signer, tx *Transaction) (common.Address, error) {
|
|||
if err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
tx.from.Store(sigCache{signer: signer, from: addr})
|
||||
tx.from.Store(&sigCache{signer: signer, from: addr})
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
|
@ -515,10 +516,7 @@ func TestYParityJSONUnmarshalling(t *testing.T) {
|
|||
test := test
|
||||
t.Run(fmt.Sprintf("txType=%d: %s", txType, test.name), func(t *testing.T) {
|
||||
// Copy the base json
|
||||
testJson := make(map[string]interface{})
|
||||
for k, v := range baseJson {
|
||||
testJson[k] = v
|
||||
}
|
||||
testJson := maps.Clone(baseJson)
|
||||
|
||||
// Set v, yParity and type
|
||||
if test.v != "" {
|
||||
|
|
|
|||
|
|
@ -191,6 +191,12 @@ func (tx *BlobTx) withoutSidecar() *BlobTx {
|
|||
return &cpy
|
||||
}
|
||||
|
||||
func (tx *BlobTx) withSidecar(sideCar *BlobTxSidecar) *BlobTx {
|
||||
cpy := *tx
|
||||
cpy.Sidecar = sideCar
|
||||
return &cpy
|
||||
}
|
||||
|
||||
func (tx *BlobTx) encode(b *bytes.Buffer) error {
|
||||
if tx.Sidecar == nil {
|
||||
return rlp.Encode(b, tx)
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue