mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 02:42:27 +00:00
Merge branch 'master' into reenable-tests
This commit is contained in:
commit
d44951b695
59 changed files with 931 additions and 962 deletions
|
|
@ -25,7 +25,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
// typeWithoutStringer is a alias for the Type type which simply doesn't implement
|
// typeWithoutStringer is an alias for the Type type which simply doesn't implement
|
||||||
// the stringer interface to allow printing type details in the tests below.
|
// the stringer interface to allow printing type details in the tests below.
|
||||||
type typeWithoutStringer Type
|
type typeWithoutStringer Type
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ type beaconBlockSync struct {
|
||||||
|
|
||||||
type headTracker interface {
|
type headTracker interface {
|
||||||
PrefetchHead() types.HeadInfo
|
PrefetchHead() types.HeadInfo
|
||||||
ValidatedHead() (types.SignedHeader, bool)
|
ValidatedOptimistic() (types.OptimisticUpdate, bool)
|
||||||
ValidatedFinality() (types.FinalityUpdate, bool)
|
ValidatedFinality() (types.FinalityUpdate, bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -66,6 +66,7 @@ func (s *beaconBlockSync) Process(requester request.Requester, events []request.
|
||||||
case request.EvResponse, request.EvFail, request.EvTimeout:
|
case request.EvResponse, request.EvFail, request.EvTimeout:
|
||||||
sid, req, resp := event.RequestInfo()
|
sid, req, resp := event.RequestInfo()
|
||||||
blockRoot := common.Hash(req.(sync.ReqBeaconBlock))
|
blockRoot := common.Hash(req.(sync.ReqBeaconBlock))
|
||||||
|
log.Debug("Beacon block event", "type", event.Type.Name, "hash", blockRoot)
|
||||||
if resp != nil {
|
if resp != nil {
|
||||||
s.recentBlocks.Add(blockRoot, resp.(*types.BeaconBlock))
|
s.recentBlocks.Add(blockRoot, resp.(*types.BeaconBlock))
|
||||||
}
|
}
|
||||||
|
|
@ -80,8 +81,8 @@ func (s *beaconBlockSync) Process(requester request.Requester, events []request.
|
||||||
}
|
}
|
||||||
s.updateEventFeed()
|
s.updateEventFeed()
|
||||||
// request validated head block if unavailable and not yet requested
|
// request validated head block if unavailable and not yet requested
|
||||||
if vh, ok := s.headTracker.ValidatedHead(); ok {
|
if vh, ok := s.headTracker.ValidatedOptimistic(); ok {
|
||||||
s.tryRequestBlock(requester, vh.Header.Hash(), false)
|
s.tryRequestBlock(requester, vh.Attested.Hash(), false)
|
||||||
}
|
}
|
||||||
// request prefetch head if the given server has announced it
|
// request prefetch head if the given server has announced it
|
||||||
if prefetchHead := s.headTracker.PrefetchHead().BlockRoot; prefetchHead != (common.Hash{}) {
|
if prefetchHead := s.headTracker.PrefetchHead().BlockRoot; prefetchHead != (common.Hash{}) {
|
||||||
|
|
@ -114,12 +115,12 @@ func blockHeadInfo(block *types.BeaconBlock) types.HeadInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *beaconBlockSync) updateEventFeed() {
|
func (s *beaconBlockSync) updateEventFeed() {
|
||||||
head, ok := s.headTracker.ValidatedHead()
|
optimistic, ok := s.headTracker.ValidatedOptimistic()
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
validatedHead := head.Header.Hash()
|
validatedHead := optimistic.Attested.Hash()
|
||||||
headBlock, ok := s.recentBlocks.Get(validatedHead)
|
headBlock, ok := s.recentBlocks.Get(validatedHead)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
|
|
@ -127,7 +128,7 @@ func (s *beaconBlockSync) updateEventFeed() {
|
||||||
|
|
||||||
var finalizedHash common.Hash
|
var finalizedHash common.Hash
|
||||||
if finality, ok := s.headTracker.ValidatedFinality(); ok {
|
if finality, ok := s.headTracker.ValidatedFinality(); ok {
|
||||||
he := head.Header.Epoch()
|
he := optimistic.Attested.Epoch()
|
||||||
fe := finality.Attested.Header.Epoch()
|
fe := finality.Attested.Header.Epoch()
|
||||||
switch {
|
switch {
|
||||||
case he == fe:
|
case he == fe:
|
||||||
|
|
@ -135,10 +136,9 @@ func (s *beaconBlockSync) updateEventFeed() {
|
||||||
case he < fe:
|
case he < fe:
|
||||||
return
|
return
|
||||||
case he == fe+1:
|
case he == fe+1:
|
||||||
parent, ok := s.recentBlocks.Get(head.Header.ParentRoot)
|
parent, ok := s.recentBlocks.Get(optimistic.Attested.ParentRoot)
|
||||||
if !ok || parent.Slot()/params.EpochLength == fe {
|
if !ok || parent.Slot()/params.EpochLength == fe {
|
||||||
return // head is at first slot of next epoch, wait for finality update
|
return // head is at first slot of next epoch, wait for finality update
|
||||||
//TODO: try to fetch finality update directly if subscription does not deliver
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -156,7 +156,7 @@ func (s *beaconBlockSync) updateEventFeed() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.chainHeadFeed.Send(types.ChainHeadEvent{
|
s.chainHeadFeed.Send(types.ChainHeadEvent{
|
||||||
BeaconHead: head.Header,
|
BeaconHead: optimistic.Attested.Header,
|
||||||
Block: execBlock,
|
Block: execBlock,
|
||||||
Finalized: finalizedHash,
|
Finalized: finalizedHash,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -140,8 +140,12 @@ func (h *testHeadTracker) PrefetchHead() types.HeadInfo {
|
||||||
return h.prefetch
|
return h.prefetch
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *testHeadTracker) ValidatedHead() (types.SignedHeader, bool) {
|
func (h *testHeadTracker) ValidatedOptimistic() (types.OptimisticUpdate, bool) {
|
||||||
return h.validated, h.validated.Header != (types.Header{})
|
return types.OptimisticUpdate{
|
||||||
|
Attested: types.HeaderWithExecProof{Header: h.validated.Header},
|
||||||
|
Signature: h.validated.Signature,
|
||||||
|
SignatureSlot: h.validated.SignatureSlot,
|
||||||
|
}, h.validated.Header != (types.Header{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO add test case for finality
|
// TODO add test case for finality
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ func (ec *engineClient) updateLoop(headCh <-chan types.ChainHeadEvent) {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ec.rootCtx.Done():
|
case <-ec.rootCtx.Done():
|
||||||
|
log.Debug("Stopping engine API update loop")
|
||||||
return
|
return
|
||||||
|
|
||||||
case event := <-headCh:
|
case event := <-headCh:
|
||||||
|
|
@ -73,12 +74,14 @@ func (ec *engineClient) updateLoop(headCh <-chan types.ChainHeadEvent) {
|
||||||
fork := ec.config.ForkAtEpoch(event.BeaconHead.Epoch())
|
fork := ec.config.ForkAtEpoch(event.BeaconHead.Epoch())
|
||||||
forkName := strings.ToLower(fork.Name)
|
forkName := strings.ToLower(fork.Name)
|
||||||
|
|
||||||
|
log.Debug("Calling NewPayload", "number", event.Block.NumberU64(), "hash", event.Block.Hash())
|
||||||
if status, err := ec.callNewPayload(forkName, event); err == nil {
|
if status, err := ec.callNewPayload(forkName, event); err == nil {
|
||||||
log.Info("Successful NewPayload", "number", event.Block.NumberU64(), "hash", event.Block.Hash(), "status", status)
|
log.Info("Successful NewPayload", "number", event.Block.NumberU64(), "hash", event.Block.Hash(), "status", status)
|
||||||
} else {
|
} else {
|
||||||
log.Error("Failed NewPayload", "number", event.Block.NumberU64(), "hash", event.Block.Hash(), "error", err)
|
log.Error("Failed NewPayload", "number", event.Block.NumberU64(), "hash", event.Block.Hash(), "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Debug("Calling ForkchoiceUpdated", "head", event.Block.Hash())
|
||||||
if status, err := ec.callForkchoiceUpdated(forkName, event); err == nil {
|
if status, err := ec.callForkchoiceUpdated(forkName, event); err == nil {
|
||||||
log.Info("Successful ForkchoiceUpdated", "head", event.Block.Hash(), "status", status)
|
log.Info("Successful ForkchoiceUpdated", "head", event.Block.Hash(), "status", status)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -46,13 +46,13 @@ func (s *ApiServer) Subscribe(eventCallback func(event request.Event)) {
|
||||||
log.Debug("New head received", "slot", slot, "blockRoot", blockRoot)
|
log.Debug("New head received", "slot", slot, "blockRoot", blockRoot)
|
||||||
eventCallback(request.Event{Type: sync.EvNewHead, Data: types.HeadInfo{Slot: slot, BlockRoot: blockRoot}})
|
eventCallback(request.Event{Type: sync.EvNewHead, Data: types.HeadInfo{Slot: slot, BlockRoot: blockRoot}})
|
||||||
},
|
},
|
||||||
OnSignedHead: func(head types.SignedHeader) {
|
OnOptimistic: func(update types.OptimisticUpdate) {
|
||||||
log.Debug("New signed head received", "slot", head.Header.Slot, "blockRoot", head.Header.Hash(), "signerCount", head.Signature.SignerCount())
|
log.Debug("New optimistic update received", "slot", update.Attested.Slot, "blockRoot", update.Attested.Hash(), "signerCount", update.Signature.SignerCount())
|
||||||
eventCallback(request.Event{Type: sync.EvNewSignedHead, Data: head})
|
eventCallback(request.Event{Type: sync.EvNewOptimisticUpdate, Data: update})
|
||||||
},
|
},
|
||||||
OnFinality: func(head types.FinalityUpdate) {
|
OnFinality: func(update types.FinalityUpdate) {
|
||||||
log.Debug("New finality update received", "slot", head.Attested.Slot, "blockRoot", head.Attested.Hash(), "signerCount", head.Signature.SignerCount())
|
log.Debug("New finality update received", "slot", update.Attested.Slot, "blockRoot", update.Attested.Hash(), "signerCount", update.Signature.SignerCount())
|
||||||
eventCallback(request.Event{Type: sync.EvNewFinalityUpdate, Data: head})
|
eventCallback(request.Event{Type: sync.EvNewFinalityUpdate, Data: update})
|
||||||
},
|
},
|
||||||
OnError: func(err error) {
|
OnError: func(err error) {
|
||||||
log.Warn("Head event stream error", "err", err)
|
log.Warn("Head event stream error", "err", err)
|
||||||
|
|
@ -83,6 +83,9 @@ func (s *ApiServer) SendRequest(id request.ID, req request.Request) {
|
||||||
case sync.ReqBeaconBlock:
|
case sync.ReqBeaconBlock:
|
||||||
log.Debug("Beacon API: requesting block", "reqid", id, "hash", common.Hash(data))
|
log.Debug("Beacon API: requesting block", "reqid", id, "hash", common.Hash(data))
|
||||||
resp, err = s.api.GetBeaconBlock(common.Hash(data))
|
resp, err = s.api.GetBeaconBlock(common.Hash(data))
|
||||||
|
case sync.ReqFinality:
|
||||||
|
log.Debug("Beacon API: requesting finality update")
|
||||||
|
resp, err = s.api.GetFinalityUpdate()
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,6 +93,7 @@ func (s *ApiServer) SendRequest(id request.ID, req request.Request) {
|
||||||
log.Warn("Beacon API request failed", "type", reflect.TypeOf(req), "reqid", id, "err", err)
|
log.Warn("Beacon API request failed", "type", reflect.TypeOf(req), "reqid", id, "err", err)
|
||||||
s.eventCallback(request.Event{Type: request.EvFail, Data: request.RequestResponse{ID: id, Request: req}})
|
s.eventCallback(request.Event{Type: request.EvFail, Data: request.RequestResponse{ID: id, Request: req}})
|
||||||
} else {
|
} else {
|
||||||
|
log.Debug("Beacon API request answered", "type", reflect.TypeOf(req), "reqid", id)
|
||||||
s.eventCallback(request.Event{Type: request.EvResponse, Data: request.RequestResponse{ID: id, Request: req, Response: resp}})
|
s.eventCallback(request.Event{Type: request.EvResponse, Data: request.RequestResponse{ID: id, Request: req, Response: resp}})
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/beacon/types"
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -184,46 +185,56 @@ func (api *BeaconLightApi) GetBestUpdatesAndCommittees(firstPeriod, count uint64
|
||||||
return updates, committees, nil
|
return updates, committees, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOptimisticHeadUpdate fetches a signed header based on the latest available
|
// GetOptimisticUpdate fetches the latest available optimistic update.
|
||||||
// optimistic update. Note that the signature should be verified by the caller
|
// Note that the signature should be verified by the caller as its validity
|
||||||
// as its validity depends on the update chain.
|
// depends on the update chain.
|
||||||
//
|
//
|
||||||
// See data structure definition here:
|
// See data structure definition here:
|
||||||
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientoptimisticupdate
|
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientoptimisticupdate
|
||||||
func (api *BeaconLightApi) GetOptimisticHeadUpdate() (types.SignedHeader, error) {
|
func (api *BeaconLightApi) GetOptimisticUpdate() (types.OptimisticUpdate, error) {
|
||||||
resp, err := api.httpGet("/eth/v1/beacon/light_client/optimistic_update")
|
resp, err := api.httpGet("/eth/v1/beacon/light_client/optimistic_update")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.SignedHeader{}, err
|
return types.OptimisticUpdate{}, err
|
||||||
}
|
}
|
||||||
return decodeOptimisticHeadUpdate(resp)
|
return decodeOptimisticUpdate(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeOptimisticHeadUpdate(enc []byte) (types.SignedHeader, error) {
|
func decodeOptimisticUpdate(enc []byte) (types.OptimisticUpdate, error) {
|
||||||
var data struct {
|
var data struct {
|
||||||
|
Version string
|
||||||
Data struct {
|
Data struct {
|
||||||
Header jsonBeaconHeader `json:"attested_header"`
|
Attested jsonHeaderWithExecProof `json:"attested_header"`
|
||||||
Aggregate types.SyncAggregate `json:"sync_aggregate"`
|
Aggregate types.SyncAggregate `json:"sync_aggregate"`
|
||||||
SignatureSlot common.Decimal `json:"signature_slot"`
|
SignatureSlot common.Decimal `json:"signature_slot"`
|
||||||
} `json:"data"`
|
} `json:"data"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(enc, &data); err != nil {
|
if err := json.Unmarshal(enc, &data); err != nil {
|
||||||
return types.SignedHeader{}, err
|
return types.OptimisticUpdate{}, err
|
||||||
}
|
}
|
||||||
if data.Data.Header.Beacon.StateRoot == (common.Hash{}) {
|
// Decode the execution payload headers.
|
||||||
|
attestedExecHeader, err := types.ExecutionHeaderFromJSON(data.Version, data.Data.Attested.Execution)
|
||||||
|
if err != nil {
|
||||||
|
return types.OptimisticUpdate{}, fmt.Errorf("invalid attested header: %v", err)
|
||||||
|
}
|
||||||
|
if data.Data.Attested.Beacon.StateRoot == (common.Hash{}) {
|
||||||
// workaround for different event encoding format in Lodestar
|
// workaround for different event encoding format in Lodestar
|
||||||
if err := json.Unmarshal(enc, &data.Data); err != nil {
|
if err := json.Unmarshal(enc, &data.Data); err != nil {
|
||||||
return types.SignedHeader{}, err
|
return types.OptimisticUpdate{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(data.Data.Aggregate.Signers) != params.SyncCommitteeBitmaskSize {
|
if len(data.Data.Aggregate.Signers) != params.SyncCommitteeBitmaskSize {
|
||||||
return types.SignedHeader{}, errors.New("invalid sync_committee_bits length")
|
return types.OptimisticUpdate{}, errors.New("invalid sync_committee_bits length")
|
||||||
}
|
}
|
||||||
if len(data.Data.Aggregate.Signature) != params.BLSSignatureSize {
|
if len(data.Data.Aggregate.Signature) != params.BLSSignatureSize {
|
||||||
return types.SignedHeader{}, errors.New("invalid sync_committee_signature length")
|
return types.OptimisticUpdate{}, errors.New("invalid sync_committee_signature length")
|
||||||
}
|
}
|
||||||
return types.SignedHeader{
|
return types.OptimisticUpdate{
|
||||||
Header: data.Data.Header.Beacon,
|
Attested: types.HeaderWithExecProof{
|
||||||
|
Header: data.Data.Attested.Beacon,
|
||||||
|
PayloadHeader: attestedExecHeader,
|
||||||
|
PayloadBranch: data.Data.Attested.ExecutionBranch,
|
||||||
|
},
|
||||||
Signature: data.Data.Aggregate,
|
Signature: data.Data.Aggregate,
|
||||||
SignatureSlot: uint64(data.Data.SignatureSlot),
|
SignatureSlot: uint64(data.Data.SignatureSlot),
|
||||||
}, nil
|
}, nil
|
||||||
|
|
@ -411,7 +422,7 @@ func decodeHeadEvent(enc []byte) (uint64, common.Hash, error) {
|
||||||
|
|
||||||
type HeadEventListener struct {
|
type HeadEventListener struct {
|
||||||
OnNewHead func(slot uint64, blockRoot common.Hash)
|
OnNewHead func(slot uint64, blockRoot common.Hash)
|
||||||
OnSignedHead func(head types.SignedHeader)
|
OnOptimistic func(head types.OptimisticUpdate)
|
||||||
OnFinality func(head types.FinalityUpdate)
|
OnFinality func(head types.FinalityUpdate)
|
||||||
OnError func(err error)
|
OnError func(err error)
|
||||||
}
|
}
|
||||||
|
|
@ -449,21 +460,35 @@ func (api *BeaconLightApi) StartHeadListener(listener HeadEventListener) func()
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
// Request initial data.
|
// Request initial data.
|
||||||
|
log.Trace("Requesting initial head header")
|
||||||
if head, _, _, err := api.GetHeader(common.Hash{}); err == nil {
|
if head, _, _, err := api.GetHeader(common.Hash{}); err == nil {
|
||||||
|
log.Trace("Retrieved initial head header", "slot", head.Slot, "hash", head.Hash())
|
||||||
listener.OnNewHead(head.Slot, head.Hash())
|
listener.OnNewHead(head.Slot, head.Hash())
|
||||||
|
} else {
|
||||||
|
log.Debug("Failed to retrieve initial head header", "error", err)
|
||||||
}
|
}
|
||||||
if signedHead, err := api.GetOptimisticHeadUpdate(); err == nil {
|
log.Trace("Requesting initial optimistic update")
|
||||||
listener.OnSignedHead(signedHead)
|
if optimisticUpdate, err := api.GetOptimisticUpdate(); err == nil {
|
||||||
|
log.Trace("Retrieved initial optimistic update", "slot", optimisticUpdate.Attested.Slot, "hash", optimisticUpdate.Attested.Hash())
|
||||||
|
listener.OnOptimistic(optimisticUpdate)
|
||||||
|
} else {
|
||||||
|
log.Debug("Failed to retrieve initial optimistic update", "error", err)
|
||||||
}
|
}
|
||||||
|
log.Trace("Requesting initial finality update")
|
||||||
if finalityUpdate, err := api.GetFinalityUpdate(); err == nil {
|
if finalityUpdate, err := api.GetFinalityUpdate(); err == nil {
|
||||||
|
log.Trace("Retrieved initial finality update", "slot", finalityUpdate.Finalized.Slot, "hash", finalityUpdate.Finalized.Hash())
|
||||||
listener.OnFinality(finalityUpdate)
|
listener.OnFinality(finalityUpdate)
|
||||||
|
} else {
|
||||||
|
log.Debug("Failed to retrieve initial finality update", "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Trace("Starting event stream processing loop")
|
||||||
// Receive the stream.
|
// Receive the stream.
|
||||||
var stream *eventsource.Stream
|
var stream *eventsource.Stream
|
||||||
select {
|
select {
|
||||||
case stream = <-streamCh:
|
case stream = <-streamCh:
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
log.Trace("Stopping event stream processing loop")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -474,8 +499,10 @@ func (api *BeaconLightApi) StartHeadListener(listener HeadEventListener) func()
|
||||||
|
|
||||||
case event, ok := <-stream.Events:
|
case event, ok := <-stream.Events:
|
||||||
if !ok {
|
if !ok {
|
||||||
|
log.Trace("Event stream closed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
log.Trace("New event received from event stream", "type", event.Event())
|
||||||
switch event.Event() {
|
switch event.Event() {
|
||||||
case "head":
|
case "head":
|
||||||
slot, blockRoot, err := decodeHeadEvent([]byte(event.Data()))
|
slot, blockRoot, err := decodeHeadEvent([]byte(event.Data()))
|
||||||
|
|
@ -485,9 +512,9 @@ func (api *BeaconLightApi) StartHeadListener(listener HeadEventListener) func()
|
||||||
listener.OnError(fmt.Errorf("error decoding head event: %v", err))
|
listener.OnError(fmt.Errorf("error decoding head event: %v", err))
|
||||||
}
|
}
|
||||||
case "light_client_optimistic_update":
|
case "light_client_optimistic_update":
|
||||||
signedHead, err := decodeOptimisticHeadUpdate([]byte(event.Data()))
|
optimisticUpdate, err := decodeOptimisticUpdate([]byte(event.Data()))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
listener.OnSignedHead(signedHead)
|
listener.OnOptimistic(optimisticUpdate)
|
||||||
} else {
|
} else {
|
||||||
listener.OnError(fmt.Errorf("error decoding optimistic update event: %v", err))
|
listener.OnError(fmt.Errorf("error decoding optimistic update event: %v", err))
|
||||||
}
|
}
|
||||||
|
|
@ -521,7 +548,8 @@ func (api *BeaconLightApi) StartHeadListener(listener HeadEventListener) func()
|
||||||
// established. It can only return nil when the context is canceled.
|
// established. It can only return nil when the context is canceled.
|
||||||
func (api *BeaconLightApi) startEventStream(ctx context.Context, listener *HeadEventListener) *eventsource.Stream {
|
func (api *BeaconLightApi) startEventStream(ctx context.Context, listener *HeadEventListener) *eventsource.Stream {
|
||||||
for retry := true; retry; retry = ctxSleep(ctx, 5*time.Second) {
|
for retry := true; retry; retry = ctxSleep(ctx, 5*time.Second) {
|
||||||
path := "/eth/v1/events?topics=head&topics=light_client_optimistic_update&topics=light_client_finality_update"
|
path := "/eth/v1/events?topics=head&topics=light_client_finality_update&topics=light_client_optimistic_update"
|
||||||
|
log.Trace("Sending event subscription request")
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", api.url+path, nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", api.url+path, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
listener.OnError(fmt.Errorf("error creating event subscription request: %v", err))
|
listener.OnError(fmt.Errorf("error creating event subscription request: %v", err))
|
||||||
|
|
@ -535,6 +563,7 @@ func (api *BeaconLightApi) startEventStream(ctx context.Context, listener *HeadE
|
||||||
listener.OnError(fmt.Errorf("error creating event subscription: %v", err))
|
listener.OnError(fmt.Errorf("error creating event subscription: %v", err))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
log.Trace("Successfully created event stream")
|
||||||
return stream
|
return stream
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,8 @@ type HeadTracker struct {
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
committeeChain *CommitteeChain
|
committeeChain *CommitteeChain
|
||||||
minSignerCount int
|
minSignerCount int
|
||||||
signedHead types.SignedHeader
|
optimisticUpdate types.OptimisticUpdate
|
||||||
hasSignedHead bool
|
hasOptimisticUpdate bool
|
||||||
finalityUpdate types.FinalityUpdate
|
finalityUpdate types.FinalityUpdate
|
||||||
hasFinalityUpdate bool
|
hasFinalityUpdate bool
|
||||||
prefetchHead types.HeadInfo
|
prefetchHead types.HeadInfo
|
||||||
|
|
@ -48,15 +48,15 @@ func NewHeadTracker(committeeChain *CommitteeChain, minSignerCount int) *HeadTra
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidatedHead returns the latest validated head.
|
// ValidatedOptimistic returns the latest validated optimistic update.
|
||||||
func (h *HeadTracker) ValidatedHead() (types.SignedHeader, bool) {
|
func (h *HeadTracker) ValidatedOptimistic() (types.OptimisticUpdate, bool) {
|
||||||
h.lock.RLock()
|
h.lock.RLock()
|
||||||
defer h.lock.RUnlock()
|
defer h.lock.RUnlock()
|
||||||
|
|
||||||
return h.signedHead, h.hasSignedHead
|
return h.optimisticUpdate, h.hasOptimisticUpdate
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidatedFinality returns the latest validated finality.
|
// ValidatedFinality returns the latest validated finality update.
|
||||||
func (h *HeadTracker) ValidatedFinality() (types.FinalityUpdate, bool) {
|
func (h *HeadTracker) ValidatedFinality() (types.FinalityUpdate, bool) {
|
||||||
h.lock.RLock()
|
h.lock.RLock()
|
||||||
defer h.lock.RUnlock()
|
defer h.lock.RUnlock()
|
||||||
|
|
@ -64,26 +64,36 @@ func (h *HeadTracker) ValidatedFinality() (types.FinalityUpdate, bool) {
|
||||||
return h.finalityUpdate, h.hasFinalityUpdate
|
return h.finalityUpdate, h.hasFinalityUpdate
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateHead validates the given signed head. If the head is successfully validated
|
// ValidateOptimistic validates the given optimistic update. If the update is
|
||||||
// and it is better than the old validated head (higher slot or same slot and more
|
// successfully validated and it is better than the old validated update (higher
|
||||||
// signers) then ValidatedHead is updated. The boolean return flag signals if
|
// slot or same slot and more signers) then ValidatedOptimistic is updated.
|
||||||
// ValidatedHead has been changed.
|
// The boolean return flag signals if ValidatedOptimistic has been changed.
|
||||||
func (h *HeadTracker) ValidateHead(head types.SignedHeader) (bool, error) {
|
func (h *HeadTracker) ValidateOptimistic(update types.OptimisticUpdate) (bool, error) {
|
||||||
h.lock.Lock()
|
h.lock.Lock()
|
||||||
defer h.lock.Unlock()
|
defer h.lock.Unlock()
|
||||||
|
|
||||||
replace, err := h.validate(head, h.signedHead)
|
if err := update.Validate(); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
replace, err := h.validate(update.SignedHeader(), h.optimisticUpdate.SignedHeader())
|
||||||
if replace {
|
if replace {
|
||||||
h.signedHead, h.hasSignedHead = head, true
|
h.optimisticUpdate, h.hasOptimisticUpdate = update, true
|
||||||
h.changeCounter++
|
h.changeCounter++
|
||||||
}
|
}
|
||||||
return replace, err
|
return replace, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidateFinality validates the given finality update. If the update is
|
||||||
|
// successfully validated and it is better than the old validated update (higher
|
||||||
|
// slot or same slot and more signers) then ValidatedFinality is updated.
|
||||||
|
// The boolean return flag signals if ValidatedFinality has been changed.
|
||||||
func (h *HeadTracker) ValidateFinality(update types.FinalityUpdate) (bool, error) {
|
func (h *HeadTracker) ValidateFinality(update types.FinalityUpdate) (bool, error) {
|
||||||
h.lock.Lock()
|
h.lock.Lock()
|
||||||
defer h.lock.Unlock()
|
defer h.lock.Unlock()
|
||||||
|
|
||||||
|
if err := update.Validate(); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
replace, err := h.validate(update.SignedHeader(), h.finalityUpdate.SignedHeader())
|
replace, err := h.validate(update.SignedHeader(), h.finalityUpdate.SignedHeader())
|
||||||
if replace {
|
if replace {
|
||||||
h.finalityUpdate, h.hasFinalityUpdate = update, true
|
h.finalityUpdate, h.hasFinalityUpdate = update, true
|
||||||
|
|
@ -142,6 +152,7 @@ func (h *HeadTracker) SetPrefetchHead(head types.HeadInfo) {
|
||||||
h.changeCounter++
|
h.changeCounter++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ChangeCounter implements request.targetData
|
||||||
func (h *HeadTracker) ChangeCounter() uint64 {
|
func (h *HeadTracker) ChangeCounter() uint64 {
|
||||||
h.lock.RLock()
|
h.lock.RLock()
|
||||||
defer h.lock.RUnlock()
|
defer h.lock.RUnlock()
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,13 @@ package sync
|
||||||
import (
|
import (
|
||||||
"github.com/ethereum/go-ethereum/beacon/light/request"
|
"github.com/ethereum/go-ethereum/beacon/light/request"
|
||||||
"github.com/ethereum/go-ethereum/beacon/types"
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type headTracker interface {
|
type headTracker interface {
|
||||||
ValidateHead(head types.SignedHeader) (bool, error)
|
ValidateOptimistic(update types.OptimisticUpdate) (bool, error)
|
||||||
ValidateFinality(head types.FinalityUpdate) (bool, error)
|
ValidateFinality(head types.FinalityUpdate) (bool, error)
|
||||||
|
ValidatedFinality() (types.FinalityUpdate, bool)
|
||||||
SetPrefetchHead(head types.HeadInfo)
|
SetPrefetchHead(head types.HeadInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,9 +39,10 @@ type HeadSync struct {
|
||||||
chain committeeChain
|
chain committeeChain
|
||||||
nextSyncPeriod uint64
|
nextSyncPeriod uint64
|
||||||
chainInit bool
|
chainInit bool
|
||||||
unvalidatedHeads map[request.Server]types.SignedHeader
|
unvalidatedOptimistic map[request.Server]types.OptimisticUpdate
|
||||||
unvalidatedFinality map[request.Server]types.FinalityUpdate
|
unvalidatedFinality map[request.Server]types.FinalityUpdate
|
||||||
serverHeads map[request.Server]types.HeadInfo
|
serverHeads map[request.Server]types.HeadInfo
|
||||||
|
reqFinalityEpoch map[request.Server]uint64 // next epoch to request finality update
|
||||||
headServerCount map[types.HeadInfo]headServerCount
|
headServerCount map[types.HeadInfo]headServerCount
|
||||||
headCounter uint64
|
headCounter uint64
|
||||||
prefetchHead types.HeadInfo
|
prefetchHead types.HeadInfo
|
||||||
|
|
@ -59,73 +62,96 @@ func NewHeadSync(headTracker headTracker, chain committeeChain) *HeadSync {
|
||||||
s := &HeadSync{
|
s := &HeadSync{
|
||||||
headTracker: headTracker,
|
headTracker: headTracker,
|
||||||
chain: chain,
|
chain: chain,
|
||||||
unvalidatedHeads: make(map[request.Server]types.SignedHeader),
|
unvalidatedOptimistic: make(map[request.Server]types.OptimisticUpdate),
|
||||||
unvalidatedFinality: make(map[request.Server]types.FinalityUpdate),
|
unvalidatedFinality: make(map[request.Server]types.FinalityUpdate),
|
||||||
serverHeads: make(map[request.Server]types.HeadInfo),
|
serverHeads: make(map[request.Server]types.HeadInfo),
|
||||||
headServerCount: make(map[types.HeadInfo]headServerCount),
|
headServerCount: make(map[types.HeadInfo]headServerCount),
|
||||||
|
reqFinalityEpoch: make(map[request.Server]uint64),
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process implements request.Module.
|
// Process implements request.Module.
|
||||||
func (s *HeadSync) Process(requester request.Requester, events []request.Event) {
|
func (s *HeadSync) Process(requester request.Requester, events []request.Event) {
|
||||||
|
nextPeriod, chainInit := s.chain.NextSyncPeriod()
|
||||||
|
if nextPeriod != s.nextSyncPeriod || chainInit != s.chainInit {
|
||||||
|
s.nextSyncPeriod, s.chainInit = nextPeriod, chainInit
|
||||||
|
s.processUnvalidatedUpdates()
|
||||||
|
}
|
||||||
|
|
||||||
for _, event := range events {
|
for _, event := range events {
|
||||||
switch event.Type {
|
switch event.Type {
|
||||||
case EvNewHead:
|
case EvNewHead:
|
||||||
s.setServerHead(event.Server, event.Data.(types.HeadInfo))
|
s.setServerHead(event.Server, event.Data.(types.HeadInfo))
|
||||||
case EvNewSignedHead:
|
case EvNewOptimisticUpdate:
|
||||||
s.newSignedHead(event.Server, event.Data.(types.SignedHeader))
|
update := event.Data.(types.OptimisticUpdate)
|
||||||
|
s.newOptimisticUpdate(event.Server, update)
|
||||||
|
epoch := update.Attested.Epoch()
|
||||||
|
if epoch < s.reqFinalityEpoch[event.Server] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if finality, ok := s.headTracker.ValidatedFinality(); ok && finality.Attested.Header.Epoch() >= epoch {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
requester.Send(event.Server, ReqFinality{})
|
||||||
|
s.reqFinalityEpoch[event.Server] = epoch + 1
|
||||||
case EvNewFinalityUpdate:
|
case EvNewFinalityUpdate:
|
||||||
s.newFinalityUpdate(event.Server, event.Data.(types.FinalityUpdate))
|
s.newFinalityUpdate(event.Server, event.Data.(types.FinalityUpdate))
|
||||||
|
case request.EvResponse:
|
||||||
|
_, _, resp := event.RequestInfo()
|
||||||
|
s.newFinalityUpdate(event.Server, resp.(types.FinalityUpdate))
|
||||||
case request.EvUnregistered:
|
case request.EvUnregistered:
|
||||||
s.setServerHead(event.Server, types.HeadInfo{})
|
s.setServerHead(event.Server, types.HeadInfo{})
|
||||||
delete(s.serverHeads, event.Server)
|
delete(s.serverHeads, event.Server)
|
||||||
delete(s.unvalidatedHeads, event.Server)
|
delete(s.unvalidatedOptimistic, event.Server)
|
||||||
|
delete(s.unvalidatedFinality, event.Server)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nextPeriod, chainInit := s.chain.NextSyncPeriod()
|
// newOptimisticUpdate handles received optimistic update; either validates it if
|
||||||
if nextPeriod != s.nextSyncPeriod || chainInit != s.chainInit {
|
// the chain is properly synced or stores it for further validation.
|
||||||
s.nextSyncPeriod, s.chainInit = nextPeriod, chainInit
|
func (s *HeadSync) newOptimisticUpdate(server request.Server, optimisticUpdate types.OptimisticUpdate) {
|
||||||
s.processUnvalidated()
|
if !s.chainInit || types.SyncPeriod(optimisticUpdate.SignatureSlot) > s.nextSyncPeriod {
|
||||||
}
|
s.unvalidatedOptimistic[server] = optimisticUpdate
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.headTracker.ValidateHead(signedHead)
|
if _, err := s.headTracker.ValidateOptimistic(optimisticUpdate); err != nil {
|
||||||
|
log.Debug("Error validating optimistic update", "error", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// newFinalityUpdate handles received finality update; either validates it if the chain
|
// newFinalityUpdate handles received finality update; either validates it if
|
||||||
// is properly synced or stores it for further validation.
|
// the chain is properly synced or stores it for further validation.
|
||||||
func (s *HeadSync) newFinalityUpdate(server request.Server, finalityUpdate types.FinalityUpdate) {
|
func (s *HeadSync) newFinalityUpdate(server request.Server, finalityUpdate types.FinalityUpdate) {
|
||||||
if !s.chainInit || types.SyncPeriod(finalityUpdate.SignatureSlot) > s.nextSyncPeriod {
|
if !s.chainInit || types.SyncPeriod(finalityUpdate.SignatureSlot) > s.nextSyncPeriod {
|
||||||
s.unvalidatedFinality[server] = finalityUpdate
|
s.unvalidatedFinality[server] = finalityUpdate
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.headTracker.ValidateFinality(finalityUpdate)
|
if _, err := s.headTracker.ValidateFinality(finalityUpdate); err != nil {
|
||||||
|
log.Debug("Error validating finality update", "error", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// processUnvalidated iterates the list of unvalidated heads and validates
|
// processUnvalidatedUpdates iterates the list of unvalidated updates and validates
|
||||||
// those which can be validated.
|
// those which can be validated.
|
||||||
func (s *HeadSync) processUnvalidated() {
|
func (s *HeadSync) processUnvalidatedUpdates() {
|
||||||
if !s.chainInit {
|
if !s.chainInit {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for server, signedHead := range s.unvalidatedHeads {
|
for server, optimisticUpdate := range s.unvalidatedOptimistic {
|
||||||
if types.SyncPeriod(signedHead.SignatureSlot) <= s.nextSyncPeriod {
|
if types.SyncPeriod(optimisticUpdate.SignatureSlot) <= s.nextSyncPeriod {
|
||||||
s.headTracker.ValidateHead(signedHead)
|
if _, err := s.headTracker.ValidateOptimistic(optimisticUpdate); err != nil {
|
||||||
delete(s.unvalidatedHeads, server)
|
log.Debug("Error validating deferred optimistic update", "error", err)
|
||||||
|
}
|
||||||
|
delete(s.unvalidatedOptimistic, server)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for server, finalityUpdate := range s.unvalidatedFinality {
|
for server, finalityUpdate := range s.unvalidatedFinality {
|
||||||
if types.SyncPeriod(finalityUpdate.SignatureSlot) <= s.nextSyncPeriod {
|
if types.SyncPeriod(finalityUpdate.SignatureSlot) <= s.nextSyncPeriod {
|
||||||
s.headTracker.ValidateFinality(finalityUpdate)
|
if _, err := s.headTracker.ValidateFinality(finalityUpdate); err != nil {
|
||||||
|
log.Debug("Error validating deferred finality update", "error", err)
|
||||||
|
}
|
||||||
delete(s.unvalidatedFinality, server)
|
delete(s.unvalidatedFinality, server)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package sync
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light/request"
|
||||||
"github.com/ethereum/go-ethereum/beacon/types"
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
)
|
)
|
||||||
|
|
@ -28,6 +29,7 @@ var (
|
||||||
testServer2 = testServer("testServer2")
|
testServer2 = testServer("testServer2")
|
||||||
testServer3 = testServer("testServer3")
|
testServer3 = testServer("testServer3")
|
||||||
testServer4 = testServer("testServer4")
|
testServer4 = testServer("testServer4")
|
||||||
|
testServer5 = testServer("testServer5")
|
||||||
|
|
||||||
testHead0 = types.HeadInfo{}
|
testHead0 = types.HeadInfo{}
|
||||||
testHead1 = types.HeadInfo{Slot: 123, BlockRoot: common.Hash{1}}
|
testHead1 = types.HeadInfo{Slot: 123, BlockRoot: common.Hash{1}}
|
||||||
|
|
@ -35,13 +37,21 @@ var (
|
||||||
testHead3 = types.HeadInfo{Slot: 124, BlockRoot: common.Hash{3}}
|
testHead3 = types.HeadInfo{Slot: 124, BlockRoot: common.Hash{3}}
|
||||||
testHead4 = types.HeadInfo{Slot: 125, BlockRoot: common.Hash{4}}
|
testHead4 = types.HeadInfo{Slot: 125, BlockRoot: common.Hash{4}}
|
||||||
|
|
||||||
testSHead1 = types.SignedHeader{SignatureSlot: 0x0124, Header: types.Header{Slot: 0x0123, StateRoot: common.Hash{1}}}
|
testOptUpdate1 = types.OptimisticUpdate{SignatureSlot: 0x0124, Attested: types.HeaderWithExecProof{Header: types.Header{Slot: 0x0123, StateRoot: common.Hash{1}}}}
|
||||||
testSHead2 = types.SignedHeader{SignatureSlot: 0x2010, Header: types.Header{Slot: 0x200e, StateRoot: common.Hash{2}}}
|
testOptUpdate2 = types.OptimisticUpdate{SignatureSlot: 0x2010, Attested: types.HeaderWithExecProof{Header: types.Header{Slot: 0x200e, StateRoot: common.Hash{2}}}}
|
||||||
// testSHead3 is at the end of period 1 but signed in period 2
|
// testOptUpdate3 is at the end of period 1 but signed in period 2
|
||||||
testSHead3 = types.SignedHeader{SignatureSlot: 0x4000, Header: types.Header{Slot: 0x3fff, StateRoot: common.Hash{3}}}
|
testOptUpdate3 = types.OptimisticUpdate{SignatureSlot: 0x4000, Attested: types.HeaderWithExecProof{Header: types.Header{Slot: 0x3fff, StateRoot: common.Hash{3}}}}
|
||||||
testSHead4 = types.SignedHeader{SignatureSlot: 0x6444, Header: types.Header{Slot: 0x6443, StateRoot: common.Hash{4}}}
|
testOptUpdate4 = types.OptimisticUpdate{SignatureSlot: 0x6444, Attested: types.HeaderWithExecProof{Header: types.Header{Slot: 0x6443, StateRoot: common.Hash{4}}}}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func finality(opt types.OptimisticUpdate) types.FinalityUpdate {
|
||||||
|
return types.FinalityUpdate{
|
||||||
|
SignatureSlot: opt.SignatureSlot,
|
||||||
|
Attested: opt.Attested,
|
||||||
|
Finalized: types.HeaderWithExecProof{Header: types.Header{Slot: (opt.Attested.Header.Slot - 64) & uint64(0xffffffffffffffe0)}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type testServer string
|
type testServer string
|
||||||
|
|
||||||
func (t testServer) Name() string {
|
func (t testServer) Name() string {
|
||||||
|
|
@ -57,50 +67,66 @@ func TestValidatedHead(t *testing.T) {
|
||||||
ht.ExpValidated(t, 0, nil)
|
ht.ExpValidated(t, 0, nil)
|
||||||
|
|
||||||
ts.AddServer(testServer1, 1)
|
ts.AddServer(testServer1, 1)
|
||||||
ts.ServerEvent(EvNewSignedHead, testServer1, testSHead1)
|
ts.ServerEvent(EvNewOptimisticUpdate, testServer1, testOptUpdate1)
|
||||||
ts.Run(1)
|
ts.Run(1, testServer1, ReqFinality{})
|
||||||
// announced head should be queued because of uninitialized chain
|
// announced head should be queued because of uninitialized chain
|
||||||
ht.ExpValidated(t, 1, nil)
|
ht.ExpValidated(t, 1, nil)
|
||||||
|
|
||||||
chain.SetNextSyncPeriod(0) // initialize chain
|
chain.SetNextSyncPeriod(0) // initialize chain
|
||||||
ts.Run(2)
|
ts.Run(2)
|
||||||
// expect previously queued head to be validated
|
// expect previously queued head to be validated
|
||||||
ht.ExpValidated(t, 2, []types.SignedHeader{testSHead1})
|
ht.ExpValidated(t, 2, []types.OptimisticUpdate{testOptUpdate1})
|
||||||
|
|
||||||
chain.SetNextSyncPeriod(1)
|
chain.SetNextSyncPeriod(1)
|
||||||
ts.ServerEvent(EvNewSignedHead, testServer1, testSHead2)
|
ts.ServerEvent(EvNewFinalityUpdate, testServer1, finality(testOptUpdate2))
|
||||||
|
ts.ServerEvent(EvNewOptimisticUpdate, testServer1, testOptUpdate2)
|
||||||
ts.AddServer(testServer2, 1)
|
ts.AddServer(testServer2, 1)
|
||||||
ts.ServerEvent(EvNewSignedHead, testServer2, testSHead2)
|
ts.ServerEvent(EvNewOptimisticUpdate, testServer2, testOptUpdate2)
|
||||||
ts.Run(3)
|
ts.Run(3)
|
||||||
// expect both head announcements to be validated instantly
|
// expect both head announcements to be validated instantly
|
||||||
ht.ExpValidated(t, 3, []types.SignedHeader{testSHead2, testSHead2})
|
ht.ExpValidated(t, 3, []types.OptimisticUpdate{testOptUpdate2, testOptUpdate2})
|
||||||
|
|
||||||
ts.ServerEvent(EvNewSignedHead, testServer1, testSHead3)
|
ts.ServerEvent(EvNewOptimisticUpdate, testServer1, testOptUpdate3)
|
||||||
ts.AddServer(testServer3, 1)
|
ts.AddServer(testServer3, 1)
|
||||||
ts.ServerEvent(EvNewSignedHead, testServer3, testSHead4)
|
ts.ServerEvent(EvNewOptimisticUpdate, testServer3, testOptUpdate4)
|
||||||
ts.Run(4)
|
// finality should be requested from both servers
|
||||||
// future period announced heads should be queued
|
ts.Run(4, testServer1, ReqFinality{}, testServer3, ReqFinality{})
|
||||||
|
// future period annonced heads should be queued
|
||||||
ht.ExpValidated(t, 4, nil)
|
ht.ExpValidated(t, 4, nil)
|
||||||
|
|
||||||
chain.SetNextSyncPeriod(2)
|
chain.SetNextSyncPeriod(2)
|
||||||
ts.Run(5)
|
ts.Run(5)
|
||||||
// testSHead3 can be validated now but not testSHead4
|
// testOptUpdate3 can be validated now but not testOptUpdate4
|
||||||
ht.ExpValidated(t, 5, []types.SignedHeader{testSHead3})
|
ht.ExpValidated(t, 5, []types.OptimisticUpdate{testOptUpdate3})
|
||||||
|
|
||||||
|
ts.AddServer(testServer4, 1)
|
||||||
|
ts.ServerEvent(EvNewOptimisticUpdate, testServer4, testOptUpdate3)
|
||||||
|
// new server joined with recent optimistic update but still no finality; should be requested
|
||||||
|
ts.Run(6, testServer4, ReqFinality{})
|
||||||
|
ht.ExpValidated(t, 6, []types.OptimisticUpdate{testOptUpdate3})
|
||||||
|
|
||||||
|
ts.AddServer(testServer5, 1)
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(6, 1), finality(testOptUpdate3))
|
||||||
|
ts.ServerEvent(EvNewOptimisticUpdate, testServer5, testOptUpdate3)
|
||||||
|
// finality update request answered; new server should not be requested
|
||||||
|
ts.Run(7)
|
||||||
|
ht.ExpValidated(t, 7, []types.OptimisticUpdate{testOptUpdate3})
|
||||||
|
|
||||||
// server 3 disconnected without proving period 3, its announced head should be dropped
|
// server 3 disconnected without proving period 3, its announced head should be dropped
|
||||||
ts.RemoveServer(testServer3)
|
ts.RemoveServer(testServer3)
|
||||||
ts.Run(6)
|
ts.Run(8)
|
||||||
ht.ExpValidated(t, 6, nil)
|
ht.ExpValidated(t, 8, nil)
|
||||||
|
|
||||||
chain.SetNextSyncPeriod(3)
|
chain.SetNextSyncPeriod(3)
|
||||||
ts.Run(7)
|
ts.Run(9)
|
||||||
// testSHead4 could be validated now but it's not queued by any registered server
|
// testOptUpdate4 could be validated now but it's not queued by any registered server
|
||||||
ht.ExpValidated(t, 7, nil)
|
ht.ExpValidated(t, 9, nil)
|
||||||
|
|
||||||
ts.ServerEvent(EvNewSignedHead, testServer2, testSHead4)
|
ts.ServerEvent(EvNewFinalityUpdate, testServer2, finality(testOptUpdate4))
|
||||||
ts.Run(8)
|
ts.ServerEvent(EvNewOptimisticUpdate, testServer2, testOptUpdate4)
|
||||||
// now testSHead4 should be validated
|
ts.Run(10)
|
||||||
ht.ExpValidated(t, 8, []types.SignedHeader{testSHead4})
|
// now testOptUpdate4 should be validated
|
||||||
|
ht.ExpValidated(t, 10, []types.OptimisticUpdate{testOptUpdate4})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPrefetchHead(t *testing.T) {
|
func TestPrefetchHead(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -212,32 +212,37 @@ func (tc *TestCommitteeChain) ExpNextSyncPeriod(t *testing.T, expNsp uint64) {
|
||||||
|
|
||||||
type TestHeadTracker struct {
|
type TestHeadTracker struct {
|
||||||
phead types.HeadInfo
|
phead types.HeadInfo
|
||||||
validated []types.SignedHeader
|
validated []types.OptimisticUpdate
|
||||||
|
finality types.FinalityUpdate
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ht *TestHeadTracker) ValidateHead(head types.SignedHeader) (bool, error) {
|
func (ht *TestHeadTracker) ValidateOptimistic(update types.OptimisticUpdate) (bool, error) {
|
||||||
ht.validated = append(ht.validated, head)
|
ht.validated = append(ht.validated, update)
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO add test case for finality
|
func (ht *TestHeadTracker) ValidateFinality(update types.FinalityUpdate) (bool, error) {
|
||||||
func (ht *TestHeadTracker) ValidateFinality(head types.FinalityUpdate) (bool, error) {
|
ht.finality = update
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ht *TestHeadTracker) ExpValidated(t *testing.T, tci int, expHeads []types.SignedHeader) {
|
func (ht *TestHeadTracker) ValidatedFinality() (types.FinalityUpdate, bool) {
|
||||||
|
return ht.finality, ht.finality.Attested.Header != (types.Header{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ht *TestHeadTracker) ExpValidated(t *testing.T, tci int, expHeads []types.OptimisticUpdate) {
|
||||||
for i, expHead := range expHeads {
|
for i, expHead := range expHeads {
|
||||||
if i >= len(ht.validated) {
|
if i >= len(ht.validated) {
|
||||||
t.Errorf("Missing validated head in test case #%d index #%d (expected {slot %d blockRoot %x}, got none)", tci, i, expHead.Header.Slot, expHead.Header.Hash())
|
t.Errorf("Missing validated head in test case #%d index #%d (expected {slot %d blockRoot %x}, got none)", tci, i, expHead.Attested.Header.Slot, expHead.Attested.Header.Hash())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if ht.validated[i] != expHead {
|
if !reflect.DeepEqual(ht.validated[i], expHead) {
|
||||||
vhead := ht.validated[i].Header
|
vhead := ht.validated[i].Attested.Header
|
||||||
t.Errorf("Wrong validated head in test case #%d index #%d (expected {slot %d blockRoot %x}, got {slot %d blockRoot %x})", tci, i, expHead.Header.Slot, expHead.Header.Hash(), vhead.Slot, vhead.Hash())
|
t.Errorf("Wrong validated head in test case #%d index #%d (expected {slot %d blockRoot %x}, got {slot %d blockRoot %x})", tci, i, expHead.Attested.Header.Slot, expHead.Attested.Header.Hash(), vhead.Slot, vhead.Hash())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i := len(expHeads); i < len(ht.validated); i++ {
|
for i := len(expHeads); i < len(ht.validated); i++ {
|
||||||
vhead := ht.validated[i].Header
|
vhead := ht.validated[i].Attested.Header
|
||||||
t.Errorf("Unexpected validated head in test case #%d index #%d (expected none, got {slot %d blockRoot %x})", tci, i, vhead.Slot, vhead.Hash())
|
t.Errorf("Unexpected validated head in test case #%d index #%d (expected none, got {slot %d blockRoot %x})", tci, i, vhead.Slot, vhead.Hash())
|
||||||
}
|
}
|
||||||
ht.validated = nil
|
ht.validated = nil
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
EvNewHead = &request.EventType{Name: "newHead"} // data: types.HeadInfo
|
EvNewHead = &request.EventType{Name: "newHead"} // data: types.HeadInfo
|
||||||
EvNewSignedHead = &request.EventType{Name: "newSignedHead"} // data: types.SignedHeader
|
EvNewOptimisticUpdate = &request.EventType{Name: "newOptimisticUpdate"} // data: types.OptimisticUpdate
|
||||||
EvNewFinalityUpdate = &request.EventType{Name: "newFinalityUpdate"} // data: types.FinalityUpdate
|
EvNewFinalityUpdate = &request.EventType{Name: "newFinalityUpdate"} // data: types.FinalityUpdate
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -43,4 +43,5 @@ type (
|
||||||
}
|
}
|
||||||
ReqCheckpointData common.Hash
|
ReqCheckpointData common.Hash
|
||||||
ReqBeaconBlock common.Hash
|
ReqBeaconBlock common.Hash
|
||||||
|
ReqFinality struct{}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,7 @@ func (s *CheckpointInit) Process(requester request.Requester, events []request.E
|
||||||
if s.initialized {
|
if s.initialized {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, event := range events {
|
for _, event := range events {
|
||||||
switch event.Type {
|
switch event.Type {
|
||||||
case request.EvResponse, request.EvFail, request.EvTimeout:
|
case request.EvResponse, request.EvFail, request.EvTimeout:
|
||||||
|
|
@ -132,10 +133,12 @@ func (s *CheckpointInit) Process(requester request.Requester, events []request.E
|
||||||
newState.state = ssPrintStatus
|
newState.state = ssPrintStatus
|
||||||
s.serverState[sid.Server] = newState
|
s.serverState[sid.Server] = newState
|
||||||
}
|
}
|
||||||
|
|
||||||
case request.EvUnregistered:
|
case request.EvUnregistered:
|
||||||
delete(s.serverState, event.Server)
|
delete(s.serverState, event.Server)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// start a request if possible
|
// start a request if possible
|
||||||
for _, server := range requester.CanSendTo() {
|
for _, server := range requester.CanSendTo() {
|
||||||
switch s.serverState[server].state {
|
switch s.serverState[server].state {
|
||||||
|
|
@ -156,6 +159,7 @@ func (s *CheckpointInit) Process(requester request.Requester, events []request.E
|
||||||
s.serverState[server] = newState
|
s.serverState[server] = newState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// print log message if necessary
|
// print log message if necessary
|
||||||
for server, state := range s.serverState {
|
for server, state := range s.serverState {
|
||||||
if state.state != ssPrintStatus {
|
if state.state != ssPrintStatus {
|
||||||
|
|
@ -316,9 +320,9 @@ func (s *ForwardUpdateSync) Process(requester request.Requester, events []reques
|
||||||
if !queued {
|
if !queued {
|
||||||
s.unlockRange(sid, req)
|
s.unlockRange(sid, req)
|
||||||
}
|
}
|
||||||
case EvNewSignedHead:
|
case EvNewOptimisticUpdate:
|
||||||
signedHead := event.Data.(types.SignedHeader)
|
update := event.Data.(types.OptimisticUpdate)
|
||||||
s.nextSyncPeriod[event.Server] = types.SyncPeriod(signedHead.SignatureSlot + 256)
|
s.nextSyncPeriod[event.Server] = types.SyncPeriod(update.SignatureSlot + 256)
|
||||||
case request.EvUnregistered:
|
case request.EvUnregistered:
|
||||||
delete(s.nextSyncPeriod, event.Server)
|
delete(s.nextSyncPeriod, event.Server)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,9 +68,9 @@ func TestUpdateSyncParallel(t *testing.T) {
|
||||||
ts := NewTestScheduler(t, updateSync)
|
ts := NewTestScheduler(t, updateSync)
|
||||||
// add 2 servers, head at period 100; allow 3-3 parallel requests for each
|
// add 2 servers, head at period 100; allow 3-3 parallel requests for each
|
||||||
ts.AddServer(testServer1, 3)
|
ts.AddServer(testServer1, 3)
|
||||||
ts.ServerEvent(EvNewSignedHead, testServer1, types.SignedHeader{SignatureSlot: 0x2000*100 + 0x1000})
|
ts.ServerEvent(EvNewOptimisticUpdate, testServer1, types.OptimisticUpdate{SignatureSlot: 0x2000*100 + 0x1000})
|
||||||
ts.AddServer(testServer2, 3)
|
ts.AddServer(testServer2, 3)
|
||||||
ts.ServerEvent(EvNewSignedHead, testServer2, types.SignedHeader{SignatureSlot: 0x2000*100 + 0x1000})
|
ts.ServerEvent(EvNewOptimisticUpdate, testServer2, types.OptimisticUpdate{SignatureSlot: 0x2000*100 + 0x1000})
|
||||||
|
|
||||||
// expect 6 requests to be sent
|
// expect 6 requests to be sent
|
||||||
ts.Run(1,
|
ts.Run(1,
|
||||||
|
|
@ -150,11 +150,11 @@ func TestUpdateSyncDifferentHeads(t *testing.T) {
|
||||||
ts := NewTestScheduler(t, updateSync)
|
ts := NewTestScheduler(t, updateSync)
|
||||||
// add 3 servers with different announced head periods
|
// add 3 servers with different announced head periods
|
||||||
ts.AddServer(testServer1, 1)
|
ts.AddServer(testServer1, 1)
|
||||||
ts.ServerEvent(EvNewSignedHead, testServer1, types.SignedHeader{SignatureSlot: 0x2000*15 + 0x1000})
|
ts.ServerEvent(EvNewOptimisticUpdate, testServer1, types.OptimisticUpdate{SignatureSlot: 0x2000*15 + 0x1000})
|
||||||
ts.AddServer(testServer2, 1)
|
ts.AddServer(testServer2, 1)
|
||||||
ts.ServerEvent(EvNewSignedHead, testServer2, types.SignedHeader{SignatureSlot: 0x2000*16 + 0x1000})
|
ts.ServerEvent(EvNewOptimisticUpdate, testServer2, types.OptimisticUpdate{SignatureSlot: 0x2000*16 + 0x1000})
|
||||||
ts.AddServer(testServer3, 1)
|
ts.AddServer(testServer3, 1)
|
||||||
ts.ServerEvent(EvNewSignedHead, testServer3, types.SignedHeader{SignatureSlot: 0x2000*17 + 0x1000})
|
ts.ServerEvent(EvNewOptimisticUpdate, testServer3, types.OptimisticUpdate{SignatureSlot: 0x2000*17 + 0x1000})
|
||||||
|
|
||||||
// expect request to the best announced head
|
// expect request to the best announced head
|
||||||
ts.Run(1, testServer3, ReqUpdates{FirstPeriod: 10, Count: 7})
|
ts.Run(1, testServer3, ReqUpdates{FirstPeriod: 10, Count: 7})
|
||||||
|
|
@ -190,7 +190,7 @@ func TestUpdateSyncDifferentHeads(t *testing.T) {
|
||||||
|
|
||||||
// a new server is registered with announced head period 17
|
// a new server is registered with announced head period 17
|
||||||
ts.AddServer(testServer4, 1)
|
ts.AddServer(testServer4, 1)
|
||||||
ts.ServerEvent(EvNewSignedHead, testServer4, types.SignedHeader{SignatureSlot: 0x2000*17 + 0x1000})
|
ts.ServerEvent(EvNewOptimisticUpdate, testServer4, types.OptimisticUpdate{SignatureSlot: 0x2000*17 + 0x1000})
|
||||||
// expect request to sync one more period
|
// expect request to sync one more period
|
||||||
ts.Run(7, testServer4, ReqUpdates{FirstPeriod: 16, Count: 1})
|
ts.Run(7, testServer4, ReqUpdates{FirstPeriod: 16, Count: 1})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -66,9 +66,8 @@ func convertPayload[T payloadType](payload T, parentRoot *zrntcommon.Root) (*typ
|
||||||
block := types.NewBlockWithHeader(&header)
|
block := types.NewBlockWithHeader(&header)
|
||||||
block = block.WithBody(transactions, nil)
|
block = block.WithBody(transactions, nil)
|
||||||
block = block.WithWithdrawals(withdrawals)
|
block = block.WithWithdrawals(withdrawals)
|
||||||
hash := block.Hash()
|
if hash := block.Hash(); hash != expectedHash {
|
||||||
if hash != expectedHash {
|
return nil, fmt.Errorf("Sanity check failed, payload hash does not match (expected %x, got %x)", expectedHash, hash)
|
||||||
return block, fmt.Errorf("Sanity check failed, payload hash does not match (expected %x, got %x)", expectedHash, hash)
|
|
||||||
}
|
}
|
||||||
return block, nil
|
return block, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/beacon/merkle"
|
"github.com/ethereum/go-ethereum/beacon/merkle"
|
||||||
"github.com/ethereum/go-ethereum/beacon/params"
|
"github.com/ethereum/go-ethereum/beacon/params"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
ctypes "github.com/ethereum/go-ethereum/core/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HeadInfo represents an unvalidated new head announcement.
|
// HeadInfo represents an unvalidated new head announcement.
|
||||||
|
|
@ -142,17 +142,57 @@ func (u UpdateScore) BetterThan(w UpdateScore) bool {
|
||||||
return u.SignerCount > w.SignerCount
|
return u.SignerCount > w.SignerCount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HeaderWithExecProof contains a beacon header and proves the belonging execution
|
||||||
|
// payload header with a Merkle proof.
|
||||||
type HeaderWithExecProof struct {
|
type HeaderWithExecProof struct {
|
||||||
Header
|
Header
|
||||||
PayloadHeader *ExecutionHeader
|
PayloadHeader *ExecutionHeader
|
||||||
PayloadBranch merkle.Values
|
PayloadBranch merkle.Values
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate verifies the Merkle proof of the execution payload header.
|
||||||
func (h *HeaderWithExecProof) Validate() error {
|
func (h *HeaderWithExecProof) Validate() error {
|
||||||
payloadRoot := h.PayloadHeader.PayloadRoot()
|
return merkle.VerifyProof(h.BodyRoot, params.BodyIndexExecPayload, h.PayloadBranch, h.PayloadHeader.PayloadRoot())
|
||||||
return merkle.VerifyProof(h.BodyRoot, params.BodyIndexExecPayload, h.PayloadBranch, payloadRoot)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OptimisticUpdate proves sync committee commitment on the attested beacon header.
|
||||||
|
// It also proves the belonging execution payload header with a Merkle proof.
|
||||||
|
//
|
||||||
|
// See data structure definition here:
|
||||||
|
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientoptimisticupdate
|
||||||
|
type OptimisticUpdate struct {
|
||||||
|
Attested HeaderWithExecProof
|
||||||
|
// Sync committee BLS signature aggregate
|
||||||
|
Signature SyncAggregate
|
||||||
|
// Slot in which the signature has been created (newer than Header.Slot,
|
||||||
|
// determines the signing sync committee)
|
||||||
|
SignatureSlot uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedHeader returns the signed attested header of the update.
|
||||||
|
func (u *OptimisticUpdate) SignedHeader() SignedHeader {
|
||||||
|
return SignedHeader{
|
||||||
|
Header: u.Attested.Header,
|
||||||
|
Signature: u.Signature,
|
||||||
|
SignatureSlot: u.SignatureSlot,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate verifies the Merkle proof proving the execution payload header.
|
||||||
|
// Note that the sync committee signature of the attested header should be
|
||||||
|
// verified separately by a synced committee chain.
|
||||||
|
func (u *OptimisticUpdate) Validate() error {
|
||||||
|
return u.Attested.Validate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// FinalityUpdate proves a finalized beacon header by a sync committee commitment
|
||||||
|
// on an attested beacon header, referring to the latest finalized header with a
|
||||||
|
// Merkle proof.
|
||||||
|
// It also proves the execution payload header belonging to both the attested and
|
||||||
|
// the finalized beacon header with Merkle proofs.
|
||||||
|
//
|
||||||
|
// See data structure definition here:
|
||||||
|
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientfinalityupdate
|
||||||
type FinalityUpdate struct {
|
type FinalityUpdate struct {
|
||||||
Attested, Finalized HeaderWithExecProof
|
Attested, Finalized HeaderWithExecProof
|
||||||
FinalityBranch merkle.Values
|
FinalityBranch merkle.Values
|
||||||
|
|
@ -163,6 +203,7 @@ type FinalityUpdate struct {
|
||||||
SignatureSlot uint64
|
SignatureSlot uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SignedHeader returns the signed attested header of the update.
|
||||||
func (u *FinalityUpdate) SignedHeader() SignedHeader {
|
func (u *FinalityUpdate) SignedHeader() SignedHeader {
|
||||||
return SignedHeader{
|
return SignedHeader{
|
||||||
Header: u.Attested.Header,
|
Header: u.Attested.Header,
|
||||||
|
|
@ -171,6 +212,10 @@ func (u *FinalityUpdate) SignedHeader() SignedHeader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate verifies the Merkle proofs proving the finalized beacon header and
|
||||||
|
// the execution payload headers belonging to the attested and finalized headers.
|
||||||
|
// Note that the sync committee signature of the attested header should be
|
||||||
|
// verified separately by a synced committee chain.
|
||||||
func (u *FinalityUpdate) Validate() error {
|
func (u *FinalityUpdate) Validate() error {
|
||||||
if err := u.Attested.Validate(); err != nil {
|
if err := u.Attested.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -186,6 +231,6 @@ func (u *FinalityUpdate) Validate() error {
|
||||||
// finalized execution block.
|
// finalized execution block.
|
||||||
type ChainHeadEvent struct {
|
type ChainHeadEvent struct {
|
||||||
BeaconHead Header
|
BeaconHead Header
|
||||||
Block *types.Block
|
Block *ctypes.Block
|
||||||
Finalized common.Hash
|
Finalized common.Hash
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ if one is set. Otherwise it prints the genesis from the datadir.`,
|
||||||
utils.MetricsInfluxDBOrganizationFlag,
|
utils.MetricsInfluxDBOrganizationFlag,
|
||||||
utils.TxLookupLimitFlag,
|
utils.TxLookupLimitFlag,
|
||||||
utils.VMTraceFlag,
|
utils.VMTraceFlag,
|
||||||
utils.VMTraceConfigFlag,
|
utils.VMTraceJsonConfigFlag,
|
||||||
utils.TransactionHistoryFlag,
|
utils.TransactionHistoryFlag,
|
||||||
utils.StateHistoryFlag,
|
utils.StateHistoryFlag,
|
||||||
}, utils.DatabaseFlags),
|
}, utils.DatabaseFlags),
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,7 @@ var (
|
||||||
utils.DeveloperPeriodFlag,
|
utils.DeveloperPeriodFlag,
|
||||||
utils.VMEnableDebugFlag,
|
utils.VMEnableDebugFlag,
|
||||||
utils.VMTraceFlag,
|
utils.VMTraceFlag,
|
||||||
utils.VMTraceConfigFlag,
|
utils.VMTraceJsonConfigFlag,
|
||||||
utils.NetworkIdFlag,
|
utils.NetworkIdFlag,
|
||||||
utils.EthStatsURLFlag,
|
utils.EthStatsURLFlag,
|
||||||
utils.NoCompactionFlag,
|
utils.NoCompactionFlag,
|
||||||
|
|
|
||||||
|
|
@ -544,7 +544,7 @@ var (
|
||||||
Usage: "Name of tracer which should record internal VM operations (costly)",
|
Usage: "Name of tracer which should record internal VM operations (costly)",
|
||||||
Category: flags.VMCategory,
|
Category: flags.VMCategory,
|
||||||
}
|
}
|
||||||
VMTraceConfigFlag = &cli.StringFlag{
|
VMTraceJsonConfigFlag = &cli.StringFlag{
|
||||||
Name: "vmtrace.jsonconfig",
|
Name: "vmtrace.jsonconfig",
|
||||||
Usage: "Tracer configuration (JSON)",
|
Usage: "Tracer configuration (JSON)",
|
||||||
Category: flags.VMCategory,
|
Category: flags.VMCategory,
|
||||||
|
|
@ -1903,12 +1903,12 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
||||||
if ctx.IsSet(VMTraceFlag.Name) {
|
if ctx.IsSet(VMTraceFlag.Name) {
|
||||||
if name := ctx.String(VMTraceFlag.Name); name != "" {
|
if name := ctx.String(VMTraceFlag.Name); name != "" {
|
||||||
var config string
|
var config string
|
||||||
if ctx.IsSet(VMTraceConfigFlag.Name) {
|
if ctx.IsSet(VMTraceJsonConfigFlag.Name) {
|
||||||
config = ctx.String(VMTraceConfigFlag.Name)
|
config = ctx.String(VMTraceJsonConfigFlag.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.VMTrace = name
|
cfg.VMTrace = name
|
||||||
cfg.VMTraceConfig = config
|
cfg.VMTraceJsonConfig = config
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2192,8 +2192,8 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
|
||||||
if ctx.IsSet(VMTraceFlag.Name) {
|
if ctx.IsSet(VMTraceFlag.Name) {
|
||||||
if name := ctx.String(VMTraceFlag.Name); name != "" {
|
if name := ctx.String(VMTraceFlag.Name); name != "" {
|
||||||
var config json.RawMessage
|
var config json.RawMessage
|
||||||
if ctx.IsSet(VMTraceConfigFlag.Name) {
|
if ctx.IsSet(VMTraceJsonConfigFlag.Name) {
|
||||||
config = json.RawMessage(ctx.String(VMTraceConfigFlag.Name))
|
config = json.RawMessage(ctx.String(VMTraceJsonConfigFlag.Name))
|
||||||
}
|
}
|
||||||
t, err := tracers.LiveDirectory.New(name, config)
|
t, err := tracers.LiveDirectory.New(name, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package clique
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"maps"
|
||||||
"slices"
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -108,28 +109,16 @@ func (s *Snapshot) store(db ethdb.Database) error {
|
||||||
|
|
||||||
// copy creates a deep copy of the snapshot, though not the individual votes.
|
// copy creates a deep copy of the snapshot, though not the individual votes.
|
||||||
func (s *Snapshot) copy() *Snapshot {
|
func (s *Snapshot) copy() *Snapshot {
|
||||||
cpy := &Snapshot{
|
return &Snapshot{
|
||||||
config: s.config,
|
config: s.config,
|
||||||
sigcache: s.sigcache,
|
sigcache: s.sigcache,
|
||||||
Number: s.Number,
|
Number: s.Number,
|
||||||
Hash: s.Hash,
|
Hash: s.Hash,
|
||||||
Signers: make(map[common.Address]struct{}),
|
Signers: maps.Clone(s.Signers),
|
||||||
Recents: make(map[uint64]common.Address),
|
Recents: maps.Clone(s.Recents),
|
||||||
Votes: make([]*Vote, len(s.Votes)),
|
Votes: slices.Clone(s.Votes),
|
||||||
Tally: make(map[common.Address]Tally),
|
Tally: maps.Clone(s.Tally),
|
||||||
}
|
}
|
||||||
for signer := range s.Signers {
|
|
||||||
cpy.Signers[signer] = struct{}{}
|
|
||||||
}
|
|
||||||
for block, signer := range s.Recents {
|
|
||||||
cpy.Recents[block] = signer
|
|
||||||
}
|
|
||||||
for address, tally := range s.Tally {
|
|
||||||
cpy.Tally[address] = tally
|
|
||||||
}
|
|
||||||
copy(cpy.Votes, s.Votes)
|
|
||||||
|
|
||||||
return cpy
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// validVote returns whether it makes sense to cast the specified vote in the
|
// validVote returns whether it makes sense to cast the specified vote in the
|
||||||
|
|
|
||||||
|
|
@ -1153,6 +1153,10 @@ func (bc *BlockChain) Stop() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Allow tracers to clean-up and release resources.
|
||||||
|
if bc.logger != nil && bc.logger.OnClose != nil {
|
||||||
|
bc.logger.OnClose()
|
||||||
|
}
|
||||||
// Close the trie database, release all the held resources as the last step.
|
// Close the trie database, release all the held resources as the last step.
|
||||||
if err := bc.triedb.Close(); err != nil {
|
if err := bc.triedb.Close(); err != nil {
|
||||||
log.Error("Failed to close trie database", "err", err)
|
log.Error("Failed to close trie database", "err", err)
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,7 @@ func (it *insertIterator) current() *types.Header {
|
||||||
return it.chain[it.index].Header()
|
return it.chain[it.index].Header()
|
||||||
}
|
}
|
||||||
|
|
||||||
// first returns the first block in the it.
|
// first returns the first block in it.
|
||||||
func (it *insertIterator) first() *types.Block {
|
func (it *insertIterator) first() *types.Block {
|
||||||
return it.chain[0]
|
return it.chain[0]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ type nodeIterator struct {
|
||||||
Error error // Failure set in case of an internal error in the iterator
|
Error error // Failure set in case of an internal error in the iterator
|
||||||
}
|
}
|
||||||
|
|
||||||
// newNodeIterator creates an post-order state node iterator.
|
// newNodeIterator creates a post-order state node iterator.
|
||||||
func newNodeIterator(state *StateDB) *nodeIterator {
|
func newNodeIterator(state *StateDB) *nodeIterator {
|
||||||
return &nodeIterator{
|
return &nodeIterator{
|
||||||
state: state,
|
state: state,
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@
|
||||||
package state
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"maps"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
@ -29,6 +31,9 @@ type journalEntry interface {
|
||||||
|
|
||||||
// dirtied returns the Ethereum address modified by this journal entry.
|
// dirtied returns the Ethereum address modified by this journal entry.
|
||||||
dirtied() *common.Address
|
dirtied() *common.Address
|
||||||
|
|
||||||
|
// copy returns a deep-copied journal entry.
|
||||||
|
copy() journalEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
// journal contains the list of state modifications applied since the last state
|
// journal contains the list of state modifications applied since the last state
|
||||||
|
|
@ -83,22 +88,31 @@ func (j *journal) length() int {
|
||||||
return len(j.entries)
|
return len(j.entries)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// copy returns a deep-copied journal.
|
||||||
|
func (j *journal) copy() *journal {
|
||||||
|
entries := make([]journalEntry, 0, j.length())
|
||||||
|
for i := 0; i < j.length(); i++ {
|
||||||
|
entries = append(entries, j.entries[i].copy())
|
||||||
|
}
|
||||||
|
return &journal{
|
||||||
|
entries: entries,
|
||||||
|
dirties: maps.Clone(j.dirties),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// Changes to the account trie.
|
// Changes to the account trie.
|
||||||
createObjectChange struct {
|
createObjectChange struct {
|
||||||
account *common.Address
|
account *common.Address
|
||||||
}
|
}
|
||||||
resetObjectChange struct {
|
|
||||||
account *common.Address
|
|
||||||
prev *stateObject
|
|
||||||
prevdestruct bool
|
|
||||||
prevAccount []byte
|
|
||||||
prevStorage map[common.Hash][]byte
|
|
||||||
|
|
||||||
prevAccountOriginExist bool
|
// createContractChange represents an account becoming a contract-account.
|
||||||
prevAccountOrigin []byte
|
// This event happens prior to executing initcode. The journal-event simply
|
||||||
prevStorageOrigin map[common.Hash][]byte
|
// manages the created-flag, in order to allow same-tx destruction.
|
||||||
|
createContractChange struct {
|
||||||
|
account common.Address
|
||||||
}
|
}
|
||||||
|
|
||||||
selfDestructChange struct {
|
selfDestructChange struct {
|
||||||
account *common.Address
|
account *common.Address
|
||||||
prev bool // whether account had already self-destructed
|
prev bool // whether account had already self-destructed
|
||||||
|
|
@ -136,6 +150,7 @@ type (
|
||||||
touchChange struct {
|
touchChange struct {
|
||||||
account *common.Address
|
account *common.Address
|
||||||
}
|
}
|
||||||
|
|
||||||
// Changes to the access list
|
// Changes to the access list
|
||||||
accessListAddAccountChange struct {
|
accessListAddAccountChange struct {
|
||||||
address *common.Address
|
address *common.Address
|
||||||
|
|
@ -145,6 +160,7 @@ type (
|
||||||
slot *common.Hash
|
slot *common.Hash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Changes to transient storage
|
||||||
transientStorageChange struct {
|
transientStorageChange struct {
|
||||||
account *common.Address
|
account *common.Address
|
||||||
key, prevalue common.Hash
|
key, prevalue common.Hash
|
||||||
|
|
@ -153,34 +169,30 @@ type (
|
||||||
|
|
||||||
func (ch createObjectChange) revert(s *StateDB) {
|
func (ch createObjectChange) revert(s *StateDB) {
|
||||||
delete(s.stateObjects, *ch.account)
|
delete(s.stateObjects, *ch.account)
|
||||||
delete(s.stateObjectsDirty, *ch.account)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch createObjectChange) dirtied() *common.Address {
|
func (ch createObjectChange) dirtied() *common.Address {
|
||||||
return ch.account
|
return ch.account
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch resetObjectChange) revert(s *StateDB) {
|
func (ch createObjectChange) copy() journalEntry {
|
||||||
s.setStateObject(ch.prev)
|
return createObjectChange{
|
||||||
if !ch.prevdestruct {
|
account: ch.account,
|
||||||
delete(s.stateObjectsDestruct, ch.prev.address)
|
|
||||||
}
|
|
||||||
if ch.prevAccount != nil {
|
|
||||||
s.accounts[ch.prev.addrHash] = ch.prevAccount
|
|
||||||
}
|
|
||||||
if ch.prevStorage != nil {
|
|
||||||
s.storages[ch.prev.addrHash] = ch.prevStorage
|
|
||||||
}
|
|
||||||
if ch.prevAccountOriginExist {
|
|
||||||
s.accountsOrigin[ch.prev.address] = ch.prevAccountOrigin
|
|
||||||
}
|
|
||||||
if ch.prevStorageOrigin != nil {
|
|
||||||
s.storagesOrigin[ch.prev.address] = ch.prevStorageOrigin
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch resetObjectChange) dirtied() *common.Address {
|
func (ch createContractChange) revert(s *StateDB) {
|
||||||
return ch.account
|
s.getStateObject(ch.account).newContract = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch createContractChange) dirtied() *common.Address {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch createContractChange) copy() journalEntry {
|
||||||
|
return createContractChange{
|
||||||
|
account: ch.account,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch selfDestructChange) revert(s *StateDB) {
|
func (ch selfDestructChange) revert(s *StateDB) {
|
||||||
|
|
@ -195,6 +207,14 @@ func (ch selfDestructChange) dirtied() *common.Address {
|
||||||
return ch.account
|
return ch.account
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ch selfDestructChange) copy() journalEntry {
|
||||||
|
return selfDestructChange{
|
||||||
|
account: ch.account,
|
||||||
|
prev: ch.prev,
|
||||||
|
prevbalance: new(uint256.Int).Set(ch.prevbalance),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var ripemd = common.HexToAddress("0000000000000000000000000000000000000003")
|
var ripemd = common.HexToAddress("0000000000000000000000000000000000000003")
|
||||||
|
|
||||||
func (ch touchChange) revert(s *StateDB) {
|
func (ch touchChange) revert(s *StateDB) {
|
||||||
|
|
@ -204,6 +224,12 @@ func (ch touchChange) dirtied() *common.Address {
|
||||||
return ch.account
|
return ch.account
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ch touchChange) copy() journalEntry {
|
||||||
|
return touchChange{
|
||||||
|
account: ch.account,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (ch balanceChange) revert(s *StateDB) {
|
func (ch balanceChange) revert(s *StateDB) {
|
||||||
s.getStateObject(*ch.account).setBalance(ch.prev)
|
s.getStateObject(*ch.account).setBalance(ch.prev)
|
||||||
}
|
}
|
||||||
|
|
@ -212,6 +238,13 @@ func (ch balanceChange) dirtied() *common.Address {
|
||||||
return ch.account
|
return ch.account
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ch balanceChange) copy() journalEntry {
|
||||||
|
return balanceChange{
|
||||||
|
account: ch.account,
|
||||||
|
prev: new(uint256.Int).Set(ch.prev),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (ch nonceChange) revert(s *StateDB) {
|
func (ch nonceChange) revert(s *StateDB) {
|
||||||
s.getStateObject(*ch.account).setNonce(ch.prev)
|
s.getStateObject(*ch.account).setNonce(ch.prev)
|
||||||
}
|
}
|
||||||
|
|
@ -220,6 +253,13 @@ func (ch nonceChange) dirtied() *common.Address {
|
||||||
return ch.account
|
return ch.account
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ch nonceChange) copy() journalEntry {
|
||||||
|
return nonceChange{
|
||||||
|
account: ch.account,
|
||||||
|
prev: ch.prev,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (ch codeChange) revert(s *StateDB) {
|
func (ch codeChange) revert(s *StateDB) {
|
||||||
s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode)
|
s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode)
|
||||||
}
|
}
|
||||||
|
|
@ -228,6 +268,14 @@ func (ch codeChange) dirtied() *common.Address {
|
||||||
return ch.account
|
return ch.account
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ch codeChange) copy() journalEntry {
|
||||||
|
return codeChange{
|
||||||
|
account: ch.account,
|
||||||
|
prevhash: common.CopyBytes(ch.prevhash),
|
||||||
|
prevcode: common.CopyBytes(ch.prevcode),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (ch storageChange) revert(s *StateDB) {
|
func (ch storageChange) revert(s *StateDB) {
|
||||||
s.getStateObject(*ch.account).setState(ch.key, ch.prevalue)
|
s.getStateObject(*ch.account).setState(ch.key, ch.prevalue)
|
||||||
}
|
}
|
||||||
|
|
@ -236,6 +284,14 @@ func (ch storageChange) dirtied() *common.Address {
|
||||||
return ch.account
|
return ch.account
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ch storageChange) copy() journalEntry {
|
||||||
|
return storageChange{
|
||||||
|
account: ch.account,
|
||||||
|
key: ch.key,
|
||||||
|
prevalue: ch.prevalue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (ch transientStorageChange) revert(s *StateDB) {
|
func (ch transientStorageChange) revert(s *StateDB) {
|
||||||
s.setTransientState(*ch.account, ch.key, ch.prevalue)
|
s.setTransientState(*ch.account, ch.key, ch.prevalue)
|
||||||
}
|
}
|
||||||
|
|
@ -244,6 +300,14 @@ func (ch transientStorageChange) dirtied() *common.Address {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ch transientStorageChange) copy() journalEntry {
|
||||||
|
return transientStorageChange{
|
||||||
|
account: ch.account,
|
||||||
|
key: ch.key,
|
||||||
|
prevalue: ch.prevalue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (ch refundChange) revert(s *StateDB) {
|
func (ch refundChange) revert(s *StateDB) {
|
||||||
s.refund = ch.prev
|
s.refund = ch.prev
|
||||||
}
|
}
|
||||||
|
|
@ -252,6 +316,12 @@ func (ch refundChange) dirtied() *common.Address {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ch refundChange) copy() journalEntry {
|
||||||
|
return refundChange{
|
||||||
|
prev: ch.prev,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (ch addLogChange) revert(s *StateDB) {
|
func (ch addLogChange) revert(s *StateDB) {
|
||||||
logs := s.logs[ch.txhash]
|
logs := s.logs[ch.txhash]
|
||||||
if len(logs) == 1 {
|
if len(logs) == 1 {
|
||||||
|
|
@ -266,6 +336,12 @@ func (ch addLogChange) dirtied() *common.Address {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ch addLogChange) copy() journalEntry {
|
||||||
|
return addLogChange{
|
||||||
|
txhash: ch.txhash,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (ch addPreimageChange) revert(s *StateDB) {
|
func (ch addPreimageChange) revert(s *StateDB) {
|
||||||
delete(s.preimages, ch.hash)
|
delete(s.preimages, ch.hash)
|
||||||
}
|
}
|
||||||
|
|
@ -274,6 +350,12 @@ func (ch addPreimageChange) dirtied() *common.Address {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ch addPreimageChange) copy() journalEntry {
|
||||||
|
return addPreimageChange{
|
||||||
|
hash: ch.hash,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (ch accessListAddAccountChange) revert(s *StateDB) {
|
func (ch accessListAddAccountChange) revert(s *StateDB) {
|
||||||
/*
|
/*
|
||||||
One important invariant here, is that whenever a (addr, slot) is added, if the
|
One important invariant here, is that whenever a (addr, slot) is added, if the
|
||||||
|
|
@ -291,6 +373,12 @@ func (ch accessListAddAccountChange) dirtied() *common.Address {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ch accessListAddAccountChange) copy() journalEntry {
|
||||||
|
return accessListAddAccountChange{
|
||||||
|
address: ch.address,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (ch accessListAddSlotChange) revert(s *StateDB) {
|
func (ch accessListAddSlotChange) revert(s *StateDB) {
|
||||||
s.accessList.DeleteSlot(*ch.address, *ch.slot)
|
s.accessList.DeleteSlot(*ch.address, *ch.slot)
|
||||||
}
|
}
|
||||||
|
|
@ -298,3 +386,10 @@ func (ch accessListAddSlotChange) revert(s *StateDB) {
|
||||||
func (ch accessListAddSlotChange) dirtied() *common.Address {
|
func (ch accessListAddSlotChange) dirtied() *common.Address {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ch accessListAddSlotChange) copy() journalEntry {
|
||||||
|
return accessListAddSlotChange{
|
||||||
|
address: ch.address,
|
||||||
|
slot: ch.slot,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ type generatorStats struct {
|
||||||
storage common.StorageSize // Total account and storage slot size(generation or recovery)
|
storage common.StorageSize // Total account and storage slot size(generation or recovery)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log creates an contextual log with the given message and the context pulled
|
// Log creates a contextual log with the given message and the context pulled
|
||||||
// from the internally maintained statistics.
|
// from the internally maintained statistics.
|
||||||
func (gs *generatorStats) Log(msg string, root common.Hash, marker []byte) {
|
func (gs *generatorStats) Log(msg string, root common.Hash, marker []byte) {
|
||||||
var ctx []interface{}
|
var ctx []interface{}
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ func (dl *diffLayer) initBinaryStorageIterator(account common.Hash) Iterator {
|
||||||
parent, ok := dl.parent.(*diffLayer)
|
parent, ok := dl.parent.(*diffLayer)
|
||||||
if !ok {
|
if !ok {
|
||||||
// If the storage in this layer is already destructed, discard all
|
// If the storage in this layer is already destructed, discard all
|
||||||
// deeper layers but still return an valid single-branch iterator.
|
// deeper layers but still return a valid single-branch iterator.
|
||||||
a, destructed := dl.StorageIterator(account, common.Hash{})
|
a, destructed := dl.StorageIterator(account, common.Hash{})
|
||||||
if destructed {
|
if destructed {
|
||||||
l := &binaryIterator{
|
l := &binaryIterator{
|
||||||
|
|
@ -92,7 +92,7 @@ func (dl *diffLayer) initBinaryStorageIterator(account common.Hash) Iterator {
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
// If the storage in this layer is already destructed, discard all
|
// If the storage in this layer is already destructed, discard all
|
||||||
// deeper layers but still return an valid single-branch iterator.
|
// deeper layers but still return a valid single-branch iterator.
|
||||||
a, destructed := dl.StorageIterator(account, common.Hash{})
|
a, destructed := dl.StorageIterator(account, common.Hash{})
|
||||||
if destructed {
|
if destructed {
|
||||||
l := &binaryIterator{
|
l := &binaryIterator{
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
// weightedIterator is a iterator with an assigned weight. It is used to prioritise
|
// weightedIterator is an iterator with an assigned weight. It is used to prioritise
|
||||||
// which account or storage slot is the correct one if multiple iterators find the
|
// which account or storage slot is the correct one if multiple iterators find the
|
||||||
// same one (modified in multiple consecutive blocks).
|
// same one (modified in multiple consecutive blocks).
|
||||||
type weightedIterator struct {
|
type weightedIterator struct {
|
||||||
|
|
|
||||||
|
|
@ -835,7 +835,7 @@ func (t *Tree) disklayer() *diskLayer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// diskRoot is a internal helper function to return the disk layer root.
|
// diskRoot is an internal helper function to return the disk layer root.
|
||||||
// The lock of snapTree is assumed to be held already.
|
// The lock of snapTree is assumed to be held already.
|
||||||
func (t *Tree) diskRoot() common.Hash {
|
func (t *Tree) diskRoot() common.Hash {
|
||||||
disklayer := t.disklayer()
|
disklayer := t.disklayer()
|
||||||
|
|
|
||||||
|
|
@ -32,21 +32,8 @@ import (
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Code []byte
|
|
||||||
|
|
||||||
func (c Code) String() string {
|
|
||||||
return string(c) //strings.Join(Disassemble(c), " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
type Storage map[common.Hash]common.Hash
|
type Storage map[common.Hash]common.Hash
|
||||||
|
|
||||||
func (s Storage) String() (str string) {
|
|
||||||
for key, value := range s {
|
|
||||||
str += fmt.Sprintf("%X : %X\n", key, value)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s Storage) Copy() Storage {
|
func (s Storage) Copy() Storage {
|
||||||
return maps.Clone(s)
|
return maps.Clone(s)
|
||||||
}
|
}
|
||||||
|
|
@ -66,7 +53,7 @@ type stateObject struct {
|
||||||
|
|
||||||
// Write caches.
|
// Write caches.
|
||||||
trie Trie // storage trie, which becomes non-nil on first access
|
trie Trie // storage trie, which becomes non-nil on first access
|
||||||
code Code // contract bytecode, which gets set when code is loaded
|
code []byte // contract bytecode, which gets set when code is loaded
|
||||||
|
|
||||||
originStorage Storage // Storage cache of original entries to dedup rewrites
|
originStorage Storage // Storage cache of original entries to dedup rewrites
|
||||||
pendingStorage Storage // Storage entries that need to be flushed to disk, at the end of an entire block
|
pendingStorage Storage // Storage entries that need to be flushed to disk, at the end of an entire block
|
||||||
|
|
@ -75,17 +62,16 @@ type stateObject struct {
|
||||||
// Cache flags.
|
// Cache flags.
|
||||||
dirtyCode bool // true if the code was updated
|
dirtyCode bool // true if the code was updated
|
||||||
|
|
||||||
// Flag whether the account was marked as self-destructed. The self-destructed account
|
// Flag whether the account was marked as self-destructed. The self-destructed
|
||||||
// is still accessible in the scope of same transaction.
|
// account is still accessible in the scope of same transaction.
|
||||||
selfDestructed bool
|
selfDestructed bool
|
||||||
|
|
||||||
// Flag whether the account was marked as deleted. A self-destructed account
|
// This is an EIP-6780 flag indicating whether the object is eligible for
|
||||||
// or an account that is considered as empty will be marked as deleted at
|
// self-destruct according to EIP-6780. The flag could be set either when
|
||||||
// the end of transaction and no longer accessible anymore.
|
// the contract is just created within the current transaction, or when the
|
||||||
deleted bool
|
// object was previously existent and is being deployed as a contract within
|
||||||
|
// the current transaction.
|
||||||
// Flag whether the object was created in the current transaction
|
newContract bool
|
||||||
created bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// empty returns whether the account is considered empty.
|
// empty returns whether the account is considered empty.
|
||||||
|
|
@ -95,10 +81,7 @@ func (s *stateObject) empty() bool {
|
||||||
|
|
||||||
// newObject creates a state object.
|
// newObject creates a state object.
|
||||||
func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *stateObject {
|
func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *stateObject {
|
||||||
var (
|
origin := acct
|
||||||
origin = acct
|
|
||||||
created = acct == nil // true if the account was not existent
|
|
||||||
)
|
|
||||||
if acct == nil {
|
if acct == nil {
|
||||||
acct = types.NewEmptyStateAccount()
|
acct = types.NewEmptyStateAccount()
|
||||||
}
|
}
|
||||||
|
|
@ -111,7 +94,6 @@ func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *s
|
||||||
originStorage: make(Storage),
|
originStorage: make(Storage),
|
||||||
pendingStorage: make(Storage),
|
pendingStorage: make(Storage),
|
||||||
dirtyStorage: make(Storage),
|
dirtyStorage: make(Storage),
|
||||||
created: created,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -264,6 +246,10 @@ func (s *stateObject) finalise(prefetch bool) {
|
||||||
if len(s.dirtyStorage) > 0 {
|
if len(s.dirtyStorage) > 0 {
|
||||||
s.dirtyStorage = make(Storage)
|
s.dirtyStorage = make(Storage)
|
||||||
}
|
}
|
||||||
|
// Revoke the flag at the end of the transaction. It finalizes the status
|
||||||
|
// of the newly-created object as it's no longer eligible for self-destruct
|
||||||
|
// by EIP-6780. For non-newly-created objects, it's a no-op.
|
||||||
|
s.newContract = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateTrie is responsible for persisting cached storage changes into the
|
// updateTrie is responsible for persisting cached storage changes into the
|
||||||
|
|
@ -376,7 +362,7 @@ func (s *stateObject) updateTrie() (Trie, error) {
|
||||||
// new storage trie root.
|
// new storage trie root.
|
||||||
func (s *stateObject) updateRoot() {
|
func (s *stateObject) updateRoot() {
|
||||||
// Flush cached storage mutations into trie, short circuit if any error
|
// Flush cached storage mutations into trie, short circuit if any error
|
||||||
// is occurred or there is not change in the trie.
|
// is occurred or there is no change in the trie.
|
||||||
tr, err := s.updateTrie()
|
tr, err := s.updateTrie()
|
||||||
if err != nil || tr == nil {
|
if err != nil || tr == nil {
|
||||||
return
|
return
|
||||||
|
|
@ -463,12 +449,12 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject {
|
||||||
obj.trie = db.db.CopyTrie(s.trie)
|
obj.trie = db.db.CopyTrie(s.trie)
|
||||||
}
|
}
|
||||||
obj.code = s.code
|
obj.code = s.code
|
||||||
obj.dirtyStorage = s.dirtyStorage.Copy()
|
|
||||||
obj.originStorage = s.originStorage.Copy()
|
obj.originStorage = s.originStorage.Copy()
|
||||||
obj.pendingStorage = s.pendingStorage.Copy()
|
obj.pendingStorage = s.pendingStorage.Copy()
|
||||||
obj.selfDestructed = s.selfDestructed
|
obj.dirtyStorage = s.dirtyStorage.Copy()
|
||||||
obj.dirtyCode = s.dirtyCode
|
obj.dirtyCode = s.dirtyCode
|
||||||
obj.deleted = s.deleted
|
obj.selfDestructed = s.selfDestructed
|
||||||
|
obj.newContract = s.newContract
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -483,7 +469,7 @@ func (s *stateObject) Address() common.Address {
|
||||||
|
|
||||||
// Code returns the contract code associated with this object, if any.
|
// Code returns the contract code associated with this object, if any.
|
||||||
func (s *stateObject) Code() []byte {
|
func (s *stateObject) Code() []byte {
|
||||||
if s.code != nil {
|
if len(s.code) != 0 {
|
||||||
return s.code
|
return s.code
|
||||||
}
|
}
|
||||||
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {
|
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {
|
||||||
|
|
@ -501,7 +487,7 @@ func (s *stateObject) Code() []byte {
|
||||||
// or zero if none. This method is an almost mirror of Code, but uses a cache
|
// or zero if none. This method is an almost mirror of Code, but uses a cache
|
||||||
// inside the database to avoid loading codes seen recently.
|
// inside the database to avoid loading codes seen recently.
|
||||||
func (s *stateObject) CodeSize() int {
|
func (s *stateObject) CodeSize() int {
|
||||||
if s.code != nil {
|
if len(s.code) != 0 {
|
||||||
return len(s.code)
|
return len(s.code)
|
||||||
}
|
}
|
||||||
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {
|
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {
|
||||||
|
|
|
||||||
|
|
@ -194,106 +194,20 @@ func TestSnapshotEmpty(t *testing.T) {
|
||||||
s.state.RevertToSnapshot(s.state.Snapshot())
|
s.state.RevertToSnapshot(s.state.Snapshot())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSnapshot2(t *testing.T) {
|
func TestCreateObjectRevert(t *testing.T) {
|
||||||
state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
||||||
|
addr := common.BytesToAddress([]byte("so0"))
|
||||||
|
snap := state.Snapshot()
|
||||||
|
|
||||||
stateobjaddr0 := common.BytesToAddress([]byte("so0"))
|
state.CreateAccount(addr)
|
||||||
stateobjaddr1 := common.BytesToAddress([]byte("so1"))
|
so0 := state.getStateObject(addr)
|
||||||
var storageaddr common.Hash
|
|
||||||
|
|
||||||
data0 := common.BytesToHash([]byte{17})
|
|
||||||
data1 := common.BytesToHash([]byte{18})
|
|
||||||
|
|
||||||
state.SetState(stateobjaddr0, storageaddr, data0)
|
|
||||||
state.SetState(stateobjaddr1, storageaddr, data1)
|
|
||||||
|
|
||||||
// db, trie are already non-empty values
|
|
||||||
so0 := state.getStateObject(stateobjaddr0)
|
|
||||||
so0.SetBalance(uint256.NewInt(42), tracing.BalanceChangeUnspecified)
|
so0.SetBalance(uint256.NewInt(42), tracing.BalanceChangeUnspecified)
|
||||||
so0.SetNonce(43)
|
so0.SetNonce(43)
|
||||||
so0.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e'}), []byte{'c', 'a', 'f', 'e'})
|
so0.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e'}), []byte{'c', 'a', 'f', 'e'})
|
||||||
so0.selfDestructed = false
|
|
||||||
so0.deleted = false
|
|
||||||
state.setStateObject(so0)
|
state.setStateObject(so0)
|
||||||
|
|
||||||
root, _ := state.Commit(0, false)
|
state.RevertToSnapshot(snap)
|
||||||
state, _ = New(root, state.db, state.snaps)
|
if state.Exist(addr) {
|
||||||
|
t.Error("Unexpected account after revert")
|
||||||
// and one with deleted == true
|
|
||||||
so1 := state.getStateObject(stateobjaddr1)
|
|
||||||
so1.SetBalance(uint256.NewInt(52), tracing.BalanceChangeUnspecified)
|
|
||||||
so1.SetNonce(53)
|
|
||||||
so1.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e', '2'}), []byte{'c', 'a', 'f', 'e', '2'})
|
|
||||||
so1.selfDestructed = true
|
|
||||||
so1.deleted = true
|
|
||||||
state.setStateObject(so1)
|
|
||||||
|
|
||||||
so1 = state.getStateObject(stateobjaddr1)
|
|
||||||
if so1 != nil {
|
|
||||||
t.Fatalf("deleted object not nil when getting")
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshot := state.Snapshot()
|
|
||||||
state.RevertToSnapshot(snapshot)
|
|
||||||
|
|
||||||
so0Restored := state.getStateObject(stateobjaddr0)
|
|
||||||
// Update lazily-loaded values before comparing.
|
|
||||||
so0Restored.GetState(storageaddr)
|
|
||||||
so0Restored.Code()
|
|
||||||
// non-deleted is equal (restored)
|
|
||||||
compareStateObjects(so0Restored, so0, t)
|
|
||||||
|
|
||||||
// deleted should be nil, both before and after restore of state copy
|
|
||||||
so1Restored := state.getStateObject(stateobjaddr1)
|
|
||||||
if so1Restored != nil {
|
|
||||||
t.Fatalf("deleted object not nil after restoring snapshot: %+v", so1Restored)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func compareStateObjects(so0, so1 *stateObject, t *testing.T) {
|
|
||||||
if so0.Address() != so1.Address() {
|
|
||||||
t.Fatalf("Address mismatch: have %v, want %v", so0.address, so1.address)
|
|
||||||
}
|
|
||||||
if so0.Balance().Cmp(so1.Balance()) != 0 {
|
|
||||||
t.Fatalf("Balance mismatch: have %v, want %v", so0.Balance(), so1.Balance())
|
|
||||||
}
|
|
||||||
if so0.Nonce() != so1.Nonce() {
|
|
||||||
t.Fatalf("Nonce mismatch: have %v, want %v", so0.Nonce(), so1.Nonce())
|
|
||||||
}
|
|
||||||
if so0.data.Root != so1.data.Root {
|
|
||||||
t.Errorf("Root mismatch: have %x, want %x", so0.data.Root[:], so1.data.Root[:])
|
|
||||||
}
|
|
||||||
if !bytes.Equal(so0.CodeHash(), so1.CodeHash()) {
|
|
||||||
t.Fatalf("CodeHash mismatch: have %v, want %v", so0.CodeHash(), so1.CodeHash())
|
|
||||||
}
|
|
||||||
if !bytes.Equal(so0.code, so1.code) {
|
|
||||||
t.Fatalf("Code mismatch: have %v, want %v", so0.code, so1.code)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(so1.dirtyStorage) != len(so0.dirtyStorage) {
|
|
||||||
t.Errorf("Dirty storage size mismatch: have %d, want %d", len(so1.dirtyStorage), len(so0.dirtyStorage))
|
|
||||||
}
|
|
||||||
for k, v := range so1.dirtyStorage {
|
|
||||||
if so0.dirtyStorage[k] != v {
|
|
||||||
t.Errorf("Dirty storage key %x mismatch: have %v, want %v", k, so0.dirtyStorage[k], v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for k, v := range so0.dirtyStorage {
|
|
||||||
if so1.dirtyStorage[k] != v {
|
|
||||||
t.Errorf("Dirty storage key %x mismatch: have %v, want none.", k, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(so1.originStorage) != len(so0.originStorage) {
|
|
||||||
t.Errorf("Origin storage size mismatch: have %d, want %d", len(so1.originStorage), len(so0.originStorage))
|
|
||||||
}
|
|
||||||
for k, v := range so1.originStorage {
|
|
||||||
if so0.originStorage[k] != v {
|
|
||||||
t.Errorf("Origin storage key %x mismatch: have %v, want %v", k, so0.originStorage[k], v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for k, v := range so0.originStorage {
|
|
||||||
if so1.originStorage[k] != v {
|
|
||||||
t.Errorf("Origin storage key %x mismatch: have %v, want none.", k, v)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,26 @@ type revision struct {
|
||||||
journalIndex int
|
journalIndex int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mutationType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
update mutationType = iota
|
||||||
|
deletion
|
||||||
|
)
|
||||||
|
|
||||||
|
type mutation struct {
|
||||||
|
typ mutationType
|
||||||
|
applied bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mutation) copy() *mutation {
|
||||||
|
return &mutation{typ: m.typ, applied: m.applied}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mutation) isDelete() bool {
|
||||||
|
return m.typ == deletion
|
||||||
|
}
|
||||||
|
|
||||||
// StateDB structs within the ethereum protocol are used to store anything
|
// StateDB structs within the ethereum protocol are used to store anything
|
||||||
// within the merkle trie. StateDBs take care of caching and storing
|
// within the merkle trie. StateDBs take care of caching and storing
|
||||||
// nested states. It's the general query interface to retrieve:
|
// nested states. It's the general query interface to retrieve:
|
||||||
|
|
@ -75,12 +95,22 @@ type StateDB struct {
|
||||||
accountsOrigin map[common.Address][]byte // The original value of mutated accounts in 'slim RLP' encoding
|
accountsOrigin map[common.Address][]byte // The original value of mutated accounts in 'slim RLP' encoding
|
||||||
storagesOrigin map[common.Address]map[common.Hash][]byte // The original value of mutated slots in prefix-zero trimmed rlp format
|
storagesOrigin map[common.Address]map[common.Hash][]byte // The original value of mutated slots in prefix-zero trimmed rlp format
|
||||||
|
|
||||||
// This map holds 'live' objects, which will get modified while processing
|
// This map holds 'live' objects, which will get modified while
|
||||||
// a state transition.
|
// processing a state transition.
|
||||||
stateObjects map[common.Address]*stateObject
|
stateObjects map[common.Address]*stateObject
|
||||||
stateObjectsPending map[common.Address]struct{} // State objects finalized but not yet written to the trie
|
|
||||||
stateObjectsDirty map[common.Address]struct{} // State objects modified in the current execution
|
// This map holds 'deleted' objects. An object with the same address
|
||||||
stateObjectsDestruct map[common.Address]*types.StateAccount // State objects destructed in the block along with its previous value
|
// might also occur in the 'stateObjects' map due to account
|
||||||
|
// resurrection. The account value is tracked as the original value
|
||||||
|
// before the transition. This map is populated at the transaction
|
||||||
|
// boundaries.
|
||||||
|
stateObjectsDestruct map[common.Address]*types.StateAccount
|
||||||
|
|
||||||
|
// This map tracks the account mutations that occurred during the
|
||||||
|
// transition. Uncommitted mutations belonging to the same account
|
||||||
|
// can be merged into a single one which is equivalent from database's
|
||||||
|
// perspective. This map is populated at the transaction boundaries.
|
||||||
|
mutations map[common.Address]*mutation
|
||||||
|
|
||||||
// DB error.
|
// DB error.
|
||||||
// State objects are used by the consensus core and VM which are
|
// State objects are used by the consensus core and VM which are
|
||||||
|
|
@ -154,9 +184,8 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
|
||||||
accountsOrigin: make(map[common.Address][]byte),
|
accountsOrigin: make(map[common.Address][]byte),
|
||||||
storagesOrigin: make(map[common.Address]map[common.Hash][]byte),
|
storagesOrigin: make(map[common.Address]map[common.Hash][]byte),
|
||||||
stateObjects: make(map[common.Address]*stateObject),
|
stateObjects: make(map[common.Address]*stateObject),
|
||||||
stateObjectsPending: make(map[common.Address]struct{}),
|
|
||||||
stateObjectsDirty: make(map[common.Address]struct{}),
|
|
||||||
stateObjectsDestruct: make(map[common.Address]*types.StateAccount),
|
stateObjectsDestruct: make(map[common.Address]*types.StateAccount),
|
||||||
|
mutations: make(map[common.Address]*mutation),
|
||||||
logs: make(map[common.Hash][]*types.Log),
|
logs: make(map[common.Hash][]*types.Log),
|
||||||
preimages: make(map[common.Hash][]byte),
|
preimages: make(map[common.Hash][]byte),
|
||||||
journal: newJournal(),
|
journal: newJournal(),
|
||||||
|
|
@ -472,8 +501,7 @@ func (s *StateDB) Selfdestruct6780(addr common.Address) {
|
||||||
if stateObject == nil {
|
if stateObject == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if stateObject.newContract {
|
||||||
if stateObject.created {
|
|
||||||
s.SelfDestruct(addr)
|
s.SelfDestruct(addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -552,24 +580,16 @@ func (s *StateDB) deleteStateObject(addr common.Address) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// getStateObject retrieves a state object given by the address, returning nil if
|
// getStateObject retrieves a state object given by the address, returning nil if
|
||||||
// the object is not found or was deleted in this execution context. If you need
|
// the object is not found or was deleted in this execution context.
|
||||||
// to differentiate between non-existent/just-deleted, use getDeletedStateObject.
|
|
||||||
func (s *StateDB) getStateObject(addr common.Address) *stateObject {
|
func (s *StateDB) getStateObject(addr common.Address) *stateObject {
|
||||||
if obj := s.getDeletedStateObject(addr); obj != nil && !obj.deleted {
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getDeletedStateObject is similar to getStateObject, but instead of returning
|
|
||||||
// nil for a deleted state object, it returns the actual object with the deleted
|
|
||||||
// flag set. This is needed by the state journal to revert to the correct s-
|
|
||||||
// destructed object instead of wiping all knowledge about the state object.
|
|
||||||
func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
|
|
||||||
// Prefer live objects if any is available
|
// Prefer live objects if any is available
|
||||||
if obj := s.stateObjects[addr]; obj != nil {
|
if obj := s.stateObjects[addr]; obj != nil {
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
// Short circuit if the account is already destructed in this block.
|
||||||
|
if _, ok := s.stateObjectsDestruct[addr]; ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
// If no live objects are available, attempt to use snapshots
|
// If no live objects are available, attempt to use snapshots
|
||||||
var data *types.StateAccount
|
var data *types.StateAccount
|
||||||
if s.snap != nil {
|
if s.snap != nil {
|
||||||
|
|
@ -622,69 +642,40 @@ func (s *StateDB) setStateObject(object *stateObject) {
|
||||||
|
|
||||||
// getOrNewStateObject retrieves a state object or create a new state object if nil.
|
// getOrNewStateObject retrieves a state object or create a new state object if nil.
|
||||||
func (s *StateDB) getOrNewStateObject(addr common.Address) *stateObject {
|
func (s *StateDB) getOrNewStateObject(addr common.Address) *stateObject {
|
||||||
stateObject := s.getStateObject(addr)
|
obj := s.getStateObject(addr)
|
||||||
if stateObject == nil {
|
if obj == nil {
|
||||||
stateObject, _ = s.createObject(addr)
|
obj = s.createObject(addr)
|
||||||
}
|
}
|
||||||
return stateObject
|
return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
// createObject creates a new state object. If there is an existing account with
|
// createObject creates a new state object. The assumption is held there is no
|
||||||
// the given address, it is overwritten and returned as the second return value.
|
// existing account with the given address, otherwise it will be silently overwritten.
|
||||||
func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) {
|
func (s *StateDB) createObject(addr common.Address) *stateObject {
|
||||||
prev = s.getDeletedStateObject(addr) // Note, prev might have been deleted, we need that!
|
obj := newObject(s, addr, nil)
|
||||||
newobj = newObject(s, addr, nil)
|
|
||||||
if prev == nil {
|
|
||||||
s.journal.append(createObjectChange{account: &addr})
|
s.journal.append(createObjectChange{account: &addr})
|
||||||
} else {
|
s.setStateObject(obj)
|
||||||
// The original account should be marked as destructed and all cached
|
return obj
|
||||||
// account and storage data should be cleared as well. Note, it must
|
|
||||||
// be done here, otherwise the destruction event of "original account"
|
|
||||||
// will be lost.
|
|
||||||
_, prevdestruct := s.stateObjectsDestruct[prev.address]
|
|
||||||
if !prevdestruct {
|
|
||||||
s.stateObjectsDestruct[prev.address] = prev.origin
|
|
||||||
}
|
|
||||||
// There may be some cached account/storage data already since IntermediateRoot
|
|
||||||
// will be called for each transaction before byzantium fork which will always
|
|
||||||
// cache the latest account/storage data.
|
|
||||||
prevAccount, ok := s.accountsOrigin[prev.address]
|
|
||||||
s.journal.append(resetObjectChange{
|
|
||||||
account: &addr,
|
|
||||||
prev: prev,
|
|
||||||
prevdestruct: prevdestruct,
|
|
||||||
prevAccount: s.accounts[prev.addrHash],
|
|
||||||
prevStorage: s.storages[prev.addrHash],
|
|
||||||
prevAccountOriginExist: ok,
|
|
||||||
prevAccountOrigin: prevAccount,
|
|
||||||
prevStorageOrigin: s.storagesOrigin[prev.address],
|
|
||||||
})
|
|
||||||
delete(s.accounts, prev.addrHash)
|
|
||||||
delete(s.storages, prev.addrHash)
|
|
||||||
delete(s.accountsOrigin, prev.address)
|
|
||||||
delete(s.storagesOrigin, prev.address)
|
|
||||||
}
|
|
||||||
s.setStateObject(newobj)
|
|
||||||
if prev != nil && !prev.deleted {
|
|
||||||
return newobj, prev
|
|
||||||
}
|
|
||||||
return newobj, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateAccount explicitly creates a state object. If a state object with the address
|
// CreateAccount explicitly creates a new state object, assuming that the
|
||||||
// already exists the balance is carried over to the new account.
|
// account did not previously exist in the state. If the account already
|
||||||
//
|
// exists, this function will silently overwrite it which might lead to a
|
||||||
// CreateAccount is called during the EVM CREATE operation. The situation might arise that
|
// consensus bug eventually.
|
||||||
// a contract does the following:
|
|
||||||
//
|
|
||||||
// 1. sends funds to sha(account ++ (nonce + 1))
|
|
||||||
// 2. tx_create(sha(account ++ nonce)) (note that this gets the address of 1)
|
|
||||||
//
|
|
||||||
// Carrying over the balance ensures that Ether doesn't disappear.
|
|
||||||
func (s *StateDB) CreateAccount(addr common.Address) {
|
func (s *StateDB) CreateAccount(addr common.Address) {
|
||||||
newObj, prev := s.createObject(addr)
|
s.createObject(addr)
|
||||||
if prev != nil {
|
}
|
||||||
newObj.setBalance(prev.data.Balance)
|
|
||||||
|
// CreateContract is used whenever a contract is created. This may be preceded
|
||||||
|
// by CreateAccount, but that is not required if it already existed in the
|
||||||
|
// state due to funds sent beforehand.
|
||||||
|
// This operation sets the 'newContract'-flag, which is required in order to
|
||||||
|
// correctly handle EIP-6780 'delete-in-same-transaction' logic.
|
||||||
|
func (s *StateDB) CreateContract(addr common.Address) {
|
||||||
|
obj := s.getStateObject(addr)
|
||||||
|
if !obj.newContract {
|
||||||
|
obj.newContract = true
|
||||||
|
s.journal.append(createContractChange{account: addr})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -695,21 +686,25 @@ func (s *StateDB) Copy() *StateDB {
|
||||||
state := &StateDB{
|
state := &StateDB{
|
||||||
db: s.db,
|
db: s.db,
|
||||||
trie: s.db.CopyTrie(s.trie),
|
trie: s.db.CopyTrie(s.trie),
|
||||||
|
hasher: crypto.NewKeccakState(),
|
||||||
originalRoot: s.originalRoot,
|
originalRoot: s.originalRoot,
|
||||||
accounts: copySet(s.accounts),
|
accounts: copySet(s.accounts),
|
||||||
storages: copy2DSet(s.storages),
|
storages: copy2DSet(s.storages),
|
||||||
accountsOrigin: copySet(s.accountsOrigin),
|
accountsOrigin: copySet(s.accountsOrigin),
|
||||||
storagesOrigin: copy2DSet(s.storagesOrigin),
|
storagesOrigin: copy2DSet(s.storagesOrigin),
|
||||||
stateObjects: make(map[common.Address]*stateObject, len(s.journal.dirties)),
|
stateObjects: make(map[common.Address]*stateObject, len(s.stateObjects)),
|
||||||
stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)),
|
|
||||||
stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)),
|
|
||||||
stateObjectsDestruct: maps.Clone(s.stateObjectsDestruct),
|
stateObjectsDestruct: maps.Clone(s.stateObjectsDestruct),
|
||||||
|
mutations: make(map[common.Address]*mutation, len(s.mutations)),
|
||||||
|
dbErr: s.dbErr,
|
||||||
refund: s.refund,
|
refund: s.refund,
|
||||||
|
thash: s.thash,
|
||||||
|
txIndex: s.txIndex,
|
||||||
logs: make(map[common.Hash][]*types.Log, len(s.logs)),
|
logs: make(map[common.Hash][]*types.Log, len(s.logs)),
|
||||||
logSize: s.logSize,
|
logSize: s.logSize,
|
||||||
preimages: maps.Clone(s.preimages),
|
preimages: maps.Clone(s.preimages),
|
||||||
journal: newJournal(),
|
journal: s.journal.copy(),
|
||||||
hasher: crypto.NewKeccakState(),
|
validRevisions: slices.Clone(s.validRevisions),
|
||||||
|
nextRevisionId: s.nextRevisionId,
|
||||||
|
|
||||||
// In order for the block producer to be able to use and make additions
|
// In order for the block producer to be able to use and make additions
|
||||||
// to the snapshot tree, we need to copy that as well. Otherwise, any
|
// to the snapshot tree, we need to copy that as well. Otherwise, any
|
||||||
|
|
@ -718,39 +713,14 @@ func (s *StateDB) Copy() *StateDB {
|
||||||
snaps: s.snaps,
|
snaps: s.snaps,
|
||||||
snap: s.snap,
|
snap: s.snap,
|
||||||
}
|
}
|
||||||
// Copy the dirty states, logs, and preimages
|
// Deep copy cached state objects.
|
||||||
for addr := range s.journal.dirties {
|
for addr, obj := range s.stateObjects {
|
||||||
// As documented [here](https://github.com/ethereum/go-ethereum/pull/16485#issuecomment-380438527),
|
state.stateObjects[addr] = obj.deepCopy(state)
|
||||||
// and in the Finalise-method, there is a case where an object is in the journal but not
|
|
||||||
// in the stateObjects: OOG after touch on ripeMD prior to Byzantium. Thus, we need to check for
|
|
||||||
// nil
|
|
||||||
if object, exist := s.stateObjects[addr]; exist {
|
|
||||||
// Even though the original object is dirty, we are not copying the journal,
|
|
||||||
// so we need to make sure that any side-effect the journal would have caused
|
|
||||||
// during a commit (or similar op) is already applied to the copy.
|
|
||||||
state.stateObjects[addr] = object.deepCopy(state)
|
|
||||||
|
|
||||||
state.stateObjectsDirty[addr] = struct{}{} // Mark the copy dirty to force internal (code/state) commits
|
|
||||||
state.stateObjectsPending[addr] = struct{}{} // Mark the copy pending to force external (account) commits
|
|
||||||
}
|
}
|
||||||
|
// Deep copy the object state markers.
|
||||||
|
for addr, op := range s.mutations {
|
||||||
|
state.mutations[addr] = op.copy()
|
||||||
}
|
}
|
||||||
// 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{}{}
|
|
||||||
}
|
|
||||||
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 logs occurred in the scope of block
|
// Deep copy the logs occurred in the scope of block
|
||||||
for hash, logs := range s.logs {
|
for hash, logs := range s.logs {
|
||||||
cpy := make([]*types.Log, len(logs))
|
cpy := make([]*types.Log, len(logs))
|
||||||
|
|
@ -760,7 +730,6 @@ func (s *StateDB) Copy() *StateDB {
|
||||||
}
|
}
|
||||||
state.logs[hash] = cpy
|
state.logs[hash] = cpy
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do we need to copy the access list and transient storage?
|
// Do we need to copy the access list and transient storage?
|
||||||
// In practice: No. At the start of a transaction, these two lists are empty.
|
// In practice: No. At the start of a transaction, these two lists are empty.
|
||||||
// In practice, we only ever copy state _between_ transactions/blocks, never
|
// In practice, we only ever copy state _between_ transactions/blocks, never
|
||||||
|
|
@ -825,7 +794,8 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) {
|
if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) {
|
||||||
obj.deleted = true
|
delete(s.stateObjects, obj.address)
|
||||||
|
s.markDelete(addr)
|
||||||
|
|
||||||
// If ether was sent to account post-selfdestruct it is burnt.
|
// If ether was sent to account post-selfdestruct it is burnt.
|
||||||
if bal := obj.Balance(); s.logger != nil && s.logger.OnBalanceChange != nil && obj.selfDestructed && bal.Sign() != 0 {
|
if bal := obj.Balance(); s.logger != nil && s.logger.OnBalanceChange != nil && obj.selfDestructed && bal.Sign() != 0 {
|
||||||
|
|
@ -846,11 +816,8 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
|
||||||
delete(s.storagesOrigin, obj.address) // Clear out any previously updated storage data (may be recreated via a resurrect)
|
delete(s.storagesOrigin, obj.address) // Clear out any previously updated storage data (may be recreated via a resurrect)
|
||||||
} else {
|
} else {
|
||||||
obj.finalise(true) // Prefetch slots in the background
|
obj.finalise(true) // Prefetch slots in the background
|
||||||
|
s.markUpdate(addr)
|
||||||
}
|
}
|
||||||
obj.created = false
|
|
||||||
s.stateObjectsPending[addr] = struct{}{}
|
|
||||||
s.stateObjectsDirty[addr] = struct{}{}
|
|
||||||
|
|
||||||
// At this point, also ship the address off to the precacher. The precacher
|
// At this point, also ship the address off to the precacher. The precacher
|
||||||
// will start loading tries, and when the change is eventually committed,
|
// will start loading tries, and when the change is eventually committed,
|
||||||
// the commit-phase will be a lot faster
|
// the commit-phase will be a lot faster
|
||||||
|
|
@ -889,10 +856,14 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
||||||
// the account prefetcher. Instead, let's process all the storage updates
|
// the account prefetcher. Instead, let's process all the storage updates
|
||||||
// first, giving the account prefetches just a few more milliseconds of time
|
// first, giving the account prefetches just a few more milliseconds of time
|
||||||
// to pull useful data from disk.
|
// to pull useful data from disk.
|
||||||
for addr := range s.stateObjectsPending {
|
for addr, op := range s.mutations {
|
||||||
if obj := s.stateObjects[addr]; !obj.deleted {
|
if op.applied {
|
||||||
obj.updateRoot()
|
continue
|
||||||
}
|
}
|
||||||
|
if op.isDelete() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s.stateObjects[addr].updateRoot()
|
||||||
}
|
}
|
||||||
// Now we're about to start to write changes to the trie. The trie is so far
|
// Now we're about to start to write changes to the trie. The trie is so far
|
||||||
// _untouched_. We can check with the prefetcher, if it can give us a trie
|
// _untouched_. We can check with the prefetcher, if it can give us a trie
|
||||||
|
|
@ -902,7 +873,6 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
||||||
s.trie = trie
|
s.trie = trie
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
usedAddrs := make([][]byte, 0, len(s.stateObjectsPending))
|
|
||||||
// Perform updates before deletions. This prevents resolution of unnecessary trie nodes
|
// Perform updates before deletions. This prevents resolution of unnecessary trie nodes
|
||||||
// in circumstances similar to the following:
|
// in circumstances similar to the following:
|
||||||
//
|
//
|
||||||
|
|
@ -913,13 +883,21 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
||||||
// If the self-destruct is handled first, then `P` would be left with only one child, thus collapsed
|
// 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.
|
// 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.
|
// Whereas if the created node is handled first, then the collapse is avoided, and `B` is not resolved.
|
||||||
var deletedAddrs []common.Address
|
var (
|
||||||
for addr := range s.stateObjectsPending {
|
usedAddrs [][]byte
|
||||||
if obj := s.stateObjects[addr]; !obj.deleted {
|
deletedAddrs []common.Address
|
||||||
s.updateStateObject(obj)
|
)
|
||||||
s.AccountUpdated += 1
|
for addr, op := range s.mutations {
|
||||||
|
if op.applied {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
op.applied = true
|
||||||
|
|
||||||
|
if op.isDelete() {
|
||||||
|
deletedAddrs = append(deletedAddrs, addr)
|
||||||
} else {
|
} else {
|
||||||
deletedAddrs = append(deletedAddrs, obj.address)
|
s.updateStateObject(s.stateObjects[addr])
|
||||||
|
s.AccountUpdated += 1
|
||||||
}
|
}
|
||||||
usedAddrs = append(usedAddrs, common.CopyBytes(addr[:])) // Copy needed for closure
|
usedAddrs = append(usedAddrs, common.CopyBytes(addr[:])) // Copy needed for closure
|
||||||
}
|
}
|
||||||
|
|
@ -930,9 +908,6 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
||||||
if prefetcher != nil {
|
if prefetcher != nil {
|
||||||
prefetcher.used(common.Hash{}, s.originalRoot, usedAddrs)
|
prefetcher.used(common.Hash{}, s.originalRoot, usedAddrs)
|
||||||
}
|
}
|
||||||
if len(s.stateObjectsPending) > 0 {
|
|
||||||
s.stateObjectsPending = make(map[common.Address]struct{})
|
|
||||||
}
|
|
||||||
// Track the amount of time wasted on hashing the account trie
|
// Track the amount of time wasted on hashing the account trie
|
||||||
defer func(start time.Time) { s.AccountHashes += time.Since(start) }(time.Now())
|
defer func(start time.Time) { s.AccountHashes += time.Since(start) }(time.Now())
|
||||||
|
|
||||||
|
|
@ -1176,11 +1151,12 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
|
||||||
return common.Hash{}, err
|
return common.Hash{}, err
|
||||||
}
|
}
|
||||||
// Handle all state updates afterwards
|
// Handle all state updates afterwards
|
||||||
for addr := range s.stateObjectsDirty {
|
for addr, op := range s.mutations {
|
||||||
obj := s.stateObjects[addr]
|
if op.isDelete() {
|
||||||
if obj.deleted {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
obj := s.stateObjects[addr]
|
||||||
|
|
||||||
// Write any contract code associated with the state object
|
// Write any contract code associated with the state object
|
||||||
if obj.code != nil && obj.dirtyCode {
|
if obj.code != nil && obj.dirtyCode {
|
||||||
rawdb.WriteCode(codeWriter, common.BytesToHash(obj.CodeHash()), obj.code)
|
rawdb.WriteCode(codeWriter, common.BytesToHash(obj.CodeHash()), obj.code)
|
||||||
|
|
@ -1280,7 +1256,7 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
|
||||||
s.storages = make(map[common.Hash]map[common.Hash][]byte)
|
s.storages = make(map[common.Hash]map[common.Hash][]byte)
|
||||||
s.accountsOrigin = make(map[common.Address][]byte)
|
s.accountsOrigin = make(map[common.Address][]byte)
|
||||||
s.storagesOrigin = make(map[common.Address]map[common.Hash][]byte)
|
s.storagesOrigin = make(map[common.Address]map[common.Hash][]byte)
|
||||||
s.stateObjectsDirty = make(map[common.Address]struct{})
|
s.mutations = make(map[common.Address]*mutation)
|
||||||
s.stateObjectsDestruct = make(map[common.Address]*types.StateAccount)
|
s.stateObjectsDestruct = make(map[common.Address]*types.StateAccount)
|
||||||
return root, nil
|
return root, nil
|
||||||
}
|
}
|
||||||
|
|
@ -1395,3 +1371,19 @@ func copy2DSet[k comparable](set map[k]map[common.Hash][]byte) map[k]map[common.
|
||||||
}
|
}
|
||||||
return copied
|
return copied
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *StateDB) markDelete(addr common.Address) {
|
||||||
|
if _, ok := s.mutations[addr]; !ok {
|
||||||
|
s.mutations[addr] = &mutation{}
|
||||||
|
}
|
||||||
|
s.mutations[addr].applied = false
|
||||||
|
s.mutations[addr].typ = deletion
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StateDB) markUpdate(addr common.Address) {
|
||||||
|
if _, ok := s.mutations[addr]; !ok {
|
||||||
|
s.mutations[addr] = &mutation{}
|
||||||
|
}
|
||||||
|
s.mutations[addr].applied = false
|
||||||
|
s.mutations[addr].typ = update
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,9 @@ func newStateTestAction(addr common.Address, r *rand.Rand, index int) testAction
|
||||||
{
|
{
|
||||||
name: "CreateAccount",
|
name: "CreateAccount",
|
||||||
fn: func(a testAction, s *StateDB) {
|
fn: func(a testAction, s *StateDB) {
|
||||||
|
if !s.Exist(addr) {
|
||||||
s.CreateAccount(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) {
|
func TestSnapshotRandom(t *testing.T) {
|
||||||
config := &quick.Config{MaxCount: 1000}
|
config := &quick.Config{MaxCount: 1000}
|
||||||
err := quick.Check((*snapshotTest).run, config)
|
err := quick.Check((*snapshotTest).run, config)
|
||||||
|
|
@ -308,7 +380,30 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction {
|
||||||
{
|
{
|
||||||
name: "CreateAccount",
|
name: "CreateAccount",
|
||||||
fn: func(a testAction, s *StateDB) {
|
fn: func(a testAction, s *StateDB) {
|
||||||
|
if !s.Exist(addr) {
|
||||||
s.CreateAccount(addr)
|
s.CreateAccount(addr)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "CreateContract",
|
||||||
|
fn: func(a testAction, s *StateDB) {
|
||||||
|
if !s.Exist(addr) {
|
||||||
|
s.CreateAccount(addr)
|
||||||
|
}
|
||||||
|
contractHash := s.GetCodeHash(addr)
|
||||||
|
emptyCode := contractHash == (common.Hash{}) || contractHash == types.EmptyCodeHash
|
||||||
|
storageRoot := s.GetStorageRoot(addr)
|
||||||
|
emptyStorage := storageRoot == (common.Hash{}) || storageRoot == types.EmptyRootHash
|
||||||
|
if s.GetNonce(addr) == 0 && emptyCode && emptyStorage {
|
||||||
|
s.CreateContract(addr)
|
||||||
|
// We also set some code here, to prevent the
|
||||||
|
// CreateContract action from being performed twice in a row,
|
||||||
|
// which would cause a difference in state when unrolling
|
||||||
|
// the journal. (CreateContact assumes created was false prior to
|
||||||
|
// invocation, and the journal rollback sets it to false).
|
||||||
|
s.SetCode(addr, []byte{1})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -709,18 +804,19 @@ func TestCopyCopyCommitCopy(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestCommitCopy tests the copy from a committed state is not functional.
|
// TestCommitCopy tests the copy from a committed state is not fully functional.
|
||||||
func TestCommitCopy(t *testing.T) {
|
func TestCommitCopy(t *testing.T) {
|
||||||
state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
db := NewDatabase(rawdb.NewMemoryDatabase())
|
||||||
|
state, _ := New(types.EmptyRootHash, db, nil)
|
||||||
|
|
||||||
// Create an account and check if the retrieved balance is correct
|
// Create an account and check if the retrieved balance is correct
|
||||||
addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe")
|
addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe")
|
||||||
skey := common.HexToHash("aaa")
|
skey1, skey2 := common.HexToHash("a1"), common.HexToHash("a2")
|
||||||
sval := common.HexToHash("bbb")
|
sval1, sval2 := common.HexToHash("b1"), common.HexToHash("b2")
|
||||||
|
|
||||||
state.SetBalance(addr, uint256.NewInt(42), tracing.BalanceChangeUnspecified) // Change the account trie
|
state.SetBalance(addr, uint256.NewInt(42), tracing.BalanceChangeUnspecified) // Change the account trie
|
||||||
state.SetCode(addr, []byte("hello")) // Change an external metadata
|
state.SetCode(addr, []byte("hello")) // Change an external metadata
|
||||||
state.SetState(addr, skey, sval) // Change the storage trie
|
state.SetState(addr, skey1, sval1) // Change the storage trie
|
||||||
|
|
||||||
if balance := state.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 {
|
if balance := state.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 {
|
||||||
t.Fatalf("initial balance mismatch: have %v, want %v", balance, 42)
|
t.Fatalf("initial balance mismatch: have %v, want %v", balance, 42)
|
||||||
|
|
@ -728,25 +824,38 @@ func TestCommitCopy(t *testing.T) {
|
||||||
if code := state.GetCode(addr); !bytes.Equal(code, []byte("hello")) {
|
if code := state.GetCode(addr); !bytes.Equal(code, []byte("hello")) {
|
||||||
t.Fatalf("initial code mismatch: have %x, want %x", code, []byte("hello"))
|
t.Fatalf("initial code mismatch: have %x, want %x", code, []byte("hello"))
|
||||||
}
|
}
|
||||||
if val := state.GetState(addr, skey); val != sval {
|
if val := state.GetState(addr, skey1); val != sval1 {
|
||||||
t.Fatalf("initial non-committed storage slot mismatch: have %x, want %x", val, sval)
|
t.Fatalf("initial non-committed storage slot mismatch: have %x, want %x", val, sval1)
|
||||||
}
|
}
|
||||||
if val := state.GetCommittedState(addr, skey); val != (common.Hash{}) {
|
if val := state.GetCommittedState(addr, skey1); val != (common.Hash{}) {
|
||||||
t.Fatalf("initial committed storage slot mismatch: have %x, want %x", val, common.Hash{})
|
t.Fatalf("initial committed storage slot mismatch: have %x, want %x", val, common.Hash{})
|
||||||
}
|
}
|
||||||
// Copy the committed state database, the copied one is not functional.
|
root, _ := state.Commit(0, true)
|
||||||
state.Commit(0, true)
|
|
||||||
|
state, _ = New(root, db, nil)
|
||||||
|
state.SetState(addr, skey2, sval2)
|
||||||
|
state.Commit(1, true)
|
||||||
|
|
||||||
|
// Copy the committed state database, the copied one is not fully functional.
|
||||||
copied := state.Copy()
|
copied := state.Copy()
|
||||||
if balance := copied.GetBalance(addr); balance.Cmp(uint256.NewInt(0)) != 0 {
|
if balance := copied.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 {
|
||||||
t.Fatalf("unexpected balance: have %v", balance)
|
t.Fatalf("unexpected balance: have %v", balance)
|
||||||
}
|
}
|
||||||
if code := copied.GetCode(addr); code != nil {
|
if code := copied.GetCode(addr); !bytes.Equal(code, []byte("hello")) {
|
||||||
t.Fatalf("unexpected code: have %x", code)
|
t.Fatalf("unexpected code: have %x", code)
|
||||||
}
|
}
|
||||||
if val := copied.GetState(addr, skey); val != (common.Hash{}) {
|
// Miss slots because of non-functional trie after commit
|
||||||
|
if val := copied.GetState(addr, skey1); val != (common.Hash{}) {
|
||||||
|
t.Fatalf("unexpected storage slot: have %x", sval1)
|
||||||
|
}
|
||||||
|
if val := copied.GetCommittedState(addr, skey1); val != (common.Hash{}) {
|
||||||
t.Fatalf("unexpected storage slot: have %x", val)
|
t.Fatalf("unexpected storage slot: have %x", val)
|
||||||
}
|
}
|
||||||
if val := copied.GetCommittedState(addr, skey); val != (common.Hash{}) {
|
// Slots cached in the stateDB, available after commit
|
||||||
|
if val := copied.GetState(addr, skey2); val != sval2 {
|
||||||
|
t.Fatalf("unexpected storage slot: have %x", sval1)
|
||||||
|
}
|
||||||
|
if val := copied.GetCommittedState(addr, skey2); val != sval2 {
|
||||||
t.Fatalf("unexpected storage slot: have %x", val)
|
t.Fatalf("unexpected storage slot: have %x", val)
|
||||||
}
|
}
|
||||||
if !errors.Is(copied.Error(), trie.ErrCommitted) {
|
if !errors.Is(copied.Error(), trie.ErrCommitted) {
|
||||||
|
|
@ -1103,40 +1212,6 @@ func TestStateDBTransientStorage(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResetObject(t *testing.T) {
|
|
||||||
var (
|
|
||||||
disk = rawdb.NewMemoryDatabase()
|
|
||||||
tdb = triedb.NewDatabase(disk, nil)
|
|
||||||
db = NewDatabaseWithNodeDB(disk, tdb)
|
|
||||||
snaps, _ = snapshot.New(snapshot.Config{CacheSize: 10}, disk, tdb, types.EmptyRootHash)
|
|
||||||
state, _ = New(types.EmptyRootHash, db, snaps)
|
|
||||||
addr = common.HexToAddress("0x1")
|
|
||||||
slotA = common.HexToHash("0x1")
|
|
||||||
slotB = common.HexToHash("0x2")
|
|
||||||
)
|
|
||||||
// Initialize account with balance and storage in first transaction.
|
|
||||||
state.SetBalance(addr, uint256.NewInt(1), tracing.BalanceChangeUnspecified)
|
|
||||||
state.SetState(addr, slotA, common.BytesToHash([]byte{0x1}))
|
|
||||||
state.IntermediateRoot(true)
|
|
||||||
|
|
||||||
// Reset account and mutate balance and storages
|
|
||||||
state.CreateAccount(addr)
|
|
||||||
state.SetBalance(addr, uint256.NewInt(2), tracing.BalanceChangeUnspecified)
|
|
||||||
state.SetState(addr, slotB, common.BytesToHash([]byte{0x2}))
|
|
||||||
root, _ := state.Commit(0, true)
|
|
||||||
|
|
||||||
// Ensure the original account is wiped properly
|
|
||||||
snap := snaps.Snapshot(root)
|
|
||||||
slot, _ := snap.Storage(crypto.Keccak256Hash(addr.Bytes()), crypto.Keccak256Hash(slotA.Bytes()))
|
|
||||||
if len(slot) != 0 {
|
|
||||||
t.Fatalf("Unexpected storage slot")
|
|
||||||
}
|
|
||||||
slot, _ = snap.Storage(crypto.Keccak256Hash(addr.Bytes()), crypto.Keccak256Hash(slotB.Bytes()))
|
|
||||||
if !bytes.Equal(slot, []byte{0x2}) {
|
|
||||||
t.Fatalf("Unexpected storage slot value %v", slot)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDeleteStorage(t *testing.T) {
|
func TestDeleteStorage(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
disk = rawdb.NewMemoryDatabase()
|
disk = rawdb.NewMemoryDatabase()
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,9 @@ type (
|
||||||
// BlockchainInitHook is called when the blockchain is initialized.
|
// BlockchainInitHook is called when the blockchain is initialized.
|
||||||
BlockchainInitHook = func(chainConfig *params.ChainConfig)
|
BlockchainInitHook = func(chainConfig *params.ChainConfig)
|
||||||
|
|
||||||
|
// CloseHook is called when the blockchain closes.
|
||||||
|
CloseHook = func()
|
||||||
|
|
||||||
// BlockStartHook is called before executing `block`.
|
// BlockStartHook is called before executing `block`.
|
||||||
// `td` is the total difficulty prior to `block`.
|
// `td` is the total difficulty prior to `block`.
|
||||||
BlockStartHook = func(event BlockEvent)
|
BlockStartHook = func(event BlockEvent)
|
||||||
|
|
@ -153,6 +156,7 @@ type Hooks struct {
|
||||||
OnGasChange GasChangeHook
|
OnGasChange GasChangeHook
|
||||||
// Chain events
|
// Chain events
|
||||||
OnBlockchainInit BlockchainInitHook
|
OnBlockchainInit BlockchainInitHook
|
||||||
|
OnClose CloseHook
|
||||||
OnBlockStart BlockStartHook
|
OnBlockStart BlockStartHook
|
||||||
OnBlockEnd BlockEndHook
|
OnBlockEnd BlockEndHook
|
||||||
OnSkippedBlock SkippedBlockHook
|
OnSkippedBlock SkippedBlockHook
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,16 @@ var PrecompiledContractsCancun = map[common.Address]PrecompiledContract{
|
||||||
// PrecompiledContractsPrague contains the set of pre-compiled Ethereum
|
// PrecompiledContractsPrague contains the set of pre-compiled Ethereum
|
||||||
// contracts used in the Prague release.
|
// contracts used in the Prague release.
|
||||||
var PrecompiledContractsPrague = map[common.Address]PrecompiledContract{
|
var PrecompiledContractsPrague = map[common.Address]PrecompiledContract{
|
||||||
|
common.BytesToAddress([]byte{0x01}): &ecrecover{},
|
||||||
|
common.BytesToAddress([]byte{0x02}): &sha256hash{},
|
||||||
|
common.BytesToAddress([]byte{0x03}): &ripemd160hash{},
|
||||||
|
common.BytesToAddress([]byte{0x04}): &dataCopy{},
|
||||||
|
common.BytesToAddress([]byte{0x05}): &bigModExp{eip2565: true},
|
||||||
|
common.BytesToAddress([]byte{0x06}): &bn256AddIstanbul{},
|
||||||
|
common.BytesToAddress([]byte{0x07}): &bn256ScalarMulIstanbul{},
|
||||||
|
common.BytesToAddress([]byte{0x08}): &bn256PairingIstanbul{},
|
||||||
|
common.BytesToAddress([]byte{0x09}): &blake2F{},
|
||||||
|
common.BytesToAddress([]byte{0x0a}): &kzgPointEvaluation{},
|
||||||
common.BytesToAddress([]byte{0x0b}): &bls12381G1Add{},
|
common.BytesToAddress([]byte{0x0b}): &bls12381G1Add{},
|
||||||
common.BytesToAddress([]byte{0x0c}): &bls12381G1Mul{},
|
common.BytesToAddress([]byte{0x0c}): &bls12381G1Mul{},
|
||||||
common.BytesToAddress([]byte{0x0d}): &bls12381G1MultiExp{},
|
common.BytesToAddress([]byte{0x0d}): &bls12381G1MultiExp{},
|
||||||
|
|
|
||||||
|
|
@ -436,14 +436,15 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
||||||
return nil, common.Address{}, gas, ErrNonceUintOverflow
|
return nil, common.Address{}, gas, ErrNonceUintOverflow
|
||||||
}
|
}
|
||||||
evm.StateDB.SetNonce(caller.Address(), nonce+1)
|
evm.StateDB.SetNonce(caller.Address(), nonce+1)
|
||||||
// We add this to the access list _before_ taking a snapshot. Even if the creation fails,
|
|
||||||
// the access-list change should not be rolled back
|
// We add this to the access list _before_ taking a snapshot. Even if the
|
||||||
|
// creation fails, the access-list change should not be rolled back.
|
||||||
if evm.chainRules.IsBerlin {
|
if evm.chainRules.IsBerlin {
|
||||||
evm.StateDB.AddAddressToAccessList(address)
|
evm.StateDB.AddAddressToAccessList(address)
|
||||||
}
|
}
|
||||||
// Ensure there's no existing contract already at the designated address.
|
// Ensure there's no existing contract already at the designated address.
|
||||||
// Account is regarded as existent if any of these three conditions is met:
|
// Account is regarded as existent if any of these three conditions is met:
|
||||||
// - the nonce is nonzero
|
// - the nonce is non-zero
|
||||||
// - the code is non-empty
|
// - the code is non-empty
|
||||||
// - the storage is non-empty
|
// - the storage is non-empty
|
||||||
contractHash := evm.StateDB.GetCodeHash(address)
|
contractHash := evm.StateDB.GetCodeHash(address)
|
||||||
|
|
@ -456,9 +457,19 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
||||||
}
|
}
|
||||||
return nil, common.Address{}, 0, ErrContractAddressCollision
|
return nil, common.Address{}, 0, ErrContractAddressCollision
|
||||||
}
|
}
|
||||||
// Create a new account on the state
|
// Create a new account on the state only if the object was not present.
|
||||||
|
// It might be possible the contract code is deployed to a pre-existent
|
||||||
|
// account with non-zero balance.
|
||||||
snapshot := evm.StateDB.Snapshot()
|
snapshot := evm.StateDB.Snapshot()
|
||||||
|
if !evm.StateDB.Exist(address) {
|
||||||
evm.StateDB.CreateAccount(address)
|
evm.StateDB.CreateAccount(address)
|
||||||
|
}
|
||||||
|
// CreateContract means that regardless of whether the account previously existed
|
||||||
|
// in the state trie or not, it _now_ becomes created as a _contract_ account.
|
||||||
|
// This is performed _prior_ to executing the initcode, since the initcode
|
||||||
|
// acts inside that account.
|
||||||
|
evm.StateDB.CreateContract(address)
|
||||||
|
|
||||||
if evm.chainRules.IsEIP158 {
|
if evm.chainRules.IsEIP158 {
|
||||||
evm.StateDB.SetNonce(address, 1)
|
evm.StateDB.SetNonce(address, 1)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import (
|
||||||
// StateDB is an EVM database for full state querying.
|
// StateDB is an EVM database for full state querying.
|
||||||
type StateDB interface {
|
type StateDB interface {
|
||||||
CreateAccount(common.Address)
|
CreateAccount(common.Address)
|
||||||
|
CreateContract(common.Address)
|
||||||
|
|
||||||
SubBalance(common.Address, *uint256.Int, tracing.BalanceChangeReason)
|
SubBalance(common.Address, *uint256.Int, tracing.BalanceChangeReason)
|
||||||
AddBalance(common.Address, *uint256.Int, tracing.BalanceChangeReason)
|
AddBalance(common.Address, *uint256.Int, tracing.BalanceChangeReason)
|
||||||
|
|
|
||||||
|
|
@ -203,8 +203,8 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||||
)
|
)
|
||||||
if config.VMTrace != "" {
|
if config.VMTrace != "" {
|
||||||
var traceConfig json.RawMessage
|
var traceConfig json.RawMessage
|
||||||
if config.VMTraceConfig != "" {
|
if config.VMTraceJsonConfig != "" {
|
||||||
traceConfig = json.RawMessage(config.VMTraceConfig)
|
traceConfig = json.RawMessage(config.VMTraceJsonConfig)
|
||||||
}
|
}
|
||||||
t, err := tracers.LiveDirectory.New(config.VMTrace, traceConfig)
|
t, err := tracers.LiveDirectory.New(config.VMTrace, traceConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ func TestEth2AssembleBlock(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error signing transaction, err=%v", err)
|
t.Fatalf("error signing transaction, err=%v", err)
|
||||||
}
|
}
|
||||||
ethservice.TxPool().Add([]*types.Transaction{tx}, true, false)
|
ethservice.TxPool().Add([]*types.Transaction{tx}, true, true)
|
||||||
blockParams := engine.PayloadAttributes{
|
blockParams := engine.PayloadAttributes{
|
||||||
Timestamp: blocks[9].Time() + 5,
|
Timestamp: blocks[9].Time() + 5,
|
||||||
}
|
}
|
||||||
|
|
@ -189,7 +189,7 @@ func TestEth2PrepareAndGetPayload(t *testing.T) {
|
||||||
|
|
||||||
// Put the 10th block's tx in the pool and produce a new block
|
// Put the 10th block's tx in the pool and produce a new block
|
||||||
txs := blocks[9].Transactions()
|
txs := blocks[9].Transactions()
|
||||||
ethservice.TxPool().Add(txs, true, false)
|
ethservice.TxPool().Add(txs, true, true)
|
||||||
blockParams := engine.PayloadAttributes{
|
blockParams := engine.PayloadAttributes{
|
||||||
Timestamp: blocks[8].Time() + 5,
|
Timestamp: blocks[8].Time() + 5,
|
||||||
}
|
}
|
||||||
|
|
@ -310,13 +310,13 @@ func TestEth2NewBlock(t *testing.T) {
|
||||||
statedb, _ := ethservice.BlockChain().StateAt(parent.Root())
|
statedb, _ := ethservice.BlockChain().StateAt(parent.Root())
|
||||||
nonce := statedb.GetNonce(testAddr)
|
nonce := statedb.GetNonce(testAddr)
|
||||||
tx, _ := types.SignTx(types.NewContractCreation(nonce, new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey)
|
tx, _ := types.SignTx(types.NewContractCreation(nonce, new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey)
|
||||||
ethservice.TxPool().Add([]*types.Transaction{tx}, true, false)
|
ethservice.TxPool().Add([]*types.Transaction{tx}, true, true)
|
||||||
|
|
||||||
execData, err := assembleWithTransactions(api, parent.Hash(), &engine.PayloadAttributes{
|
execData, err := assembleWithTransactions(api, parent.Hash(), &engine.PayloadAttributes{
|
||||||
Timestamp: parent.Time() + 5,
|
Timestamp: parent.Time() + 5,
|
||||||
}, 1)
|
}, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create the executable data %v", err)
|
t.Fatalf("Failed to create the executable data, block %d: %v", i, err)
|
||||||
}
|
}
|
||||||
block, err := engine.ExecutableDataToBlock(*execData, nil, nil)
|
block, err := engine.ExecutableDataToBlock(*execData, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -1311,3 +1311,84 @@ func testBeaconSync(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tests that synchronisation progress (origin block number and highest block
|
||||||
|
// number) is tracked and updated correctly in case of manual head reversion
|
||||||
|
func TestBeaconForkedSyncProgress68Full(t *testing.T) {
|
||||||
|
testBeaconForkedSyncProgress(t, eth.ETH68, FullSync)
|
||||||
|
}
|
||||||
|
func TestBeaconForkedSyncProgress68Snap(t *testing.T) {
|
||||||
|
testBeaconForkedSyncProgress(t, eth.ETH68, SnapSync)
|
||||||
|
}
|
||||||
|
func TestBeaconForkedSyncProgress68Light(t *testing.T) {
|
||||||
|
testBeaconForkedSyncProgress(t, eth.ETH68, LightSync)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBeaconForkedSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
|
||||||
|
success := make(chan struct{})
|
||||||
|
tester := newTesterWithNotification(t, func() {
|
||||||
|
success <- struct{}{}
|
||||||
|
})
|
||||||
|
defer tester.terminate()
|
||||||
|
|
||||||
|
chainA := testChainForkLightA.shorten(len(testChainBase.blocks) + MaxHeaderFetch)
|
||||||
|
chainB := testChainForkLightB.shorten(len(testChainBase.blocks) + MaxHeaderFetch)
|
||||||
|
|
||||||
|
// Set a sync init hook to catch progress changes
|
||||||
|
starting := make(chan struct{})
|
||||||
|
progress := make(chan struct{})
|
||||||
|
|
||||||
|
tester.downloader.syncInitHook = func(origin, latest uint64) {
|
||||||
|
starting <- struct{}{}
|
||||||
|
<-progress
|
||||||
|
}
|
||||||
|
checkProgress(t, tester.downloader, "pristine", ethereum.SyncProgress{})
|
||||||
|
|
||||||
|
// Synchronise with one of the forks and check progress
|
||||||
|
tester.newPeer("fork A", protocol, chainA.blocks[1:])
|
||||||
|
pending := new(sync.WaitGroup)
|
||||||
|
pending.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer pending.Done()
|
||||||
|
if err := tester.downloader.BeaconSync(mode, chainA.blocks[len(chainA.blocks)-1].Header(), nil); err != nil {
|
||||||
|
panic(fmt.Sprintf("failed to beacon sync: %v", err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
<-starting
|
||||||
|
progress <- struct{}{}
|
||||||
|
select {
|
||||||
|
case <-success:
|
||||||
|
checkProgress(t, tester.downloader, "initial", ethereum.SyncProgress{
|
||||||
|
HighestBlock: uint64(len(chainA.blocks) - 1),
|
||||||
|
CurrentBlock: uint64(len(chainA.blocks) - 1),
|
||||||
|
})
|
||||||
|
case <-time.NewTimer(time.Second * 3).C:
|
||||||
|
t.Fatalf("Failed to sync chain in three seconds")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the head to a second fork
|
||||||
|
tester.newPeer("fork B", protocol, chainB.blocks[1:])
|
||||||
|
pending.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer pending.Done()
|
||||||
|
if err := tester.downloader.BeaconSync(mode, chainB.blocks[len(chainB.blocks)-1].Header(), nil); err != nil {
|
||||||
|
panic(fmt.Sprintf("failed to beacon sync: %v", err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
<-starting
|
||||||
|
progress <- struct{}{}
|
||||||
|
|
||||||
|
// reorg below available state causes the state sync to rewind to genesis
|
||||||
|
select {
|
||||||
|
case <-success:
|
||||||
|
checkProgress(t, tester.downloader, "initial", ethereum.SyncProgress{
|
||||||
|
HighestBlock: uint64(len(chainB.blocks) - 1),
|
||||||
|
CurrentBlock: uint64(len(chainB.blocks) - 1),
|
||||||
|
StartingBlock: 0,
|
||||||
|
})
|
||||||
|
case <-time.NewTimer(time.Second * 3).C:
|
||||||
|
t.Fatalf("Failed to sync chain in three seconds")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1132,6 +1132,16 @@ func (s *skeleton) cleanStales(filled *types.Header) error {
|
||||||
if number+1 == s.progress.Subchains[0].Tail {
|
if number+1 == s.progress.Subchains[0].Tail {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
// If the latest fill was on a different subchain, it means the backfiller
|
||||||
|
// was interrupted before it got to do any meaningful work, no cleanup
|
||||||
|
header := rawdb.ReadSkeletonHeader(s.db, filled.Number.Uint64())
|
||||||
|
if header == nil {
|
||||||
|
log.Debug("Filled header outside of skeleton range", "number", number, "head", s.progress.Subchains[0].Head, "tail", s.progress.Subchains[0].Tail)
|
||||||
|
return nil
|
||||||
|
} else if header.Hash() != filled.Hash() {
|
||||||
|
log.Debug("Filled header on different sidechain", "number", number, "filled", filled.Hash(), "skeleton", header.Hash())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
var (
|
var (
|
||||||
start uint64
|
start uint64
|
||||||
end uint64
|
end uint64
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,7 @@ type Config struct {
|
||||||
|
|
||||||
// Enables VM tracing
|
// Enables VM tracing
|
||||||
VMTrace string
|
VMTrace string
|
||||||
VMTraceConfig string
|
VMTraceJsonConfig string
|
||||||
|
|
||||||
// Miscellaneous options
|
// Miscellaneous options
|
||||||
DocRoot string `toml:"-"`
|
DocRoot string `toml:"-"`
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||||
GPO gasprice.Config
|
GPO gasprice.Config
|
||||||
EnablePreimageRecording bool
|
EnablePreimageRecording bool
|
||||||
VMTrace string
|
VMTrace string
|
||||||
VMTraceConfig string
|
VMTraceJsonConfig string
|
||||||
DocRoot string `toml:"-"`
|
DocRoot string `toml:"-"`
|
||||||
RPCGasCap uint64
|
RPCGasCap uint64
|
||||||
RPCEVMTimeout time.Duration
|
RPCEVMTimeout time.Duration
|
||||||
|
|
@ -94,7 +94,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||||
enc.GPO = c.GPO
|
enc.GPO = c.GPO
|
||||||
enc.EnablePreimageRecording = c.EnablePreimageRecording
|
enc.EnablePreimageRecording = c.EnablePreimageRecording
|
||||||
enc.VMTrace = c.VMTrace
|
enc.VMTrace = c.VMTrace
|
||||||
enc.VMTraceConfig = c.VMTraceConfig
|
enc.VMTraceJsonConfig = c.VMTraceJsonConfig
|
||||||
enc.DocRoot = c.DocRoot
|
enc.DocRoot = c.DocRoot
|
||||||
enc.RPCGasCap = c.RPCGasCap
|
enc.RPCGasCap = c.RPCGasCap
|
||||||
enc.RPCEVMTimeout = c.RPCEVMTimeout
|
enc.RPCEVMTimeout = c.RPCEVMTimeout
|
||||||
|
|
@ -141,7 +141,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||||
GPO *gasprice.Config
|
GPO *gasprice.Config
|
||||||
EnablePreimageRecording *bool
|
EnablePreimageRecording *bool
|
||||||
VMTrace *string
|
VMTrace *string
|
||||||
VMTraceConfig *string
|
VMTraceJsonConfig *string
|
||||||
DocRoot *string `toml:"-"`
|
DocRoot *string `toml:"-"`
|
||||||
RPCGasCap *uint64
|
RPCGasCap *uint64
|
||||||
RPCEVMTimeout *time.Duration
|
RPCEVMTimeout *time.Duration
|
||||||
|
|
@ -255,8 +255,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||||
if dec.VMTrace != nil {
|
if dec.VMTrace != nil {
|
||||||
c.VMTrace = *dec.VMTrace
|
c.VMTrace = *dec.VMTrace
|
||||||
}
|
}
|
||||||
if dec.VMTraceConfig != nil {
|
if dec.VMTraceJsonConfig != nil {
|
||||||
c.VMTraceConfig = *dec.VMTraceConfig
|
c.VMTraceJsonConfig = *dec.VMTraceJsonConfig
|
||||||
}
|
}
|
||||||
if dec.DocRoot != nil {
|
if dec.DocRoot != nil {
|
||||||
c.DocRoot = *dec.DocRoot
|
c.DocRoot = *dec.DocRoot
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,7 @@ func (t *pathTrie) onTrieNode(path []byte, hash common.Hash, blob []byte) {
|
||||||
//
|
//
|
||||||
// The extension node is detected if its path is the prefix of last committed
|
// The extension node is detected if its path is the prefix of last committed
|
||||||
// one and path gap is larger than one. If the path gap is only one byte,
|
// one and path gap is larger than one. If the path gap is only one byte,
|
||||||
// the current node could either be a full node, or a extension with single
|
// the current node could either be a full node, or an extension with single
|
||||||
// byte key. In either case, no gaps will be left in the path.
|
// byte key. In either case, no gaps will be left in the path.
|
||||||
if t.last != nil && bytes.HasPrefix(t.last, path) && len(t.last)-len(path) > 1 {
|
if t.last != nil && bytes.HasPrefix(t.last, path) && len(t.last)-len(path) > 1 {
|
||||||
for i := len(path) + 1; i < len(t.last); i++ {
|
for i := len(path) + 1; i < len(t.last); i++ {
|
||||||
|
|
|
||||||
|
|
@ -233,6 +233,12 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, vm.BlockContext{}, nil, nil, err
|
return nil, vm.BlockContext{}, nil, nil, err
|
||||||
}
|
}
|
||||||
|
// Insert parent beacon block root in the state as per EIP-4788.
|
||||||
|
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
||||||
|
context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil)
|
||||||
|
vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, eth.blockchain.Config(), vm.Config{})
|
||||||
|
core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb)
|
||||||
|
}
|
||||||
if txIndex == 0 && len(block.Transactions()) == 0 {
|
if txIndex == 0 && len(block.Transactions()) == 0 {
|
||||||
return nil, vm.BlockContext{}, statedb, release, nil
|
return nil, vm.BlockContext{}, statedb, release, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -376,6 +376,13 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed
|
||||||
failed = err
|
failed = err
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
// Insert block's parent beacon block root in the state
|
||||||
|
// as per EIP-4788.
|
||||||
|
if beaconRoot := next.BeaconRoot(); beaconRoot != nil {
|
||||||
|
context := core.NewEVMBlockContext(next.Header(), api.chainContext(ctx), nil)
|
||||||
|
vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, api.backend.ChainConfig(), vm.Config{})
|
||||||
|
core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb)
|
||||||
|
}
|
||||||
// Clean out any pending release functions of trace state. Note this
|
// Clean out any pending release functions of trace state. Note this
|
||||||
// step must be done after constructing tracing state, because the
|
// step must be done after constructing tracing state, because the
|
||||||
// tracing state of block next depends on the parent state and construction
|
// tracing state of block next depends on the parent state and construction
|
||||||
|
|
@ -517,7 +524,6 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer release()
|
defer release()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
roots []common.Hash
|
roots []common.Hash
|
||||||
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
|
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
|
||||||
|
|
@ -525,6 +531,10 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
|
||||||
vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||||
deleteEmptyObjects = chainConfig.IsEIP158(block.Number())
|
deleteEmptyObjects = chainConfig.IsEIP158(block.Number())
|
||||||
)
|
)
|
||||||
|
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
||||||
|
vmenv := vm.NewEVM(vmctx, vm.TxContext{}, statedb, chainConfig, vm.Config{})
|
||||||
|
core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb)
|
||||||
|
}
|
||||||
for i, tx := range block.Transactions() {
|
for i, tx := range block.Transactions() {
|
||||||
if err := ctx.Err(); err != nil {
|
if err := ctx.Err(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -584,7 +594,6 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer release()
|
defer release()
|
||||||
|
|
||||||
// JS tracers have high overhead. In this case run a parallel
|
// JS tracers have high overhead. In this case run a parallel
|
||||||
// process that generates states in one thread and traces txes
|
// process that generates states in one thread and traces txes
|
||||||
// in separate worker threads.
|
// in separate worker threads.
|
||||||
|
|
@ -601,6 +610,10 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
|
||||||
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
|
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
|
||||||
results = make([]*txTraceResult, len(txs))
|
results = make([]*txTraceResult, len(txs))
|
||||||
)
|
)
|
||||||
|
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
||||||
|
vmenv := vm.NewEVM(blockCtx, vm.TxContext{}, statedb, api.backend.ChainConfig(), vm.Config{})
|
||||||
|
core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb)
|
||||||
|
}
|
||||||
for i, tx := range txs {
|
for i, tx := range txs {
|
||||||
// Generate the next state snapshot fast without tracing
|
// Generate the next state snapshot fast without tracing
|
||||||
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
|
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
|
||||||
|
|
@ -727,7 +740,6 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer release()
|
defer release()
|
||||||
|
|
||||||
// Retrieve the tracing configurations, or use default values
|
// Retrieve the tracing configurations, or use default values
|
||||||
var (
|
var (
|
||||||
logConfig logger.Config
|
logConfig logger.Config
|
||||||
|
|
@ -756,6 +768,10 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
|
||||||
// Note: This copies the config, to not screw up the main config
|
// Note: This copies the config, to not screw up the main config
|
||||||
chainConfig, canon = overrideConfig(chainConfig, config.Overrides)
|
chainConfig, canon = overrideConfig(chainConfig, config.Overrides)
|
||||||
}
|
}
|
||||||
|
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
||||||
|
vmenv := vm.NewEVM(vmctx, vm.TxContext{}, statedb, chainConfig, vm.Config{})
|
||||||
|
core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb)
|
||||||
|
}
|
||||||
for i, tx := range block.Transactions() {
|
for i, tx := range block.Transactions() {
|
||||||
// Prepare the transaction for un-traced execution
|
// Prepare the transaction for un-traced execution
|
||||||
var (
|
var (
|
||||||
|
|
|
||||||
|
|
@ -100,24 +100,6 @@ go influxdb.InfluxDB(metrics.DefaultRegistry,
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
Periodically upload every metric to Librato using the [Librato client](https://github.com/mihasya/go-metrics-librato):
|
|
||||||
|
|
||||||
**Note**: the client included with this repository under the `librato` package
|
|
||||||
has been deprecated and moved to the repository linked above.
|
|
||||||
|
|
||||||
```go
|
|
||||||
import "github.com/mihasya/go-metrics-librato"
|
|
||||||
|
|
||||||
go librato.Librato(metrics.DefaultRegistry,
|
|
||||||
10e9, // interval
|
|
||||||
"example@example.com", // account owner email address
|
|
||||||
"token", // Librato API token
|
|
||||||
"hostname", // source
|
|
||||||
[]float64{0.95}, // percentiles to send
|
|
||||||
time.Millisecond, // time unit
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
Periodically emit every metric to StatHat:
|
Periodically emit every metric to StatHat:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
|
@ -157,7 +139,6 @@ Publishing Metrics
|
||||||
|
|
||||||
Clients are available for the following destinations:
|
Clients are available for the following destinations:
|
||||||
|
|
||||||
* Librato - https://github.com/mihasya/go-metrics-librato
|
|
||||||
* Graphite - https://github.com/cyberdelia/go-metrics-graphite
|
* Graphite - https://github.com/cyberdelia/go-metrics-graphite
|
||||||
* InfluxDB - https://github.com/vrischmann/go-metrics-influxdb
|
* InfluxDB - https://github.com/vrischmann/go-metrics-influxdb
|
||||||
* Ganglia - https://github.com/appscode/metlia
|
* Ganglia - https://github.com/appscode/metlia
|
||||||
|
|
|
||||||
|
|
@ -1,104 +0,0 @@
|
||||||
package librato
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
const Operations = "operations"
|
|
||||||
const OperationsShort = "ops"
|
|
||||||
|
|
||||||
type LibratoClient struct {
|
|
||||||
Email, Token string
|
|
||||||
}
|
|
||||||
|
|
||||||
// property strings
|
|
||||||
const (
|
|
||||||
// display attributes
|
|
||||||
Color = "color"
|
|
||||||
DisplayMax = "display_max"
|
|
||||||
DisplayMin = "display_min"
|
|
||||||
DisplayUnitsLong = "display_units_long"
|
|
||||||
DisplayUnitsShort = "display_units_short"
|
|
||||||
DisplayStacked = "display_stacked"
|
|
||||||
DisplayTransform = "display_transform"
|
|
||||||
// special gauge display attributes
|
|
||||||
SummarizeFunction = "summarize_function"
|
|
||||||
Aggregate = "aggregate"
|
|
||||||
|
|
||||||
// metric keys
|
|
||||||
Name = "name"
|
|
||||||
Period = "period"
|
|
||||||
Description = "description"
|
|
||||||
DisplayName = "display_name"
|
|
||||||
Attributes = "attributes"
|
|
||||||
|
|
||||||
// measurement keys
|
|
||||||
MeasureTime = "measure_time"
|
|
||||||
Source = "source"
|
|
||||||
Value = "value"
|
|
||||||
|
|
||||||
// special gauge keys
|
|
||||||
Count = "count"
|
|
||||||
Sum = "sum"
|
|
||||||
Max = "max"
|
|
||||||
Min = "min"
|
|
||||||
SumSquares = "sum_squares"
|
|
||||||
|
|
||||||
// batch keys
|
|
||||||
Counters = "counters"
|
|
||||||
Gauges = "gauges"
|
|
||||||
|
|
||||||
MetricsPostUrl = "https://metrics-api.librato.com/v1/metrics"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Measurement map[string]interface{}
|
|
||||||
type Metric map[string]interface{}
|
|
||||||
|
|
||||||
type Batch struct {
|
|
||||||
Gauges []Measurement `json:"gauges,omitempty"`
|
|
||||||
Counters []Measurement `json:"counters,omitempty"`
|
|
||||||
MeasureTime int64 `json:"measure_time"`
|
|
||||||
Source string `json:"source"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *LibratoClient) PostMetrics(batch Batch) (err error) {
|
|
||||||
var (
|
|
||||||
js []byte
|
|
||||||
req *http.Request
|
|
||||||
resp *http.Response
|
|
||||||
)
|
|
||||||
|
|
||||||
if len(batch.Counters) == 0 && len(batch.Gauges) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if js, err = json.Marshal(batch); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if req, err = http.NewRequest(http.MethodPost, MetricsPostUrl, bytes.NewBuffer(js)); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
req.SetBasicAuth(c.Email, c.Token)
|
|
||||||
|
|
||||||
resp, err = http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
var body []byte
|
|
||||||
if body, err = io.ReadAll(resp.Body); err != nil {
|
|
||||||
body = []byte(fmt.Sprintf("(could not fetch response body for error: %s)", err))
|
|
||||||
}
|
|
||||||
err = fmt.Errorf("unable to post to Librato: %d %s %s", resp.StatusCode, resp.Status, string(body))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
@ -1,254 +0,0 @@
|
||||||
package librato
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"math"
|
|
||||||
"regexp"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
|
||||||
)
|
|
||||||
|
|
||||||
// a regexp for extracting the unit from time.Duration.String
|
|
||||||
var unitRegexp = regexp.MustCompile(`[^\\d]+$`)
|
|
||||||
|
|
||||||
// a helper that turns a time.Duration into librato display attributes for timer metrics
|
|
||||||
func translateTimerAttributes(d time.Duration) (attrs map[string]interface{}) {
|
|
||||||
attrs = make(map[string]interface{})
|
|
||||||
attrs[DisplayTransform] = fmt.Sprintf("x/%d", int64(d))
|
|
||||||
attrs[DisplayUnitsShort] = string(unitRegexp.Find([]byte(d.String())))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type Reporter struct {
|
|
||||||
Email, Token string
|
|
||||||
Namespace string
|
|
||||||
Source string
|
|
||||||
Interval time.Duration
|
|
||||||
Registry metrics.Registry
|
|
||||||
Percentiles []float64 // percentiles to report on histogram metrics
|
|
||||||
TimerAttributes map[string]interface{} // units in which timers will be displayed
|
|
||||||
intervalSec int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewReporter(r metrics.Registry, d time.Duration, e string, t string, s string, p []float64, u time.Duration) *Reporter {
|
|
||||||
return &Reporter{e, t, "", s, d, r, p, translateTimerAttributes(u), int64(d / time.Second)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Librato(r metrics.Registry, d time.Duration, e string, t string, s string, p []float64, u time.Duration) {
|
|
||||||
NewReporter(r, d, e, t, s, p, u).Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rep *Reporter) Run() {
|
|
||||||
log.Printf("WARNING: This client has been DEPRECATED! It has been moved to https://github.com/mihasya/go-metrics-librato and will be removed from rcrowley/go-metrics on August 5th 2015")
|
|
||||||
ticker := time.NewTicker(rep.Interval)
|
|
||||||
defer ticker.Stop()
|
|
||||||
metricsApi := &LibratoClient{rep.Email, rep.Token}
|
|
||||||
for now := range ticker.C {
|
|
||||||
var metrics Batch
|
|
||||||
var err error
|
|
||||||
if metrics, err = rep.BuildRequest(now, rep.Registry); err != nil {
|
|
||||||
log.Printf("ERROR constructing librato request body %s", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := metricsApi.PostMetrics(metrics); err != nil {
|
|
||||||
log.Printf("ERROR sending metrics to librato %s", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculate sum of squares from data provided by metrics.Histogram
|
|
||||||
// see http://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods
|
|
||||||
func sumSquares(icount int64, mean, stDev float64) float64 {
|
|
||||||
count := float64(icount)
|
|
||||||
sumSquared := math.Pow(count*mean, 2)
|
|
||||||
sumSquares := math.Pow(count*stDev, 2) + sumSquared/count
|
|
||||||
if math.IsNaN(sumSquares) {
|
|
||||||
return 0.0
|
|
||||||
}
|
|
||||||
return sumSquares
|
|
||||||
}
|
|
||||||
func sumSquaresTimer(t metrics.TimerSnapshot) float64 {
|
|
||||||
count := float64(t.Count())
|
|
||||||
sumSquared := math.Pow(count*t.Mean(), 2)
|
|
||||||
sumSquares := math.Pow(count*t.StdDev(), 2) + sumSquared/count
|
|
||||||
if math.IsNaN(sumSquares) {
|
|
||||||
return 0.0
|
|
||||||
}
|
|
||||||
return sumSquares
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rep *Reporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot Batch, err error) {
|
|
||||||
snapshot = Batch{
|
|
||||||
// coerce timestamps to a stepping fn so that they line up in Librato graphs
|
|
||||||
MeasureTime: (now.Unix() / rep.intervalSec) * rep.intervalSec,
|
|
||||||
Source: rep.Source,
|
|
||||||
}
|
|
||||||
snapshot.Gauges = make([]Measurement, 0)
|
|
||||||
snapshot.Counters = make([]Measurement, 0)
|
|
||||||
histogramGaugeCount := 1 + len(rep.Percentiles)
|
|
||||||
r.Each(func(name string, metric interface{}) {
|
|
||||||
if rep.Namespace != "" {
|
|
||||||
name = fmt.Sprintf("%s.%s", rep.Namespace, name)
|
|
||||||
}
|
|
||||||
measurement := Measurement{}
|
|
||||||
measurement[Period] = rep.Interval.Seconds()
|
|
||||||
switch m := metric.(type) {
|
|
||||||
case metrics.Counter:
|
|
||||||
ms := m.Snapshot()
|
|
||||||
if ms.Count() > 0 {
|
|
||||||
measurement[Name] = fmt.Sprintf("%s.%s", name, "count")
|
|
||||||
measurement[Value] = float64(ms.Count())
|
|
||||||
measurement[Attributes] = map[string]interface{}{
|
|
||||||
DisplayUnitsLong: Operations,
|
|
||||||
DisplayUnitsShort: OperationsShort,
|
|
||||||
DisplayMin: "0",
|
|
||||||
}
|
|
||||||
snapshot.Counters = append(snapshot.Counters, measurement)
|
|
||||||
}
|
|
||||||
case metrics.CounterFloat64:
|
|
||||||
if count := m.Snapshot().Count(); count > 0 {
|
|
||||||
measurement[Name] = fmt.Sprintf("%s.%s", name, "count")
|
|
||||||
measurement[Value] = count
|
|
||||||
measurement[Attributes] = map[string]interface{}{
|
|
||||||
DisplayUnitsLong: Operations,
|
|
||||||
DisplayUnitsShort: OperationsShort,
|
|
||||||
DisplayMin: "0",
|
|
||||||
}
|
|
||||||
snapshot.Counters = append(snapshot.Counters, measurement)
|
|
||||||
}
|
|
||||||
case metrics.Gauge:
|
|
||||||
measurement[Name] = name
|
|
||||||
measurement[Value] = float64(m.Snapshot().Value())
|
|
||||||
snapshot.Gauges = append(snapshot.Gauges, measurement)
|
|
||||||
case metrics.GaugeFloat64:
|
|
||||||
measurement[Name] = name
|
|
||||||
measurement[Value] = m.Snapshot().Value()
|
|
||||||
snapshot.Gauges = append(snapshot.Gauges, measurement)
|
|
||||||
case metrics.GaugeInfo:
|
|
||||||
measurement[Name] = name
|
|
||||||
measurement[Value] = m.Snapshot().Value()
|
|
||||||
snapshot.Gauges = append(snapshot.Gauges, measurement)
|
|
||||||
case metrics.Histogram:
|
|
||||||
ms := m.Snapshot()
|
|
||||||
if ms.Count() > 0 {
|
|
||||||
gauges := make([]Measurement, histogramGaugeCount)
|
|
||||||
measurement[Name] = fmt.Sprintf("%s.%s", name, "hist")
|
|
||||||
measurement[Count] = uint64(ms.Count())
|
|
||||||
measurement[Max] = float64(ms.Max())
|
|
||||||
measurement[Min] = float64(ms.Min())
|
|
||||||
measurement[Sum] = float64(ms.Sum())
|
|
||||||
measurement[SumSquares] = sumSquares(ms.Count(), ms.Mean(), ms.StdDev())
|
|
||||||
gauges[0] = measurement
|
|
||||||
for i, p := range rep.Percentiles {
|
|
||||||
gauges[i+1] = Measurement{
|
|
||||||
Name: fmt.Sprintf("%s.%.2f", measurement[Name], p),
|
|
||||||
Value: ms.Percentile(p),
|
|
||||||
Period: measurement[Period],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
snapshot.Gauges = append(snapshot.Gauges, gauges...)
|
|
||||||
}
|
|
||||||
case metrics.Meter:
|
|
||||||
ms := m.Snapshot()
|
|
||||||
measurement[Name] = name
|
|
||||||
measurement[Value] = float64(ms.Count())
|
|
||||||
snapshot.Counters = append(snapshot.Counters, measurement)
|
|
||||||
snapshot.Gauges = append(snapshot.Gauges,
|
|
||||||
Measurement{
|
|
||||||
Name: fmt.Sprintf("%s.%s", name, "1min"),
|
|
||||||
Value: ms.Rate1(),
|
|
||||||
Period: int64(rep.Interval.Seconds()),
|
|
||||||
Attributes: map[string]interface{}{
|
|
||||||
DisplayUnitsLong: Operations,
|
|
||||||
DisplayUnitsShort: OperationsShort,
|
|
||||||
DisplayMin: "0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Measurement{
|
|
||||||
Name: fmt.Sprintf("%s.%s", name, "5min"),
|
|
||||||
Value: ms.Rate5(),
|
|
||||||
Period: int64(rep.Interval.Seconds()),
|
|
||||||
Attributes: map[string]interface{}{
|
|
||||||
DisplayUnitsLong: Operations,
|
|
||||||
DisplayUnitsShort: OperationsShort,
|
|
||||||
DisplayMin: "0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Measurement{
|
|
||||||
Name: fmt.Sprintf("%s.%s", name, "15min"),
|
|
||||||
Value: ms.Rate15(),
|
|
||||||
Period: int64(rep.Interval.Seconds()),
|
|
||||||
Attributes: map[string]interface{}{
|
|
||||||
DisplayUnitsLong: Operations,
|
|
||||||
DisplayUnitsShort: OperationsShort,
|
|
||||||
DisplayMin: "0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
case metrics.Timer:
|
|
||||||
ms := m.Snapshot()
|
|
||||||
measurement[Name] = name
|
|
||||||
measurement[Value] = float64(ms.Count())
|
|
||||||
snapshot.Counters = append(snapshot.Counters, measurement)
|
|
||||||
if ms.Count() > 0 {
|
|
||||||
libratoName := fmt.Sprintf("%s.%s", name, "timer.mean")
|
|
||||||
gauges := make([]Measurement, histogramGaugeCount)
|
|
||||||
gauges[0] = Measurement{
|
|
||||||
Name: libratoName,
|
|
||||||
Count: uint64(ms.Count()),
|
|
||||||
Sum: ms.Mean() * float64(ms.Count()),
|
|
||||||
Max: float64(ms.Max()),
|
|
||||||
Min: float64(ms.Min()),
|
|
||||||
SumSquares: sumSquaresTimer(ms),
|
|
||||||
Period: int64(rep.Interval.Seconds()),
|
|
||||||
Attributes: rep.TimerAttributes,
|
|
||||||
}
|
|
||||||
for i, p := range rep.Percentiles {
|
|
||||||
gauges[i+1] = Measurement{
|
|
||||||
Name: fmt.Sprintf("%s.timer.%2.0f", name, p*100),
|
|
||||||
Value: ms.Percentile(p),
|
|
||||||
Period: int64(rep.Interval.Seconds()),
|
|
||||||
Attributes: rep.TimerAttributes,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
snapshot.Gauges = append(snapshot.Gauges, gauges...)
|
|
||||||
snapshot.Gauges = append(snapshot.Gauges,
|
|
||||||
Measurement{
|
|
||||||
Name: fmt.Sprintf("%s.%s", name, "rate.1min"),
|
|
||||||
Value: ms.Rate1(),
|
|
||||||
Period: int64(rep.Interval.Seconds()),
|
|
||||||
Attributes: map[string]interface{}{
|
|
||||||
DisplayUnitsLong: Operations,
|
|
||||||
DisplayUnitsShort: OperationsShort,
|
|
||||||
DisplayMin: "0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Measurement{
|
|
||||||
Name: fmt.Sprintf("%s.%s", name, "rate.5min"),
|
|
||||||
Value: ms.Rate5(),
|
|
||||||
Period: int64(rep.Interval.Seconds()),
|
|
||||||
Attributes: map[string]interface{}{
|
|
||||||
DisplayUnitsLong: Operations,
|
|
||||||
DisplayUnitsShort: OperationsShort,
|
|
||||||
DisplayMin: "0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Measurement{
|
|
||||||
Name: fmt.Sprintf("%s.%s", name, "rate.15min"),
|
|
||||||
Value: ms.Rate15(),
|
|
||||||
Period: int64(rep.Interval.Seconds()),
|
|
||||||
Attributes: map[string]interface{}{
|
|
||||||
DisplayUnitsLong: Operations,
|
|
||||||
DisplayUnitsShort: OperationsShort,
|
|
||||||
DisplayMin: "0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
@ -339,7 +339,7 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Error may be ignored here. The error has already been checked
|
// Error may be ignored here. The error has already been checked
|
||||||
// during transaction acceptance is the transaction pool.
|
// during transaction acceptance in the transaction pool.
|
||||||
from, _ := types.Sender(env.signer, tx)
|
from, _ := types.Sender(env.signer, tx)
|
||||||
|
|
||||||
// Check whether the tx is replay protected. If we're not in the EIP155 hf
|
// Check whether the tx is replay protected. If we're not in the EIP155 hf
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import (
|
||||||
const (
|
const (
|
||||||
VersionMajor = 1 // Major version component of the current release
|
VersionMajor = 1 // Major version component of the current release
|
||||||
VersionMinor = 14 // Minor version component of the current release
|
VersionMinor = 14 // Minor version component of the current release
|
||||||
VersionPatch = 0 // Patch version component of the current release
|
VersionPatch = 1 // Patch version component of the current release
|
||||||
VersionMeta = "unstable" // Version metadata to append to the version string
|
VersionMeta = "unstable" // Version metadata to append to the version string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,7 @@ type op interface {
|
||||||
// basicOp handles basic types bool, uint*, string.
|
// basicOp handles basic types bool, uint*, string.
|
||||||
type basicOp struct {
|
type basicOp struct {
|
||||||
typ types.Type
|
typ types.Type
|
||||||
writeMethod string // calle write the value
|
writeMethod string // EncoderBuffer writer method name
|
||||||
writeArgType types.Type // parameter type of writeMethod
|
writeArgType types.Type // parameter type of writeMethod
|
||||||
decMethod string
|
decMethod string
|
||||||
decResultType types.Type // return type of decMethod
|
decResultType types.Type // return type of decMethod
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ func NotifierFromContext(ctx context.Context) (*Notifier, bool) {
|
||||||
return n, ok
|
return n, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notifier is tied to a RPC connection that supports subscriptions.
|
// Notifier is tied to an RPC connection that supports subscriptions.
|
||||||
// Server callbacks use the notifier to send notifications.
|
// Server callbacks use the notifier to send notifications.
|
||||||
type Notifier struct {
|
type Notifier struct {
|
||||||
h *handler
|
h *handler
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ type API struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerCodec implements reading, parsing and writing RPC messages for the server side of
|
// ServerCodec implements reading, parsing and writing RPC messages for the server side of
|
||||||
// a RPC session. Implementations must be go-routine safe since the codec can be called in
|
// an RPC session. Implementations must be go-routine safe since the codec can be called in
|
||||||
// multiple go-routines concurrently.
|
// multiple go-routines concurrently.
|
||||||
type ServerCodec interface {
|
type ServerCodec interface {
|
||||||
peerInfo() PeerInfo
|
peerInfo() PeerInfo
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrCommitted is returned when a already committed trie is requested for usage.
|
// ErrCommitted is returned when an already committed trie is requested for usage.
|
||||||
// The potential usages can be `Get`, `Update`, `Delete`, `NodeIterator`, `Prove`
|
// The potential usages can be `Get`, `Update`, `Delete`, `NodeIterator`, `Prove`
|
||||||
// and so on.
|
// and so on.
|
||||||
var ErrCommitted = errors.New("trie is already committed")
|
var ErrCommitted = errors.New("trie is already committed")
|
||||||
|
|
|
||||||
|
|
@ -372,7 +372,7 @@ func unset(parent node, child node, key []byte, pos int, removeLeft bool) error
|
||||||
return unset(cld, cld.Children[key[pos]], key, pos+1, removeLeft)
|
return unset(cld, cld.Children[key[pos]], key, pos+1, removeLeft)
|
||||||
case *shortNode:
|
case *shortNode:
|
||||||
if len(key[pos:]) < len(cld.Key) || !bytes.Equal(cld.Key, key[pos:pos+len(cld.Key)]) {
|
if len(key[pos:]) < len(cld.Key) || !bytes.Equal(cld.Key, key[pos:pos+len(cld.Key)]) {
|
||||||
// Find the fork point, it's an non-existent branch.
|
// Find the fork point, it's a non-existent branch.
|
||||||
if removeLeft {
|
if removeLeft {
|
||||||
if bytes.Compare(cld.Key, key[pos:]) < 0 {
|
if bytes.Compare(cld.Key, key[pos:]) < 0 {
|
||||||
// The key of fork shortnode is less than the path
|
// The key of fork shortnode is less than the path
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@
|
||||||
package trie
|
package trie
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"maps"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -92,23 +94,13 @@ func (t *tracer) reset() {
|
||||||
|
|
||||||
// copy returns a deep copied tracer instance.
|
// copy returns a deep copied tracer instance.
|
||||||
func (t *tracer) copy() *tracer {
|
func (t *tracer) copy() *tracer {
|
||||||
var (
|
accessList := make(map[string][]byte, len(t.accessList))
|
||||||
inserts = make(map[string]struct{})
|
|
||||||
deletes = make(map[string]struct{})
|
|
||||||
accessList = make(map[string][]byte)
|
|
||||||
)
|
|
||||||
for path := range t.inserts {
|
|
||||||
inserts[path] = struct{}{}
|
|
||||||
}
|
|
||||||
for path := range t.deletes {
|
|
||||||
deletes[path] = struct{}{}
|
|
||||||
}
|
|
||||||
for path, blob := range t.accessList {
|
for path, blob := range t.accessList {
|
||||||
accessList[path] = common.CopyBytes(blob)
|
accessList[path] = common.CopyBytes(blob)
|
||||||
}
|
}
|
||||||
return &tracer{
|
return &tracer{
|
||||||
inserts: inserts,
|
inserts: maps.Clone(t.inserts),
|
||||||
deletes: deletes,
|
deletes: maps.Clone(t.deletes),
|
||||||
accessList: accessList,
|
accessList: accessList,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ func NewNodeSet(owner common.Hash) *NodeSet {
|
||||||
// ForEachWithOrder iterates the nodes with the order from bottom to top,
|
// ForEachWithOrder iterates the nodes with the order from bottom to top,
|
||||||
// right to left, nodes with the longest path will be iterated first.
|
// right to left, nodes with the longest path will be iterated first.
|
||||||
func (set *NodeSet) ForEachWithOrder(callback func(path string, n *Node)) {
|
func (set *NodeSet) ForEachWithOrder(callback func(path string, n *Node)) {
|
||||||
var paths []string
|
paths := make([]string, 0, len(set.Nodes))
|
||||||
for path := range set.Nodes {
|
for path := range set.Nodes {
|
||||||
paths = append(paths, path)
|
paths = append(paths, path)
|
||||||
}
|
}
|
||||||
|
|
@ -133,7 +133,7 @@ func (set *NodeSet) Size() (int, int) {
|
||||||
// Hashes returns the hashes of all updated nodes. TODO(rjl493456442) how can
|
// Hashes returns the hashes of all updated nodes. TODO(rjl493456442) how can
|
||||||
// we get rid of it?
|
// we get rid of it?
|
||||||
func (set *NodeSet) Hashes() []common.Hash {
|
func (set *NodeSet) Hashes() []common.Hash {
|
||||||
var ret []common.Hash
|
ret := make([]common.Hash, 0, len(set.Nodes))
|
||||||
for _, node := range set.Nodes {
|
for _, node := range set.Nodes {
|
||||||
ret = append(ret, node.Hash)
|
ret = append(ret, node.Hash)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -293,6 +293,7 @@ func (db *Database) Enable(root common.Hash) error {
|
||||||
// Ensure the provided state root matches the stored one.
|
// Ensure the provided state root matches the stored one.
|
||||||
root = types.TrieRootHash(root)
|
root = types.TrieRootHash(root)
|
||||||
_, stored := rawdb.ReadAccountTrieNode(db.diskdb, nil)
|
_, stored := rawdb.ReadAccountTrieNode(db.diskdb, nil)
|
||||||
|
stored = types.TrieRootHash(stored)
|
||||||
if stored != root {
|
if stored != root {
|
||||||
return fmt.Errorf("state root mismatch: stored %x, synced %x", stored, root)
|
return fmt.Errorf("state root mismatch: stored %x, synced %x", stored, root)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -476,13 +476,13 @@ func TestDisable(t *testing.T) {
|
||||||
|
|
||||||
_, stored := rawdb.ReadAccountTrieNode(tester.db.diskdb, nil)
|
_, stored := rawdb.ReadAccountTrieNode(tester.db.diskdb, nil)
|
||||||
if err := tester.db.Disable(); err != nil {
|
if err := tester.db.Disable(); err != nil {
|
||||||
t.Fatal("Failed to deactivate database")
|
t.Fatalf("Failed to deactivate database: %v", err)
|
||||||
}
|
}
|
||||||
if err := tester.db.Enable(types.EmptyRootHash); err == nil {
|
if err := tester.db.Enable(types.EmptyRootHash); err == nil {
|
||||||
t.Fatalf("Invalid activation should be rejected")
|
t.Fatal("Invalid activation should be rejected")
|
||||||
}
|
}
|
||||||
if err := tester.db.Enable(stored); err != nil {
|
if err := tester.db.Enable(stored); err != nil {
|
||||||
t.Fatal("Failed to activate database")
|
t.Fatalf("Failed to activate database: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure journal is deleted from disk
|
// Ensure journal is deleted from disk
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue