mirror of https://github.com/ethereum/go-ethereum
commit
827716f0d1
@ -0,0 +1,23 @@ |
||||
name: i386 linux tests |
||||
|
||||
on: |
||||
push: |
||||
branches: [ master ] |
||||
pull_request: |
||||
branches: [ master ] |
||||
workflow_dispatch: |
||||
|
||||
jobs: |
||||
build: |
||||
runs-on: self-hosted |
||||
steps: |
||||
- uses: actions/checkout@v2 |
||||
- name: Set up Go |
||||
uses: actions/setup-go@v2 |
||||
with: |
||||
go-version: 1.21.4 |
||||
- name: Run tests |
||||
run: go test -short ./... |
||||
env: |
||||
GOOS: linux |
||||
GOARCH: 386 |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,125 @@ |
||||
// Copyright 2023 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package light |
||||
|
||||
import ( |
||||
"encoding/binary" |
||||
"fmt" |
||||
|
||||
"github.com/ethereum/go-ethereum/common/lru" |
||||
"github.com/ethereum/go-ethereum/ethdb" |
||||
"github.com/ethereum/go-ethereum/log" |
||||
"github.com/ethereum/go-ethereum/rlp" |
||||
) |
||||
|
||||
// canonicalStore stores instances of the given type in a database and caches
|
||||
// them in memory, associated with a continuous range of period numbers.
|
||||
// Note: canonicalStore is not thread safe and it is the caller's responsibility
|
||||
// to avoid concurrent access.
|
||||
type canonicalStore[T any] struct { |
||||
keyPrefix []byte |
||||
periods periodRange |
||||
cache *lru.Cache[uint64, T] |
||||
} |
||||
|
||||
// newCanonicalStore creates a new canonicalStore and loads all keys associated
|
||||
// with the keyPrefix in order to determine the ranges available in the database.
|
||||
func newCanonicalStore[T any](db ethdb.Iteratee, keyPrefix []byte) (*canonicalStore[T], error) { |
||||
cs := &canonicalStore[T]{ |
||||
keyPrefix: keyPrefix, |
||||
cache: lru.NewCache[uint64, T](100), |
||||
} |
||||
var ( |
||||
iter = db.NewIterator(keyPrefix, nil) |
||||
kl = len(keyPrefix) |
||||
first = true |
||||
) |
||||
defer iter.Release() |
||||
|
||||
for iter.Next() { |
||||
if len(iter.Key()) != kl+8 { |
||||
log.Warn("Invalid key length in the canonical chain database", "key", fmt.Sprintf("%#x", iter.Key())) |
||||
continue |
||||
} |
||||
period := binary.BigEndian.Uint64(iter.Key()[kl : kl+8]) |
||||
if first { |
||||
cs.periods.Start = period |
||||
} else if cs.periods.End != period { |
||||
return nil, fmt.Errorf("gap in the canonical chain database between periods %d and %d", cs.periods.End, period-1) |
||||
} |
||||
first = false |
||||
cs.periods.End = period + 1 |
||||
} |
||||
return cs, nil |
||||
} |
||||
|
||||
// databaseKey returns the database key belonging to the given period.
|
||||
func (cs *canonicalStore[T]) databaseKey(period uint64) []byte { |
||||
return binary.BigEndian.AppendUint64(append([]byte{}, cs.keyPrefix...), period) |
||||
} |
||||
|
||||
// add adds the given item to the database. It also ensures that the range remains
|
||||
// continuous. Can be used either with a batch or database backend.
|
||||
func (cs *canonicalStore[T]) add(backend ethdb.KeyValueWriter, period uint64, value T) error { |
||||
if !cs.periods.canExpand(period) { |
||||
return fmt.Errorf("period expansion is not allowed, first: %d, next: %d, period: %d", cs.periods.Start, cs.periods.End, period) |
||||
} |
||||
enc, err := rlp.EncodeToBytes(value) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if err := backend.Put(cs.databaseKey(period), enc); err != nil { |
||||
return err |
||||
} |
||||
cs.cache.Add(period, value) |
||||
cs.periods.expand(period) |
||||
return nil |
||||
} |
||||
|
||||
// deleteFrom removes items starting from the given period.
|
||||
func (cs *canonicalStore[T]) deleteFrom(db ethdb.KeyValueWriter, fromPeriod uint64) (deleted periodRange) { |
||||
keepRange, deleteRange := cs.periods.split(fromPeriod) |
||||
deleteRange.each(func(period uint64) { |
||||
db.Delete(cs.databaseKey(period)) |
||||
cs.cache.Remove(period) |
||||
}) |
||||
cs.periods = keepRange |
||||
return deleteRange |
||||
} |
||||
|
||||
// get returns the item at the given period or the null value of the given type
|
||||
// if no item is present.
|
||||
func (cs *canonicalStore[T]) get(backend ethdb.KeyValueReader, period uint64) (T, bool) { |
||||
var null, value T |
||||
if !cs.periods.contains(period) { |
||||
return null, false |
||||
} |
||||
if value, ok := cs.cache.Get(period); ok { |
||||
return value, true |
||||
} |
||||
enc, err := backend.Get(cs.databaseKey(period)) |
||||
if err != nil { |
||||
log.Error("Canonical store value not found", "period", period, "start", cs.periods.Start, "end", cs.periods.End) |
||||
return null, false |
||||
} |
||||
if err := rlp.DecodeBytes(enc, &value); err != nil { |
||||
log.Error("Error decoding canonical store value", "error", err) |
||||
return null, false |
||||
} |
||||
cs.cache.Add(period, value) |
||||
return value, true |
||||
} |
@ -0,0 +1,514 @@ |
||||
// Copyright 2023 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package light |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
"math" |
||||
"sync" |
||||
"time" |
||||
|
||||
"github.com/ethereum/go-ethereum/beacon/params" |
||||
"github.com/ethereum/go-ethereum/beacon/types" |
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/common/lru" |
||||
"github.com/ethereum/go-ethereum/common/mclock" |
||||
"github.com/ethereum/go-ethereum/core/rawdb" |
||||
"github.com/ethereum/go-ethereum/ethdb" |
||||
"github.com/ethereum/go-ethereum/log" |
||||
) |
||||
|
||||
var ( |
||||
ErrNeedCommittee = errors.New("sync committee required") |
||||
ErrInvalidUpdate = errors.New("invalid committee update") |
||||
ErrInvalidPeriod = errors.New("invalid update period") |
||||
ErrWrongCommitteeRoot = errors.New("wrong committee root") |
||||
ErrCannotReorg = errors.New("can not reorg committee chain") |
||||
) |
||||
|
||||
// CommitteeChain is a passive data structure that can validate, hold and update
|
||||
// a chain of beacon light sync committees and updates. It requires at least one
|
||||
// externally set fixed committee root at the beginning of the chain which can
|
||||
// be set either based on a BootstrapData or a trusted source (a local beacon
|
||||
// full node). This makes the structure useful for both light client and light
|
||||
// server setups.
|
||||
//
|
||||
// It always maintains the following consistency constraints:
|
||||
// - a committee can only be present if its root hash matches an existing fixed
|
||||
// root or if it is proven by an update at the previous period
|
||||
// - an update can only be present if a committee is present at the same period
|
||||
// and the update signature is valid and has enough participants.
|
||||
// The committee at the next period (proven by the update) should also be
|
||||
// present (note that this means they can only be added together if neither
|
||||
// is present yet). If a fixed root is present at the next period then the
|
||||
// update can only be present if it proves the same committee root.
|
||||
//
|
||||
// Once synced to the current sync period, CommitteeChain can also validate
|
||||
// signed beacon headers.
|
||||
type CommitteeChain struct { |
||||
// chainmu guards against concurrent access to the canonicalStore structures
|
||||
// (updates, committees, fixedCommitteeRoots) and ensures that they stay consistent
|
||||
// with each other and with committeeCache.
|
||||
chainmu sync.RWMutex |
||||
db ethdb.KeyValueStore |
||||
updates *canonicalStore[*types.LightClientUpdate] |
||||
committees *canonicalStore[*types.SerializedSyncCommittee] |
||||
fixedCommitteeRoots *canonicalStore[common.Hash] |
||||
committeeCache *lru.Cache[uint64, syncCommittee] // cache deserialized committees
|
||||
|
||||
clock mclock.Clock // monotonic clock (simulated clock in tests)
|
||||
unixNano func() int64 // system clock (simulated clock in tests)
|
||||
sigVerifier committeeSigVerifier // BLS sig verifier (dummy verifier in tests)
|
||||
|
||||
config *types.ChainConfig |
||||
signerThreshold int |
||||
minimumUpdateScore types.UpdateScore |
||||
enforceTime bool // enforceTime specifies whether the age of a signed header should be checked
|
||||
} |
||||
|
||||
// NewCommitteeChain creates a new CommitteeChain.
|
||||
func NewCommitteeChain(db ethdb.KeyValueStore, config *types.ChainConfig, signerThreshold int, enforceTime bool) *CommitteeChain { |
||||
return newCommitteeChain(db, config, signerThreshold, enforceTime, blsVerifier{}, &mclock.System{}, func() int64 { return time.Now().UnixNano() }) |
||||
} |
||||
|
||||
// newCommitteeChain creates a new CommitteeChain with the option of replacing the
|
||||
// clock source and signature verification for testing purposes.
|
||||
func newCommitteeChain(db ethdb.KeyValueStore, config *types.ChainConfig, signerThreshold int, enforceTime bool, sigVerifier committeeSigVerifier, clock mclock.Clock, unixNano func() int64) *CommitteeChain { |
||||
s := &CommitteeChain{ |
||||
committeeCache: lru.NewCache[uint64, syncCommittee](10), |
||||
db: db, |
||||
sigVerifier: sigVerifier, |
||||
clock: clock, |
||||
unixNano: unixNano, |
||||
config: config, |
||||
signerThreshold: signerThreshold, |
||||
enforceTime: enforceTime, |
||||
minimumUpdateScore: types.UpdateScore{ |
||||
SignerCount: uint32(signerThreshold), |
||||
SubPeriodIndex: params.SyncPeriodLength / 16, |
||||
}, |
||||
} |
||||
|
||||
var err1, err2, err3 error |
||||
if s.fixedCommitteeRoots, err1 = newCanonicalStore[common.Hash](db, rawdb.FixedCommitteeRootKey); err1 != nil { |
||||
log.Error("Error creating fixed committee root store", "error", err1) |
||||
} |
||||
if s.committees, err2 = newCanonicalStore[*types.SerializedSyncCommittee](db, rawdb.SyncCommitteeKey); err2 != nil { |
||||
log.Error("Error creating committee store", "error", err2) |
||||
} |
||||
if s.updates, err3 = newCanonicalStore[*types.LightClientUpdate](db, rawdb.BestUpdateKey); err3 != nil { |
||||
log.Error("Error creating update store", "error", err3) |
||||
} |
||||
if err1 != nil || err2 != nil || err3 != nil || !s.checkConstraints() { |
||||
log.Info("Resetting invalid committee chain") |
||||
s.Reset() |
||||
} |
||||
// roll back invalid updates (might be necessary if forks have been changed since last time)
|
||||
for !s.updates.periods.isEmpty() { |
||||
update, ok := s.updates.get(s.db, s.updates.periods.End-1) |
||||
if !ok { |
||||
log.Error("Sync committee update missing", "period", s.updates.periods.End-1) |
||||
s.Reset() |
||||
break |
||||
} |
||||
if valid, err := s.verifyUpdate(update); err != nil { |
||||
log.Error("Error validating update", "period", s.updates.periods.End-1, "error", err) |
||||
} else if valid { |
||||
break |
||||
} |
||||
if err := s.rollback(s.updates.periods.End); err != nil { |
||||
log.Error("Error writing batch into chain database", "error", err) |
||||
} |
||||
} |
||||
if !s.committees.periods.isEmpty() { |
||||
log.Trace("Sync committee chain loaded", "first period", s.committees.periods.Start, "last period", s.committees.periods.End-1) |
||||
} |
||||
return s |
||||
} |
||||
|
||||
// checkConstraints checks committee chain validity constraints
|
||||
func (s *CommitteeChain) checkConstraints() bool { |
||||
isNotInFixedCommitteeRootRange := func(r periodRange) bool { |
||||
return s.fixedCommitteeRoots.periods.isEmpty() || |
||||
r.Start < s.fixedCommitteeRoots.periods.Start || |
||||
r.Start >= s.fixedCommitteeRoots.periods.End |
||||
} |
||||
|
||||
valid := true |
||||
if !s.updates.periods.isEmpty() { |
||||
if isNotInFixedCommitteeRootRange(s.updates.periods) { |
||||
log.Error("Start update is not in the fixed roots range") |
||||
valid = false |
||||
} |
||||
if s.committees.periods.Start > s.updates.periods.Start || s.committees.periods.End <= s.updates.periods.End { |
||||
log.Error("Missing committees in update range") |
||||
valid = false |
||||
} |
||||
} |
||||
if !s.committees.periods.isEmpty() { |
||||
if isNotInFixedCommitteeRootRange(s.committees.periods) { |
||||
log.Error("Start committee is not in the fixed roots range") |
||||
valid = false |
||||
} |
||||
if s.committees.periods.End > s.fixedCommitteeRoots.periods.End && s.committees.periods.End > s.updates.periods.End+1 { |
||||
log.Error("Last committee is neither in the fixed roots range nor proven by updates") |
||||
valid = false |
||||
} |
||||
} |
||||
return valid |
||||
} |
||||
|
||||
// Reset resets the committee chain.
|
||||
func (s *CommitteeChain) Reset() { |
||||
s.chainmu.Lock() |
||||
defer s.chainmu.Unlock() |
||||
|
||||
if err := s.rollback(0); err != nil { |
||||
log.Error("Error writing batch into chain database", "error", err) |
||||
} |
||||
} |
||||
|
||||
// CheckpointInit initializes a CommitteeChain based on the checkpoint.
|
||||
// Note: if the chain is already initialized and the committees proven by the
|
||||
// checkpoint do match the existing chain then the chain is retained and the
|
||||
// new checkpoint becomes fixed.
|
||||
func (s *CommitteeChain) CheckpointInit(bootstrap *types.BootstrapData) error { |
||||
s.chainmu.Lock() |
||||
defer s.chainmu.Unlock() |
||||
|
||||
if err := bootstrap.Validate(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
period := bootstrap.Header.SyncPeriod() |
||||
if err := s.deleteFixedCommitteeRootsFrom(period + 2); err != nil { |
||||
s.Reset() |
||||
return err |
||||
} |
||||
if s.addFixedCommitteeRoot(period, bootstrap.CommitteeRoot) != nil { |
||||
s.Reset() |
||||
if err := s.addFixedCommitteeRoot(period, bootstrap.CommitteeRoot); err != nil { |
||||
s.Reset() |
||||
return err |
||||
} |
||||
} |
||||
if err := s.addFixedCommitteeRoot(period+1, common.Hash(bootstrap.CommitteeBranch[0])); err != nil { |
||||
s.Reset() |
||||
return err |
||||
} |
||||
if err := s.addCommittee(period, bootstrap.Committee); err != nil { |
||||
s.Reset() |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// addFixedCommitteeRoot sets a fixed committee root at the given period.
|
||||
// Note that the period where the first committee is added has to have a fixed
|
||||
// root which can either come from a BootstrapData or a trusted source.
|
||||
func (s *CommitteeChain) addFixedCommitteeRoot(period uint64, root common.Hash) error { |
||||
if root == (common.Hash{}) { |
||||
return ErrWrongCommitteeRoot |
||||
} |
||||
|
||||
batch := s.db.NewBatch() |
||||
oldRoot := s.getCommitteeRoot(period) |
||||
if !s.fixedCommitteeRoots.periods.canExpand(period) { |
||||
// Note: the fixed committee root range should always be continuous and
|
||||
// therefore the expected syncing method is to forward sync and optionally
|
||||
// backward sync periods one by one, starting from a checkpoint. The only
|
||||
// case when a root that is not adjacent to the already fixed ones can be
|
||||
// fixed is when the same root has already been proven by an update chain.
|
||||
// In this case the all roots in between can and should be fixed.
|
||||
// This scenario makes sense when a new trusted checkpoint is added to an
|
||||
// existing chain, ensuring that it will not be rolled back (might be
|
||||
// important in case of low signer participation rate).
|
||||
if root != oldRoot { |
||||
return ErrInvalidPeriod |
||||
} |
||||
// if the old root exists and matches the new one then it is guaranteed
|
||||
// that the given period is after the existing fixed range and the roots
|
||||
// in between can also be fixed.
|
||||
for p := s.fixedCommitteeRoots.periods.End; p < period; p++ { |
||||
if err := s.fixedCommitteeRoots.add(batch, p, s.getCommitteeRoot(p)); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
} |
||||
if oldRoot != (common.Hash{}) && (oldRoot != root) { |
||||
// existing old root was different, we have to reorg the chain
|
||||
if err := s.rollback(period); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
if err := s.fixedCommitteeRoots.add(batch, period, root); err != nil { |
||||
return err |
||||
} |
||||
if err := batch.Write(); err != nil { |
||||
log.Error("Error writing batch into chain database", "error", err) |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// deleteFixedCommitteeRootsFrom deletes fixed roots starting from the given period.
|
||||
// It also maintains chain consistency, meaning that it also deletes updates and
|
||||
// committees if they are no longer supported by a valid update chain.
|
||||
func (s *CommitteeChain) deleteFixedCommitteeRootsFrom(period uint64) error { |
||||
if period >= s.fixedCommitteeRoots.periods.End { |
||||
return nil |
||||
} |
||||
batch := s.db.NewBatch() |
||||
s.fixedCommitteeRoots.deleteFrom(batch, period) |
||||
if s.updates.periods.isEmpty() || period <= s.updates.periods.Start { |
||||
// Note: the first period of the update chain should always be fixed so if
|
||||
// the fixed root at the first update is removed then the entire update chain
|
||||
// and the proven committees have to be removed. Earlier committees in the
|
||||
// remaining fixed root range can stay.
|
||||
s.updates.deleteFrom(batch, period) |
||||
s.deleteCommitteesFrom(batch, period) |
||||
} else { |
||||
// The update chain stays intact, some previously fixed committee roots might
|
||||
// get unfixed but are still proven by the update chain. If there were
|
||||
// committees present after the range proven by updates, those should be
|
||||
// removed if the belonging fixed roots are also removed.
|
||||
fromPeriod := s.updates.periods.End + 1 // not proven by updates
|
||||
if period > fromPeriod { |
||||
fromPeriod = period // also not justified by fixed roots
|
||||
} |
||||
s.deleteCommitteesFrom(batch, fromPeriod) |
||||
} |
||||
if err := batch.Write(); err != nil { |
||||
log.Error("Error writing batch into chain database", "error", err) |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// deleteCommitteesFrom deletes committees starting from the given period.
|
||||
func (s *CommitteeChain) deleteCommitteesFrom(batch ethdb.Batch, period uint64) { |
||||
deleted := s.committees.deleteFrom(batch, period) |
||||
for period := deleted.Start; period < deleted.End; period++ { |
||||
s.committeeCache.Remove(period) |
||||
} |
||||
} |
||||
|
||||
// addCommittee adds a committee at the given period if possible.
|
||||
func (s *CommitteeChain) addCommittee(period uint64, committee *types.SerializedSyncCommittee) error { |
||||
if !s.committees.periods.canExpand(period) { |
||||
return ErrInvalidPeriod |
||||
} |
||||
root := s.getCommitteeRoot(period) |
||||
if root == (common.Hash{}) { |
||||
return ErrInvalidPeriod |
||||
} |
||||
if root != committee.Root() { |
||||
return ErrWrongCommitteeRoot |
||||
} |
||||
if !s.committees.periods.contains(period) { |
||||
if err := s.committees.add(s.db, period, committee); err != nil { |
||||
return err |
||||
} |
||||
s.committeeCache.Remove(period) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// InsertUpdate adds a new update if possible.
|
||||
func (s *CommitteeChain) InsertUpdate(update *types.LightClientUpdate, nextCommittee *types.SerializedSyncCommittee) error { |
||||
s.chainmu.Lock() |
||||
defer s.chainmu.Unlock() |
||||
|
||||
period := update.AttestedHeader.Header.SyncPeriod() |
||||
if !s.updates.periods.canExpand(period) || !s.committees.periods.contains(period) { |
||||
return ErrInvalidPeriod |
||||
} |
||||
if s.minimumUpdateScore.BetterThan(update.Score()) { |
||||
return ErrInvalidUpdate |
||||
} |
||||
oldRoot := s.getCommitteeRoot(period + 1) |
||||
reorg := oldRoot != (common.Hash{}) && oldRoot != update.NextSyncCommitteeRoot |
||||
if oldUpdate, ok := s.updates.get(s.db, period); ok && !update.Score().BetterThan(oldUpdate.Score()) { |
||||
// a better or equal update already exists; no changes, only fail if new one tried to reorg
|
||||
if reorg { |
||||
return ErrCannotReorg |
||||
} |
||||
return nil |
||||
} |
||||
if s.fixedCommitteeRoots.periods.contains(period+1) && reorg { |
||||
return ErrCannotReorg |
||||
} |
||||
if ok, err := s.verifyUpdate(update); err != nil { |
||||
return err |
||||
} else if !ok { |
||||
return ErrInvalidUpdate |
||||
} |
||||
addCommittee := !s.committees.periods.contains(period+1) || reorg |
||||
if addCommittee { |
||||
if nextCommittee == nil { |
||||
return ErrNeedCommittee |
||||
} |
||||
if nextCommittee.Root() != update.NextSyncCommitteeRoot { |
||||
return ErrWrongCommitteeRoot |
||||
} |
||||
} |
||||
if reorg { |
||||
if err := s.rollback(period + 1); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
batch := s.db.NewBatch() |
||||
if addCommittee { |
||||
if err := s.committees.add(batch, period+1, nextCommittee); err != nil { |
||||
return err |
||||
} |
||||
s.committeeCache.Remove(period + 1) |
||||
} |
||||
if err := s.updates.add(batch, period, update); err != nil { |
||||
return err |
||||
} |
||||
if err := batch.Write(); err != nil { |
||||
log.Error("Error writing batch into chain database", "error", err) |
||||
return err |
||||
} |
||||
log.Info("Inserted new committee update", "period", period, "next committee root", update.NextSyncCommitteeRoot) |
||||
return nil |
||||
} |
||||
|
||||
// NextSyncPeriod returns the next period where an update can be added and also
|
||||
// whether the chain is initialized at all.
|
||||
func (s *CommitteeChain) NextSyncPeriod() (uint64, bool) { |
||||
s.chainmu.RLock() |
||||
defer s.chainmu.RUnlock() |
||||
|
||||
if s.committees.periods.isEmpty() { |
||||
return 0, false |
||||
} |
||||
if !s.updates.periods.isEmpty() { |
||||
return s.updates.periods.End, true |
||||
} |
||||
return s.committees.periods.End - 1, true |
||||
} |
||||
|
||||
// rollback removes all committees and fixed roots from the given period and updates
|
||||
// starting from the previous period.
|
||||
func (s *CommitteeChain) rollback(period uint64) error { |
||||
max := s.updates.periods.End + 1 |
||||
if s.committees.periods.End > max { |
||||
max = s.committees.periods.End |
||||
} |
||||
if s.fixedCommitteeRoots.periods.End > max { |
||||
max = s.fixedCommitteeRoots.periods.End |
||||
} |
||||
for max > period { |
||||
max-- |
||||
batch := s.db.NewBatch() |
||||
s.deleteCommitteesFrom(batch, max) |
||||
s.fixedCommitteeRoots.deleteFrom(batch, max) |
||||
if max > 0 { |
||||
s.updates.deleteFrom(batch, max-1) |
||||
} |
||||
if err := batch.Write(); err != nil { |
||||
log.Error("Error writing batch into chain database", "error", err) |
||||
return err |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// getCommitteeRoot returns the committee root at the given period, either fixed,
|
||||
// proven by a previous update or both. It returns an empty hash if the committee
|
||||
// root is unknown.
|
||||
func (s *CommitteeChain) getCommitteeRoot(period uint64) common.Hash { |
||||
if root, ok := s.fixedCommitteeRoots.get(s.db, period); ok || period == 0 { |
||||
return root |
||||
} |
||||
if update, ok := s.updates.get(s.db, period-1); ok { |
||||
return update.NextSyncCommitteeRoot |
||||
} |
||||
return common.Hash{} |
||||
} |
||||
|
||||
// getSyncCommittee returns the deserialized sync committee at the given period.
|
||||
func (s *CommitteeChain) getSyncCommittee(period uint64) (syncCommittee, error) { |
||||
if c, ok := s.committeeCache.Get(period); ok { |
||||
return c, nil |
||||
} |
||||
if sc, ok := s.committees.get(s.db, period); ok { |
||||
c, err := s.sigVerifier.deserializeSyncCommittee(sc) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("Sync committee #%d deserialization error: %v", period, err) |
||||
} |
||||
s.committeeCache.Add(period, c) |
||||
return c, nil |
||||
} |
||||
return nil, fmt.Errorf("Missing serialized sync committee #%d", period) |
||||
} |
||||
|
||||
// VerifySignedHeader returns true if the given signed header has a valid signature
|
||||
// according to the local committee chain. The caller should ensure that the
|
||||
// committees advertised by the same source where the signed header came from are
|
||||
// synced before verifying the signature.
|
||||
// The age of the header is also returned (the time elapsed since the beginning
|
||||
// of the given slot, according to the local system clock). If enforceTime is
|
||||
// true then negative age (future) headers are rejected.
|
||||
func (s *CommitteeChain) VerifySignedHeader(head types.SignedHeader) (bool, time.Duration, error) { |
||||
s.chainmu.RLock() |
||||
defer s.chainmu.RUnlock() |
||||
|
||||
return s.verifySignedHeader(head) |
||||
} |
||||
|
||||
func (s *CommitteeChain) verifySignedHeader(head types.SignedHeader) (bool, time.Duration, error) { |
||||
var age time.Duration |
||||
now := s.unixNano() |
||||
if head.Header.Slot < (uint64(now-math.MinInt64)/uint64(time.Second)-s.config.GenesisTime)/12 { |
||||
age = time.Duration(now - int64(time.Second)*int64(s.config.GenesisTime+head.Header.Slot*12)) |
||||
} else { |
||||
age = time.Duration(math.MinInt64) |
||||
} |
||||
if s.enforceTime && age < 0 { |
||||
return false, age, nil |
||||
} |
||||
committee, err := s.getSyncCommittee(types.SyncPeriod(head.SignatureSlot)) |
||||
if err != nil { |
||||
return false, 0, err |
||||
} |
||||
if committee == nil { |
||||
return false, age, nil |
||||
} |
||||
if signingRoot, err := s.config.Forks.SigningRoot(head.Header); err == nil { |
||||
return s.sigVerifier.verifySignature(committee, signingRoot, &head.Signature), age, nil |
||||
} |
||||
return false, age, nil |
||||
} |
||||
|
||||
// verifyUpdate checks whether the header signature is correct and the update
|
||||
// fits into the specified constraints (assumes that the update has been
|
||||
// successfully validated previously)
|
||||
func (s *CommitteeChain) verifyUpdate(update *types.LightClientUpdate) (bool, error) { |
||||
// Note: SignatureSlot determines the sync period of the committee used for signature
|
||||
// verification. Though in reality SignatureSlot is always bigger than update.Header.Slot,
|
||||
// setting them as equal here enforces the rule that they have to be in the same sync
|
||||
// period in order for the light client update proof to be meaningful.
|
||||
ok, age, err := s.verifySignedHeader(update.AttestedHeader) |
||||
if age < 0 { |
||||
log.Warn("Future committee update received", "age", age) |
||||
} |
||||
return ok, err |
||||
} |
@ -0,0 +1,356 @@ |
||||
// Copyright 2022 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package light |
||||
|
||||
import ( |
||||
"crypto/rand" |
||||
"testing" |
||||
"time" |
||||
|
||||
"github.com/ethereum/go-ethereum/beacon/params" |
||||
"github.com/ethereum/go-ethereum/beacon/types" |
||||
"github.com/ethereum/go-ethereum/common/mclock" |
||||
"github.com/ethereum/go-ethereum/ethdb/memorydb" |
||||
) |
||||
|
||||
var ( |
||||
testGenesis = newTestGenesis() |
||||
testGenesis2 = newTestGenesis() |
||||
|
||||
tfBase = newTestForks(testGenesis, types.Forks{ |
||||
&types.Fork{Epoch: 0, Version: []byte{0}}, |
||||
}) |
||||
tfAlternative = newTestForks(testGenesis, types.Forks{ |
||||
&types.Fork{Epoch: 0, Version: []byte{0}}, |
||||
&types.Fork{Epoch: 0x700, Version: []byte{1}}, |
||||
}) |
||||
tfAnotherGenesis = newTestForks(testGenesis2, types.Forks{ |
||||
&types.Fork{Epoch: 0, Version: []byte{0}}, |
||||
}) |
||||
|
||||
tcBase = newTestCommitteeChain(nil, tfBase, true, 0, 10, 400, false) |
||||
tcBaseWithInvalidUpdates = newTestCommitteeChain(tcBase, tfBase, false, 5, 10, 200, false) // signer count too low
|
||||
tcBaseWithBetterUpdates = newTestCommitteeChain(tcBase, tfBase, false, 5, 10, 440, false) |
||||
tcReorgWithWorseUpdates = newTestCommitteeChain(tcBase, tfBase, true, 5, 10, 400, false) |
||||
tcReorgWithWorseUpdates2 = newTestCommitteeChain(tcBase, tfBase, true, 5, 10, 380, false) |
||||
tcReorgWithBetterUpdates = newTestCommitteeChain(tcBase, tfBase, true, 5, 10, 420, false) |
||||
tcReorgWithFinalizedUpdates = newTestCommitteeChain(tcBase, tfBase, true, 5, 10, 400, true) |
||||
tcFork = newTestCommitteeChain(tcBase, tfAlternative, true, 7, 10, 400, false) |
||||
tcAnotherGenesis = newTestCommitteeChain(nil, tfAnotherGenesis, true, 0, 10, 400, false) |
||||
) |
||||
|
||||
func TestCommitteeChainFixedCommitteeRoots(t *testing.T) { |
||||
for _, reload := range []bool{false, true} { |
||||
c := newCommitteeChainTest(t, tfBase, 300, true) |
||||
c.setClockPeriod(7) |
||||
c.addFixedCommitteeRoot(tcBase, 4, nil) |
||||
c.addFixedCommitteeRoot(tcBase, 5, nil) |
||||
c.addFixedCommitteeRoot(tcBase, 6, nil) |
||||
c.addFixedCommitteeRoot(tcBase, 8, ErrInvalidPeriod) // range has to be continuous
|
||||
c.addFixedCommitteeRoot(tcBase, 3, nil) |
||||
c.addFixedCommitteeRoot(tcBase, 2, nil) |
||||
if reload { |
||||
c.reloadChain() |
||||
} |
||||
c.addCommittee(tcBase, 4, nil) |
||||
c.addCommittee(tcBase, 6, ErrInvalidPeriod) // range has to be continuous
|
||||
c.addCommittee(tcBase, 5, nil) |
||||
c.addCommittee(tcBase, 6, nil) |
||||
c.addCommittee(tcAnotherGenesis, 3, ErrWrongCommitteeRoot) |
||||
c.addCommittee(tcBase, 3, nil) |
||||
if reload { |
||||
c.reloadChain() |
||||
} |
||||
c.verifyRange(tcBase, 3, 6) |
||||
} |
||||
} |
||||
|
||||
func TestCommitteeChainCheckpointSync(t *testing.T) { |
||||
for _, enforceTime := range []bool{false, true} { |
||||
for _, reload := range []bool{false, true} { |
||||
c := newCommitteeChainTest(t, tfBase, 300, enforceTime) |
||||
if enforceTime { |
||||
c.setClockPeriod(6) |
||||
} |
||||
c.insertUpdate(tcBase, 3, true, ErrInvalidPeriod) |
||||
c.addFixedCommitteeRoot(tcBase, 3, nil) |
||||
c.addFixedCommitteeRoot(tcBase, 4, nil) |
||||
c.insertUpdate(tcBase, 4, true, ErrInvalidPeriod) // still no committee
|
||||
c.addCommittee(tcBase, 3, nil) |
||||
c.addCommittee(tcBase, 4, nil) |
||||
if reload { |
||||
c.reloadChain() |
||||
} |
||||
c.verifyRange(tcBase, 3, 4) |
||||
c.insertUpdate(tcBase, 3, false, nil) // update can be added without committee here
|
||||
c.insertUpdate(tcBase, 4, false, ErrNeedCommittee) // but not here as committee 5 is not there yet
|
||||
c.insertUpdate(tcBase, 4, true, nil) |
||||
c.verifyRange(tcBase, 3, 5) |
||||
c.insertUpdate(tcBaseWithInvalidUpdates, 5, true, ErrInvalidUpdate) // signer count too low
|
||||
c.insertUpdate(tcBase, 5, true, nil) |
||||
if reload { |
||||
c.reloadChain() |
||||
} |
||||
if enforceTime { |
||||
c.insertUpdate(tcBase, 6, true, ErrInvalidUpdate) // future update rejected
|
||||
c.setClockPeriod(7) |
||||
} |
||||
c.insertUpdate(tcBase, 6, true, nil) // when the time comes it's accepted
|
||||
if reload { |
||||
c.reloadChain() |
||||
} |
||||
if enforceTime { |
||||
c.verifyRange(tcBase, 3, 6) // committee 7 is there but still in the future
|
||||
c.setClockPeriod(8) |
||||
} |
||||
c.verifyRange(tcBase, 3, 7) // now period 7 can also be verified
|
||||
// try reverse syncing an update
|
||||
c.insertUpdate(tcBase, 2, false, ErrInvalidPeriod) // fixed committee is needed first
|
||||
c.addFixedCommitteeRoot(tcBase, 2, nil) |
||||
c.addCommittee(tcBase, 2, nil) |
||||
c.insertUpdate(tcBase, 2, false, nil) |
||||
c.verifyRange(tcBase, 2, 7) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestCommitteeChainReorg(t *testing.T) { |
||||
for _, reload := range []bool{false, true} { |
||||
for _, addBetterUpdates := range []bool{false, true} { |
||||
c := newCommitteeChainTest(t, tfBase, 300, true) |
||||
c.setClockPeriod(11) |
||||
c.addFixedCommitteeRoot(tcBase, 3, nil) |
||||
c.addFixedCommitteeRoot(tcBase, 4, nil) |
||||
c.addCommittee(tcBase, 3, nil) |
||||
for period := uint64(3); period < 10; period++ { |
||||
c.insertUpdate(tcBase, period, true, nil) |
||||
} |
||||
if reload { |
||||
c.reloadChain() |
||||
} |
||||
c.verifyRange(tcBase, 3, 10) |
||||
c.insertUpdate(tcReorgWithWorseUpdates, 5, true, ErrCannotReorg) |
||||
c.insertUpdate(tcReorgWithWorseUpdates2, 5, true, ErrCannotReorg) |
||||
if addBetterUpdates { |
||||
// add better updates for the base chain and expect first reorg to fail
|
||||
// (only add updates as committees should be the same)
|
||||
for period := uint64(5); period < 10; period++ { |
||||
c.insertUpdate(tcBaseWithBetterUpdates, period, false, nil) |
||||
} |
||||
if reload { |
||||
c.reloadChain() |
||||
} |
||||
c.verifyRange(tcBase, 3, 10) // still on the same chain
|
||||
c.insertUpdate(tcReorgWithBetterUpdates, 5, true, ErrCannotReorg) |
||||
} else { |
||||
// reorg with better updates
|
||||
c.insertUpdate(tcReorgWithBetterUpdates, 5, false, ErrNeedCommittee) |
||||
c.verifyRange(tcBase, 3, 10) // no success yet, still on the base chain
|
||||
c.verifyRange(tcReorgWithBetterUpdates, 3, 5) |
||||
c.insertUpdate(tcReorgWithBetterUpdates, 5, true, nil) |
||||
// successful reorg, base chain should only match before the reorg period
|
||||
if reload { |
||||
c.reloadChain() |
||||
} |
||||
c.verifyRange(tcBase, 3, 5) |
||||
c.verifyRange(tcReorgWithBetterUpdates, 3, 6) |
||||
for period := uint64(6); period < 10; period++ { |
||||
c.insertUpdate(tcReorgWithBetterUpdates, period, true, nil) |
||||
} |
||||
c.verifyRange(tcReorgWithBetterUpdates, 3, 10) |
||||
} |
||||
// reorg with finalized updates; should succeed even if base chain updates
|
||||
// have been improved because a finalized update beats everything else
|
||||
c.insertUpdate(tcReorgWithFinalizedUpdates, 5, false, ErrNeedCommittee) |
||||
c.insertUpdate(tcReorgWithFinalizedUpdates, 5, true, nil) |
||||
if reload { |
||||
c.reloadChain() |
||||
} |
||||
c.verifyRange(tcReorgWithFinalizedUpdates, 3, 6) |
||||
for period := uint64(6); period < 10; period++ { |
||||
c.insertUpdate(tcReorgWithFinalizedUpdates, period, true, nil) |
||||
} |
||||
c.verifyRange(tcReorgWithFinalizedUpdates, 3, 10) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestCommitteeChainFork(t *testing.T) { |
||||
c := newCommitteeChainTest(t, tfAlternative, 300, true) |
||||
c.setClockPeriod(11) |
||||
// trying to sync a chain on an alternative fork with the base chain data
|
||||
c.addFixedCommitteeRoot(tcBase, 0, nil) |
||||
c.addFixedCommitteeRoot(tcBase, 1, nil) |
||||
c.addCommittee(tcBase, 0, nil) |
||||
// shared section should sync without errors
|
||||
for period := uint64(0); period < 7; period++ { |
||||
c.insertUpdate(tcBase, period, true, nil) |
||||
} |
||||
c.insertUpdate(tcBase, 7, true, ErrInvalidUpdate) // wrong fork
|
||||
// committee root #7 is still the same but signatures are already signed with
|
||||
// a different fork id so period 7 should only verify on the alternative fork
|
||||
c.verifyRange(tcBase, 0, 6) |
||||
c.verifyRange(tcFork, 0, 7) |
||||
for period := uint64(7); period < 10; period++ { |
||||
c.insertUpdate(tcFork, period, true, nil) |
||||
} |
||||
c.verifyRange(tcFork, 0, 10) |
||||
// reload the chain while switching to the base fork
|
||||
c.config = tfBase |
||||
c.reloadChain() |
||||
// updates 7..9 should be rolled back now
|
||||
c.verifyRange(tcFork, 0, 6) // again, period 7 only verifies on the right fork
|
||||
c.verifyRange(tcBase, 0, 7) |
||||
c.insertUpdate(tcFork, 7, true, ErrInvalidUpdate) // wrong fork
|
||||
for period := uint64(7); period < 10; period++ { |
||||
c.insertUpdate(tcBase, period, true, nil) |
||||
} |
||||
c.verifyRange(tcBase, 0, 10) |
||||
} |
||||
|
||||
type committeeChainTest struct { |
||||
t *testing.T |
||||
db *memorydb.Database |
||||
clock *mclock.Simulated |
||||
config types.ChainConfig |
||||
signerThreshold int |
||||
enforceTime bool |
||||
chain *CommitteeChain |
||||
} |
||||
|
||||
func newCommitteeChainTest(t *testing.T, config types.ChainConfig, signerThreshold int, enforceTime bool) *committeeChainTest { |
||||
c := &committeeChainTest{ |
||||
t: t, |
||||
db: memorydb.New(), |
||||
clock: &mclock.Simulated{}, |
||||
config: config, |
||||
signerThreshold: signerThreshold, |
||||
enforceTime: enforceTime, |
||||
} |
||||
c.chain = newCommitteeChain(c.db, &config, signerThreshold, enforceTime, dummyVerifier{}, c.clock, func() int64 { return int64(c.clock.Now()) }) |
||||
return c |
||||
} |
||||
|
||||
func (c *committeeChainTest) reloadChain() { |
||||
c.chain = newCommitteeChain(c.db, &c.config, c.signerThreshold, c.enforceTime, dummyVerifier{}, c.clock, func() int64 { return int64(c.clock.Now()) }) |
||||
} |
||||
|
||||
func (c *committeeChainTest) setClockPeriod(period float64) { |
||||
target := mclock.AbsTime(period * float64(time.Second*12*params.SyncPeriodLength)) |
||||
wait := time.Duration(target - c.clock.Now()) |
||||
if wait < 0 { |
||||
c.t.Fatalf("Invalid setClockPeriod") |
||||
} |
||||
c.clock.Run(wait) |
||||
} |
||||
|
||||
func (c *committeeChainTest) addFixedCommitteeRoot(tc *testCommitteeChain, period uint64, expErr error) { |
||||
if err := c.chain.addFixedCommitteeRoot(period, tc.periods[period].committee.Root()); err != expErr { |
||||
c.t.Errorf("Incorrect error output from addFixedCommitteeRoot at period %d (expected %v, got %v)", period, expErr, err) |
||||
} |
||||
} |
||||
|
||||
func (c *committeeChainTest) addCommittee(tc *testCommitteeChain, period uint64, expErr error) { |
||||
if err := c.chain.addCommittee(period, tc.periods[period].committee); err != expErr { |
||||
c.t.Errorf("Incorrect error output from addCommittee at period %d (expected %v, got %v)", period, expErr, err) |
||||
} |
||||
} |
||||
|
||||
func (c *committeeChainTest) insertUpdate(tc *testCommitteeChain, period uint64, addCommittee bool, expErr error) { |
||||
var committee *types.SerializedSyncCommittee |
||||
if addCommittee { |
||||
committee = tc.periods[period+1].committee |
||||
} |
||||
if err := c.chain.InsertUpdate(tc.periods[period].update, committee); err != expErr { |
||||
c.t.Errorf("Incorrect error output from InsertUpdate at period %d (expected %v, got %v)", period, expErr, err) |
||||
} |
||||
} |
||||
|
||||
func (c *committeeChainTest) verifySignedHeader(tc *testCommitteeChain, period float64, expOk bool) { |
||||
slot := uint64(period * float64(params.SyncPeriodLength)) |
||||
signedHead := GenerateTestSignedHeader(types.Header{Slot: slot}, &tc.config, tc.periods[types.SyncPeriod(slot)].committee, slot+1, 400) |
||||
if ok, _, _ := c.chain.VerifySignedHeader(signedHead); ok != expOk { |
||||
c.t.Errorf("Incorrect output from VerifySignedHeader at period %f (expected %v, got %v)", period, expOk, ok) |
||||
} |
||||
} |
||||
|
||||
func (c *committeeChainTest) verifyRange(tc *testCommitteeChain, begin, end uint64) { |
||||
if begin > 0 { |
||||
c.verifySignedHeader(tc, float64(begin)-0.5, false) |
||||
} |
||||
for period := begin; period <= end; period++ { |
||||
c.verifySignedHeader(tc, float64(period)+0.5, true) |
||||
} |
||||
c.verifySignedHeader(tc, float64(end)+1.5, false) |
||||
} |
||||
|
||||
func newTestGenesis() types.ChainConfig { |
||||
var config types.ChainConfig |
||||
rand.Read(config.GenesisValidatorsRoot[:]) |
||||
return config |
||||
} |
||||
|
||||
func newTestForks(config types.ChainConfig, forks types.Forks) types.ChainConfig { |
||||
for _, fork := range forks { |
||||
config.AddFork(fork.Name, fork.Epoch, fork.Version) |
||||
} |
||||
return config |
||||
} |
||||
|
||||
func newTestCommitteeChain(parent *testCommitteeChain, config types.ChainConfig, newCommittees bool, begin, end int, signerCount int, finalizedHeader bool) *testCommitteeChain { |
||||
tc := &testCommitteeChain{ |
||||
config: config, |
||||
} |
||||
if parent != nil { |
||||
tc.periods = make([]testPeriod, len(parent.periods)) |
||||
copy(tc.periods, parent.periods) |
||||
} |
||||
if newCommittees { |
||||
if begin == 0 { |
||||
tc.fillCommittees(begin, end+1) |
||||
} else { |
||||
tc.fillCommittees(begin+1, end+1) |
||||
} |
||||
} |
||||
tc.fillUpdates(begin, end, signerCount, finalizedHeader) |
||||
return tc |
||||
} |
||||
|
||||
type testPeriod struct { |
||||
committee *types.SerializedSyncCommittee |
||||
update *types.LightClientUpdate |
||||
} |
||||
|
||||
type testCommitteeChain struct { |
||||
periods []testPeriod |
||||
config types.ChainConfig |
||||
} |
||||
|
||||
func (tc *testCommitteeChain) fillCommittees(begin, end int) { |
||||
if len(tc.periods) <= end { |
||||
tc.periods = append(tc.periods, make([]testPeriod, end+1-len(tc.periods))...) |
||||
} |
||||
for i := begin; i <= end; i++ { |
||||
tc.periods[i].committee = GenerateTestCommittee() |
||||
} |
||||
} |
||||
|
||||
func (tc *testCommitteeChain) fillUpdates(begin, end int, signerCount int, finalizedHeader bool) { |
||||
for i := begin; i <= end; i++ { |
||||
tc.periods[i].update = GenerateTestUpdate(&tc.config, uint64(i), tc.periods[i].committee, tc.periods[i+1].committee, signerCount, finalizedHeader) |
||||
} |
||||
} |
@ -0,0 +1,78 @@ |
||||
// Copyright 2023 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package light |
||||
|
||||
// periodRange represents a (possibly zero-length) range of integers (sync periods).
|
||||
type periodRange struct { |
||||
Start, End uint64 |
||||
} |
||||
|
||||
// isEmpty returns true if the length of the range is zero.
|
||||
func (a periodRange) isEmpty() bool { |
||||
return a.End == a.Start |
||||
} |
||||
|
||||
// contains returns true if the range includes the given period.
|
||||
func (a periodRange) contains(period uint64) bool { |
||||
return period >= a.Start && period < a.End |
||||
} |
||||
|
||||
// canExpand returns true if the range includes or can be expanded with the given
|
||||
// period (either the range is empty or the given period is inside, right before or
|
||||
// right after the range).
|
||||
func (a periodRange) canExpand(period uint64) bool { |
||||
return a.isEmpty() || (period+1 >= a.Start && period <= a.End) |
||||
} |
||||
|
||||
// expand expands the range with the given period.
|
||||
// This method assumes that canExpand returned true: otherwise this is a no-op.
|
||||
func (a *periodRange) expand(period uint64) { |
||||
if a.isEmpty() { |
||||
a.Start, a.End = period, period+1 |
||||
return |
||||
} |
||||
if a.Start == period+1 { |
||||
a.Start-- |
||||
} |
||||
if a.End == period { |
||||
a.End++ |
||||
} |
||||
} |
||||
|
||||
// split splits the range into two ranges. The 'fromPeriod' will be the first
|
||||
// element in the second range (if present).
|
||||
// The original range is unchanged by this operation
|
||||
func (a *periodRange) split(fromPeriod uint64) (periodRange, periodRange) { |
||||
if fromPeriod <= a.Start { |
||||
// First range empty, everything in second range,
|
||||
return periodRange{}, *a |
||||
} |
||||
if fromPeriod >= a.End { |
||||
// Second range empty, everything in first range,
|
||||
return *a, periodRange{} |
||||
} |
||||
x := periodRange{a.Start, fromPeriod} |
||||
y := periodRange{fromPeriod, a.End} |
||||
return x, y |
||||
} |
||||
|
||||
// each invokes the supplied function fn once per period in range
|
||||
func (a *periodRange) each(fn func(uint64)) { |
||||
for p := a.Start; p < a.End; p++ { |
||||
fn(p) |
||||
} |
||||
} |
@ -0,0 +1,152 @@ |
||||
// Copyright 2023 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package light |
||||
|
||||
import ( |
||||
"crypto/rand" |
||||
"crypto/sha256" |
||||
mrand "math/rand" |
||||
|
||||
"github.com/ethereum/go-ethereum/beacon/merkle" |
||||
"github.com/ethereum/go-ethereum/beacon/params" |
||||
"github.com/ethereum/go-ethereum/beacon/types" |
||||
"github.com/ethereum/go-ethereum/common" |
||||
) |
||||
|
||||
func GenerateTestCommittee() *types.SerializedSyncCommittee { |
||||
s := new(types.SerializedSyncCommittee) |
||||
rand.Read(s[:32]) |
||||
return s |
||||
} |
||||
|
||||
func GenerateTestUpdate(config *types.ChainConfig, period uint64, committee, nextCommittee *types.SerializedSyncCommittee, signerCount int, finalizedHeader bool) *types.LightClientUpdate { |
||||
update := new(types.LightClientUpdate) |
||||
update.NextSyncCommitteeRoot = nextCommittee.Root() |
||||
var attestedHeader types.Header |
||||
if finalizedHeader { |
||||
update.FinalizedHeader = new(types.Header) |
||||
*update.FinalizedHeader, update.NextSyncCommitteeBranch = makeTestHeaderWithMerkleProof(types.SyncPeriodStart(period)+100, params.StateIndexNextSyncCommittee, merkle.Value(update.NextSyncCommitteeRoot)) |
||||
attestedHeader, update.FinalityBranch = makeTestHeaderWithMerkleProof(types.SyncPeriodStart(period)+200, params.StateIndexFinalBlock, merkle.Value(update.FinalizedHeader.Hash())) |
||||
} else { |
||||
attestedHeader, update.NextSyncCommitteeBranch = makeTestHeaderWithMerkleProof(types.SyncPeriodStart(period)+2000, params.StateIndexNextSyncCommittee, merkle.Value(update.NextSyncCommitteeRoot)) |
||||
} |
||||
update.AttestedHeader = GenerateTestSignedHeader(attestedHeader, config, committee, attestedHeader.Slot+1, signerCount) |
||||
return update |
||||
} |
||||
|
||||
func GenerateTestSignedHeader(header types.Header, config *types.ChainConfig, committee *types.SerializedSyncCommittee, signatureSlot uint64, signerCount int) types.SignedHeader { |
||||
bitmask := makeBitmask(signerCount) |
||||
signingRoot, _ := config.Forks.SigningRoot(header) |
||||
c, _ := dummyVerifier{}.deserializeSyncCommittee(committee) |
||||
return types.SignedHeader{ |
||||
Header: header, |
||||
Signature: types.SyncAggregate{ |
||||
Signers: bitmask, |
||||
Signature: makeDummySignature(c.(dummySyncCommittee), signingRoot, bitmask), |
||||
}, |
||||
SignatureSlot: signatureSlot, |
||||
} |
||||
} |
||||
|
||||
func GenerateTestCheckpoint(period uint64, committee *types.SerializedSyncCommittee) *types.BootstrapData { |
||||
header, branch := makeTestHeaderWithMerkleProof(types.SyncPeriodStart(period)+200, params.StateIndexSyncCommittee, merkle.Value(committee.Root())) |
||||
return &types.BootstrapData{ |
||||
Header: header, |
||||
Committee: committee, |
||||
CommitteeRoot: committee.Root(), |
||||
CommitteeBranch: branch, |
||||
} |
||||
} |
||||
|
||||
func makeBitmask(signerCount int) (bitmask [params.SyncCommitteeBitmaskSize]byte) { |
||||
for i := 0; i < params.SyncCommitteeSize; i++ { |
||||
if mrand.Intn(params.SyncCommitteeSize-i) < signerCount { |
||||
bitmask[i/8] += byte(1) << (i & 7) |
||||
signerCount-- |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
func makeTestHeaderWithMerkleProof(slot, index uint64, value merkle.Value) (types.Header, merkle.Values) { |
||||
var branch merkle.Values |
||||
hasher := sha256.New() |
||||
for index > 1 { |
||||
var proofHash merkle.Value |
||||
rand.Read(proofHash[:]) |
||||
hasher.Reset() |
||||
if index&1 == 0 { |
||||
hasher.Write(value[:]) |
||||
hasher.Write(proofHash[:]) |
||||
} else { |
||||
hasher.Write(proofHash[:]) |
||||
hasher.Write(value[:]) |
||||
} |
||||
hasher.Sum(value[:0]) |
||||
index >>= 1 |
||||
branch = append(branch, proofHash) |
||||
} |
||||
return types.Header{Slot: slot, StateRoot: common.Hash(value)}, branch |
||||
} |
||||
|
||||
// syncCommittee holds either a blsSyncCommittee or a fake dummySyncCommittee used for testing
|
||||
type syncCommittee interface{} |
||||
|
||||
// committeeSigVerifier verifies sync committee signatures (either proper BLS
|
||||
// signatures or fake signatures used for testing)
|
||||
type committeeSigVerifier interface { |
||||
deserializeSyncCommittee(s *types.SerializedSyncCommittee) (syncCommittee, error) |
||||
verifySignature(committee syncCommittee, signedRoot common.Hash, aggregate *types.SyncAggregate) bool |
||||
} |
||||
|
||||
// blsVerifier implements committeeSigVerifier
|
||||
type blsVerifier struct{} |
||||
|
||||
// deserializeSyncCommittee implements committeeSigVerifier
|
||||
func (blsVerifier) deserializeSyncCommittee(s *types.SerializedSyncCommittee) (syncCommittee, error) { |
||||
return s.Deserialize() |
||||
} |
||||
|
||||
// verifySignature implements committeeSigVerifier
|
||||
func (blsVerifier) verifySignature(committee syncCommittee, signingRoot common.Hash, aggregate *types.SyncAggregate) bool { |
||||
return committee.(*types.SyncCommittee).VerifySignature(signingRoot, aggregate) |
||||
} |
||||
|
||||
type dummySyncCommittee [32]byte |
||||
|
||||
// dummyVerifier implements committeeSigVerifier
|
||||
type dummyVerifier struct{} |
||||
|
||||
// deserializeSyncCommittee implements committeeSigVerifier
|
||||
func (dummyVerifier) deserializeSyncCommittee(s *types.SerializedSyncCommittee) (syncCommittee, error) { |
||||
var sc dummySyncCommittee |
||||
copy(sc[:], s[:32]) |
||||
return sc, nil |
||||
} |
||||
|
||||
// verifySignature implements committeeSigVerifier
|
||||
func (dummyVerifier) verifySignature(committee syncCommittee, signingRoot common.Hash, aggregate *types.SyncAggregate) bool { |
||||
return aggregate.Signature == makeDummySignature(committee.(dummySyncCommittee), signingRoot, aggregate.Signers) |
||||
} |
||||
|
||||
func makeDummySignature(committee dummySyncCommittee, signingRoot common.Hash, bitmask [params.SyncCommitteeBitmaskSize]byte) (sig [params.BLSSignatureSize]byte) { |
||||
for i, b := range committee[:] { |
||||
sig[i] = b ^ signingRoot[i] |
||||
} |
||||
copy(sig[32:], bitmask[:]) |
||||
return |
||||
} |
@ -0,0 +1,361 @@ |
||||
// Copyright 2023 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethtest |
||||
|
||||
import ( |
||||
"crypto/ecdsa" |
||||
"errors" |
||||
"fmt" |
||||
"net" |
||||
"reflect" |
||||
"time" |
||||
|
||||
"github.com/davecgh/go-spew/spew" |
||||
"github.com/ethereum/go-ethereum/crypto" |
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth" |
||||
"github.com/ethereum/go-ethereum/eth/protocols/snap" |
||||
"github.com/ethereum/go-ethereum/p2p" |
||||
"github.com/ethereum/go-ethereum/p2p/rlpx" |
||||
"github.com/ethereum/go-ethereum/rlp" |
||||
) |
||||
|
||||
var ( |
||||
pretty = spew.ConfigState{ |
||||
Indent: " ", |
||||
DisableCapacities: true, |
||||
DisablePointerAddresses: true, |
||||
SortKeys: true, |
||||
} |
||||
timeout = 2 * time.Second |
||||
) |
||||
|
||||
// dial attempts to dial the given node and perform a handshake, returning the
|
||||
// created Conn if successful.
|
||||
func (s *Suite) dial() (*Conn, error) { |
||||
key, _ := crypto.GenerateKey() |
||||
return s.dialAs(key) |
||||
} |
||||
|
||||
// dialAs attempts to dial a given node and perform a handshake using the given
|
||||
// private key.
|
||||
func (s *Suite) dialAs(key *ecdsa.PrivateKey) (*Conn, error) { |
||||
fd, err := net.Dial("tcp", fmt.Sprintf("%v:%d", s.Dest.IP(), s.Dest.TCP())) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
conn := Conn{Conn: rlpx.NewConn(fd, s.Dest.Pubkey())} |
||||
conn.ourKey = key |
||||
_, err = conn.Handshake(conn.ourKey) |
||||
if err != nil { |
||||
conn.Close() |
||||
return nil, err |
||||
} |
||||
conn.caps = []p2p.Cap{ |
||||
{Name: "eth", Version: 67}, |
||||
{Name: "eth", Version: 68}, |
||||
} |
||||
conn.ourHighestProtoVersion = 68 |
||||
return &conn, nil |
||||
} |
||||
|
||||
// dialSnap creates a connection with snap/1 capability.
|
||||
func (s *Suite) dialSnap() (*Conn, error) { |
||||
conn, err := s.dial() |
||||
if err != nil { |
||||
return nil, fmt.Errorf("dial failed: %v", err) |
||||
} |
||||
conn.caps = append(conn.caps, p2p.Cap{Name: "snap", Version: 1}) |
||||
conn.ourHighestSnapProtoVersion = 1 |
||||
return conn, nil |
||||
} |
||||
|
||||
// Conn represents an individual connection with a peer
|
||||
type Conn struct { |
||||
*rlpx.Conn |
||||
ourKey *ecdsa.PrivateKey |
||||
negotiatedProtoVersion uint |
||||
negotiatedSnapProtoVersion uint |
||||
ourHighestProtoVersion uint |
||||
ourHighestSnapProtoVersion uint |
||||
caps []p2p.Cap |
||||
} |
||||
|
||||
// Read reads a packet from the connection.
|
||||
func (c *Conn) Read() (uint64, []byte, error) { |
||||
c.SetReadDeadline(time.Now().Add(timeout)) |
||||
code, data, _, err := c.Conn.Read() |
||||
if err != nil { |
||||
return 0, nil, err |
||||
} |
||||
return code, data, nil |
||||
} |
||||
|
||||
// ReadMsg attempts to read a devp2p message with a specific code.
|
||||
func (c *Conn) ReadMsg(proto Proto, code uint64, msg any) error { |
||||
c.SetReadDeadline(time.Now().Add(timeout)) |
||||
for { |
||||
got, data, err := c.Read() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if protoOffset(proto)+code == got { |
||||
return rlp.DecodeBytes(data, msg) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Write writes a eth packet to the connection.
|
||||
func (c *Conn) Write(proto Proto, code uint64, msg any) error { |
||||
c.SetWriteDeadline(time.Now().Add(timeout)) |
||||
payload, err := rlp.EncodeToBytes(msg) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
_, err = c.Conn.Write(protoOffset(proto)+code, payload) |
||||
return err |
||||
} |
||||
|
||||
// ReadEth reads an Eth sub-protocol wire message.
|
||||
func (c *Conn) ReadEth() (any, error) { |
||||
c.SetReadDeadline(time.Now().Add(timeout)) |
||||
for { |
||||
code, data, _, err := c.Conn.Read() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if code == pingMsg { |
||||
c.Write(baseProto, pongMsg, []byte{}) |
||||
continue |
||||
} |
||||
if getProto(code) != ethProto { |
||||
// Read until eth message.
|
||||
continue |
||||
} |
||||
code -= baseProtoLen |
||||
|
||||
var msg any |
||||
switch int(code) { |
||||
case eth.StatusMsg: |
||||
msg = new(eth.StatusPacket) |
||||
case eth.GetBlockHeadersMsg: |
||||
msg = new(eth.GetBlockHeadersPacket) |
||||
case eth.BlockHeadersMsg: |
||||
msg = new(eth.BlockHeadersPacket) |
||||
case eth.GetBlockBodiesMsg: |
||||
msg = new(eth.GetBlockBodiesPacket) |
||||
case eth.BlockBodiesMsg: |
||||
msg = new(eth.BlockBodiesPacket) |
||||
case eth.NewBlockMsg: |
||||
msg = new(eth.NewBlockPacket) |
||||
case eth.NewBlockHashesMsg: |
||||
msg = new(eth.NewBlockHashesPacket) |
||||
case eth.TransactionsMsg: |
||||
msg = new(eth.TransactionsPacket) |
||||
case eth.NewPooledTransactionHashesMsg: |
||||
msg = new(eth.NewPooledTransactionHashesPacket68) |
||||
case eth.GetPooledTransactionsMsg: |
||||
msg = new(eth.GetPooledTransactionsPacket) |
||||
case eth.PooledTransactionsMsg: |
||||
msg = new(eth.PooledTransactionsPacket) |
||||
default: |
||||
panic(fmt.Sprintf("unhandled eth msg code %d", code)) |
||||
} |
||||
if err := rlp.DecodeBytes(data, msg); err != nil { |
||||
return nil, fmt.Errorf("unable to decode eth msg: %v", err) |
||||
} |
||||
return msg, nil |
||||
} |
||||
} |
||||
|
||||
// ReadSnap reads a snap/1 response with the given id from the connection.
|
||||
func (c *Conn) ReadSnap() (any, error) { |
||||
c.SetReadDeadline(time.Now().Add(timeout)) |
||||
for { |
||||
code, data, _, err := c.Conn.Read() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if getProto(code) != snapProto { |
||||
// Read until snap message.
|
||||
continue |
||||
} |
||||
code -= baseProtoLen + ethProtoLen |
||||
|
||||
var msg any |
||||
switch int(code) { |
||||
case snap.GetAccountRangeMsg: |
||||
msg = new(snap.GetAccountRangePacket) |
||||
case snap.AccountRangeMsg: |
||||
msg = new(snap.AccountRangePacket) |
||||
case snap.GetStorageRangesMsg: |
||||
msg = new(snap.GetStorageRangesPacket) |
||||
case snap.StorageRangesMsg: |
||||
msg = new(snap.StorageRangesPacket) |
||||
case snap.GetByteCodesMsg: |
||||
msg = new(snap.GetByteCodesPacket) |
||||
case snap.ByteCodesMsg: |
||||
msg = new(snap.ByteCodesPacket) |
||||
case snap.GetTrieNodesMsg: |
||||
msg = new(snap.GetTrieNodesPacket) |
||||
case snap.TrieNodesMsg: |
||||
msg = new(snap.TrieNodesPacket) |
||||
default: |
||||
panic(fmt.Errorf("unhandled snap code: %d", code)) |
||||
} |
||||
if err := rlp.DecodeBytes(data, msg); err != nil { |
||||
return nil, fmt.Errorf("could not rlp decode message: %v", err) |
||||
} |
||||
return msg, nil |
||||
} |
||||
} |
||||
|
||||
// peer performs both the protocol handshake and the status message
|
||||
// exchange with the node in order to peer with it.
|
||||
func (c *Conn) peer(chain *Chain, status *eth.StatusPacket) error { |
||||
if err := c.handshake(); err != nil { |
||||
return fmt.Errorf("handshake failed: %v", err) |
||||
} |
||||
if err := c.statusExchange(chain, status); err != nil { |
||||
return fmt.Errorf("status exchange failed: %v", err) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// handshake performs a protocol handshake with the node.
|
||||
func (c *Conn) handshake() error { |
||||
// Write hello to client.
|
||||
pub0 := crypto.FromECDSAPub(&c.ourKey.PublicKey)[1:] |
||||
ourHandshake := &protoHandshake{ |
||||
Version: 5, |
||||
Caps: c.caps, |
||||
ID: pub0, |
||||
} |
||||
if err := c.Write(baseProto, handshakeMsg, ourHandshake); err != nil { |
||||
return fmt.Errorf("write to connection failed: %v", err) |
||||
} |
||||
// Read hello from client.
|
||||
code, data, err := c.Read() |
||||
if err != nil { |
||||
return fmt.Errorf("erroring reading handshake: %v", err) |
||||
} |
||||
switch code { |
||||
case handshakeMsg: |
||||
msg := new(protoHandshake) |
||||
if err := rlp.DecodeBytes(data, &msg); err != nil { |
||||
return fmt.Errorf("error decoding handshake msg: %v", err) |
||||
} |
||||
// Set snappy if version is at least 5.
|
||||
if msg.Version >= 5 { |
||||
c.SetSnappy(true) |
||||
} |
||||
c.negotiateEthProtocol(msg.Caps) |
||||
if c.negotiatedProtoVersion == 0 { |
||||
return fmt.Errorf("could not negotiate eth protocol (remote caps: %v, local eth version: %v)", msg.Caps, c.ourHighestProtoVersion) |
||||
} |
||||
// If we require snap, verify that it was negotiated.
|
||||
if c.ourHighestSnapProtoVersion != c.negotiatedSnapProtoVersion { |
||||
return fmt.Errorf("could not negotiate snap protocol (remote caps: %v, local snap version: %v)", msg.Caps, c.ourHighestSnapProtoVersion) |
||||
} |
||||
return nil |
||||
default: |
||||
return fmt.Errorf("bad handshake: got msg code %d", code) |
||||
} |
||||
} |
||||
|
||||
// negotiateEthProtocol sets the Conn's eth protocol version to highest
|
||||
// advertised capability from peer.
|
||||
func (c *Conn) negotiateEthProtocol(caps []p2p.Cap) { |
||||
var highestEthVersion uint |
||||
var highestSnapVersion uint |
||||
for _, capability := range caps { |
||||
switch capability.Name { |
||||
case "eth": |
||||
if capability.Version > highestEthVersion && capability.Version <= c.ourHighestProtoVersion { |
||||
highestEthVersion = capability.Version |
||||
} |
||||
case "snap": |
||||
if capability.Version > highestSnapVersion && capability.Version <= c.ourHighestSnapProtoVersion { |
||||
highestSnapVersion = capability.Version |
||||
} |
||||
} |
||||
} |
||||
c.negotiatedProtoVersion = highestEthVersion |
||||
c.negotiatedSnapProtoVersion = highestSnapVersion |
||||
} |
||||
|
||||
// statusExchange performs a `Status` message exchange with the given node.
|
||||
func (c *Conn) statusExchange(chain *Chain, status *eth.StatusPacket) error { |
||||
loop: |
||||
for { |
||||
code, data, err := c.Read() |
||||
if err != nil { |
||||
return fmt.Errorf("failed to read from connection: %w", err) |
||||
} |
||||
switch code { |
||||
case eth.StatusMsg + protoOffset(ethProto): |
||||
msg := new(eth.StatusPacket) |
||||
if err := rlp.DecodeBytes(data, &msg); err != nil { |
||||
return fmt.Errorf("error decoding status packet: %w", err) |
||||
} |
||||
if have, want := msg.Head, chain.blocks[chain.Len()-1].Hash(); have != want { |
||||
return fmt.Errorf("wrong head block in status, want: %#x (block %d) have %#x", |
||||
want, chain.blocks[chain.Len()-1].NumberU64(), have) |
||||
} |
||||
if have, want := msg.TD.Cmp(chain.TD()), 0; have != want { |
||||
return fmt.Errorf("wrong TD in status: have %v want %v", have, want) |
||||
} |
||||
if have, want := msg.ForkID, chain.ForkID(); !reflect.DeepEqual(have, want) { |
||||
return fmt.Errorf("wrong fork ID in status: have %v, want %v", have, want) |
||||
} |
||||
if have, want := msg.ProtocolVersion, c.ourHighestProtoVersion; have != uint32(want) { |
||||
return fmt.Errorf("wrong protocol version: have %v, want %v", have, want) |
||||
} |
||||
break loop |
||||
case discMsg: |
||||
var msg []p2p.DiscReason |
||||
if rlp.DecodeBytes(data, &msg); len(msg) == 0 { |
||||
return errors.New("invalid disconnect message") |
||||
} |
||||
return fmt.Errorf("disconnect received: %v", pretty.Sdump(msg)) |
||||
case pingMsg: |
||||
// TODO (renaynay): in the future, this should be an error
|
||||
// (PINGs should not be a response upon fresh connection)
|
||||
c.Write(baseProto, pongMsg, nil) |
||||
default: |
||||
return fmt.Errorf("bad status message: code %d", code) |
||||
} |
||||
} |
||||
// make sure eth protocol version is set for negotiation
|
||||
if c.negotiatedProtoVersion == 0 { |
||||
return errors.New("eth protocol version must be set in Conn") |
||||
} |
||||
if status == nil { |
||||
// default status message
|
||||
status = ð.StatusPacket{ |
||||
ProtocolVersion: uint32(c.negotiatedProtoVersion), |
||||
NetworkID: chain.config.ChainID.Uint64(), |
||||
TD: chain.TD(), |
||||
Head: chain.blocks[chain.Len()-1].Hash(), |
||||
Genesis: chain.blocks[0].Hash(), |
||||
ForkID: chain.ForkID(), |
||||
} |
||||
} |
||||
if err := c.Write(ethProto, eth.StatusMsg, status); err != nil { |
||||
return fmt.Errorf("write to connection failed: %v", err) |
||||
} |
||||
return nil |
||||
} |
@ -0,0 +1,69 @@ |
||||
// Copyright 2023 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethtest |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"io" |
||||
"net/http" |
||||
"os" |
||||
"path" |
||||
"time" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/golang-jwt/jwt/v4" |
||||
) |
||||
|
||||
// EngineClient is a wrapper around engine-related data.
|
||||
type EngineClient struct { |
||||
url string |
||||
jwt [32]byte |
||||
headfcu []byte |
||||
} |
||||
|
||||
// NewEngineClient creates a new engine client.
|
||||
func NewEngineClient(dir, url, jwt string) (*EngineClient, error) { |
||||
headfcu, err := os.ReadFile(path.Join(dir, "headfcu.json")) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("failed to read headfcu: %w", err) |
||||
} |
||||
return &EngineClient{url, common.HexToHash(jwt), headfcu}, nil |
||||
} |
||||
|
||||
// token returns the jwt claim token for authorization.
|
||||
func (ec *EngineClient) token() string { |
||||
claims := jwt.RegisteredClaims{IssuedAt: jwt.NewNumericDate(time.Now())} |
||||
token, _ := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(ec.jwt[:]) |
||||
return token |
||||
} |
||||
|
||||
// sendForkchoiceUpdated sends an fcu for the head of the generated chain.
|
||||
func (ec *EngineClient) sendForkchoiceUpdated() error { |
||||
var ( |
||||
req, _ = http.NewRequest(http.MethodPost, ec.url, io.NopCloser(bytes.NewReader(ec.headfcu))) |
||||
header = make(http.Header) |
||||
) |
||||
// Set header
|
||||
header.Set("accept", "application/json") |
||||
header.Set("content-type", "application/json") |
||||
header.Set("Authorization", fmt.Sprintf("Bearer %v", ec.token())) |
||||
req.Header = header |
||||
|
||||
_, err := new(http.Client).Do(req) |
||||
return err |
||||
} |
@ -1,650 +0,0 @@ |
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethtest |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
"net" |
||||
"reflect" |
||||
"strings" |
||||
"time" |
||||
|
||||
"github.com/davecgh/go-spew/spew" |
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/crypto" |
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth" |
||||
"github.com/ethereum/go-ethereum/internal/utesting" |
||||
"github.com/ethereum/go-ethereum/p2p" |
||||
"github.com/ethereum/go-ethereum/p2p/rlpx" |
||||
) |
||||
|
||||
var ( |
||||
pretty = spew.ConfigState{ |
||||
Indent: " ", |
||||
DisableCapacities: true, |
||||
DisablePointerAddresses: true, |
||||
SortKeys: true, |
||||
} |
||||
timeout = 20 * time.Second |
||||
) |
||||
|
||||
// dial attempts to dial the given node and perform a handshake,
|
||||
// returning the created Conn if successful.
|
||||
func (s *Suite) dial() (*Conn, error) { |
||||
// dial
|
||||
fd, err := net.Dial("tcp", fmt.Sprintf("%v:%d", s.Dest.IP(), s.Dest.TCP())) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
conn := Conn{Conn: rlpx.NewConn(fd, s.Dest.Pubkey())} |
||||
// do encHandshake
|
||||
conn.ourKey, _ = crypto.GenerateKey() |
||||
_, err = conn.Handshake(conn.ourKey) |
||||
if err != nil { |
||||
conn.Close() |
||||
return nil, err |
||||
} |
||||
// set default p2p capabilities
|
||||
conn.caps = []p2p.Cap{ |
||||
{Name: "eth", Version: 67}, |
||||
{Name: "eth", Version: 68}, |
||||
} |
||||
conn.ourHighestProtoVersion = 68 |
||||
return &conn, nil |
||||
} |
||||
|
||||
// dialSnap creates a connection with snap/1 capability.
|
||||
func (s *Suite) dialSnap() (*Conn, error) { |
||||
conn, err := s.dial() |
||||
if err != nil { |
||||
return nil, fmt.Errorf("dial failed: %v", err) |
||||
} |
||||
conn.caps = append(conn.caps, p2p.Cap{Name: "snap", Version: 1}) |
||||
conn.ourHighestSnapProtoVersion = 1 |
||||
return conn, nil |
||||
} |
||||
|
||||
// peer performs both the protocol handshake and the status message
|
||||
// exchange with the node in order to peer with it.
|
||||
func (c *Conn) peer(chain *Chain, status *Status) error { |
||||
if err := c.handshake(); err != nil { |
||||
return fmt.Errorf("handshake failed: %v", err) |
||||
} |
||||
if _, err := c.statusExchange(chain, status); err != nil { |
||||
return fmt.Errorf("status exchange failed: %v", err) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// handshake performs a protocol handshake with the node.
|
||||
func (c *Conn) handshake() error { |
||||
defer c.SetDeadline(time.Time{}) |
||||
c.SetDeadline(time.Now().Add(10 * time.Second)) |
||||
// write hello to client
|
||||
pub0 := crypto.FromECDSAPub(&c.ourKey.PublicKey)[1:] |
||||
ourHandshake := &Hello{ |
||||
Version: 5, |
||||
Caps: c.caps, |
||||
ID: pub0, |
||||
} |
||||
if err := c.Write(ourHandshake); err != nil { |
||||
return fmt.Errorf("write to connection failed: %v", err) |
||||
} |
||||
// read hello from client
|
||||
switch msg := c.Read().(type) { |
||||
case *Hello: |
||||
// set snappy if version is at least 5
|
||||
if msg.Version >= 5 { |
||||
c.SetSnappy(true) |
||||
} |
||||
c.negotiateEthProtocol(msg.Caps) |
||||
if c.negotiatedProtoVersion == 0 { |
||||
return fmt.Errorf("could not negotiate eth protocol (remote caps: %v, local eth version: %v)", msg.Caps, c.ourHighestProtoVersion) |
||||
} |
||||
// If we require snap, verify that it was negotiated
|
||||
if c.ourHighestSnapProtoVersion != c.negotiatedSnapProtoVersion { |
||||
return fmt.Errorf("could not negotiate snap protocol (remote caps: %v, local snap version: %v)", msg.Caps, c.ourHighestSnapProtoVersion) |
||||
} |
||||
return nil |
||||
default: |
||||
return fmt.Errorf("bad handshake: %#v", msg) |
||||
} |
||||
} |
||||
|
||||
// negotiateEthProtocol sets the Conn's eth protocol version to highest
|
||||
// advertised capability from peer.
|
||||
func (c *Conn) negotiateEthProtocol(caps []p2p.Cap) { |
||||
var highestEthVersion uint |
||||
var highestSnapVersion uint |
||||
for _, capability := range caps { |
||||
switch capability.Name { |
||||
case "eth": |
||||
if capability.Version > highestEthVersion && capability.Version <= c.ourHighestProtoVersion { |
||||
highestEthVersion = capability.Version |
||||
} |
||||
case "snap": |
||||
if capability.Version > highestSnapVersion && capability.Version <= c.ourHighestSnapProtoVersion { |
||||
highestSnapVersion = capability.Version |
||||
} |
||||
} |
||||
} |
||||
c.negotiatedProtoVersion = highestEthVersion |
||||
c.negotiatedSnapProtoVersion = highestSnapVersion |
||||
} |
||||
|
||||
// statusExchange performs a `Status` message exchange with the given node.
|
||||
func (c *Conn) statusExchange(chain *Chain, status *Status) (Message, error) { |
||||
defer c.SetDeadline(time.Time{}) |
||||
c.SetDeadline(time.Now().Add(20 * time.Second)) |
||||
|
||||
// read status message from client
|
||||
var message Message |
||||
loop: |
||||
for { |
||||
switch msg := c.Read().(type) { |
||||
case *Status: |
||||
if have, want := msg.Head, chain.blocks[chain.Len()-1].Hash(); have != want { |
||||
return nil, fmt.Errorf("wrong head block in status, want: %#x (block %d) have %#x", |
||||
want, chain.blocks[chain.Len()-1].NumberU64(), have) |
||||
} |
||||
if have, want := msg.TD.Cmp(chain.TD()), 0; have != want { |
||||
return nil, fmt.Errorf("wrong TD in status: have %v want %v", have, want) |
||||
} |
||||
if have, want := msg.ForkID, chain.ForkID(); !reflect.DeepEqual(have, want) { |
||||
return nil, fmt.Errorf("wrong fork ID in status: have %v, want %v", have, want) |
||||
} |
||||
if have, want := msg.ProtocolVersion, c.ourHighestProtoVersion; have != uint32(want) { |
||||
return nil, fmt.Errorf("wrong protocol version: have %v, want %v", have, want) |
||||
} |
||||
message = msg |
||||
break loop |
||||
case *Disconnect: |
||||
return nil, fmt.Errorf("disconnect received: %v", msg.Reason) |
||||
case *Ping: |
||||
c.Write(&Pong{}) // TODO (renaynay): in the future, this should be an error
|
||||
// (PINGs should not be a response upon fresh connection)
|
||||
default: |
||||
return nil, fmt.Errorf("bad status message: %s", pretty.Sdump(msg)) |
||||
} |
||||
} |
||||
// make sure eth protocol version is set for negotiation
|
||||
if c.negotiatedProtoVersion == 0 { |
||||
return nil, errors.New("eth protocol version must be set in Conn") |
||||
} |
||||
if status == nil { |
||||
// default status message
|
||||
status = &Status{ |
||||
ProtocolVersion: uint32(c.negotiatedProtoVersion), |
||||
NetworkID: chain.chainConfig.ChainID.Uint64(), |
||||
TD: chain.TD(), |
||||
Head: chain.blocks[chain.Len()-1].Hash(), |
||||
Genesis: chain.blocks[0].Hash(), |
||||
ForkID: chain.ForkID(), |
||||
} |
||||
} |
||||
if err := c.Write(status); err != nil { |
||||
return nil, fmt.Errorf("write to connection failed: %v", err) |
||||
} |
||||
return message, nil |
||||
} |
||||
|
||||
// createSendAndRecvConns creates two connections, one for sending messages to the
|
||||
// node, and one for receiving messages from the node.
|
||||
func (s *Suite) createSendAndRecvConns() (*Conn, *Conn, error) { |
||||
sendConn, err := s.dial() |
||||
if err != nil { |
||||
return nil, nil, fmt.Errorf("dial failed: %v", err) |
||||
} |
||||
recvConn, err := s.dial() |
||||
if err != nil { |
||||
sendConn.Close() |
||||
return nil, nil, fmt.Errorf("dial failed: %v", err) |
||||
} |
||||
return sendConn, recvConn, nil |
||||
} |
||||
|
||||
// readAndServe serves GetBlockHeaders requests while waiting
|
||||
// on another message from the node.
|
||||
func (c *Conn) readAndServe(chain *Chain, timeout time.Duration) Message { |
||||
start := time.Now() |
||||
for time.Since(start) < timeout { |
||||
c.SetReadDeadline(time.Now().Add(10 * time.Second)) |
||||
|
||||
msg := c.Read() |
||||
switch msg := msg.(type) { |
||||
case *Ping: |
||||
c.Write(&Pong{}) |
||||
case *GetBlockHeaders: |
||||
headers, err := chain.GetHeaders(msg) |
||||
if err != nil { |
||||
return errorf("could not get headers for inbound header request: %v", err) |
||||
} |
||||
resp := &BlockHeaders{ |
||||
RequestId: msg.ReqID(), |
||||
BlockHeadersRequest: eth.BlockHeadersRequest(headers), |
||||
} |
||||
if err := c.Write(resp); err != nil { |
||||
return errorf("could not write to connection: %v", err) |
||||
} |
||||
default: |
||||
return msg |
||||
} |
||||
} |
||||
return errorf("no message received within %v", timeout) |
||||
} |
||||
|
||||
// headersRequest executes the given `GetBlockHeaders` request.
|
||||
func (c *Conn) headersRequest(request *GetBlockHeaders, chain *Chain, reqID uint64) ([]*types.Header, error) { |
||||
defer c.SetReadDeadline(time.Time{}) |
||||
c.SetReadDeadline(time.Now().Add(20 * time.Second)) |
||||
|
||||
// write request
|
||||
request.RequestId = reqID |
||||
if err := c.Write(request); err != nil { |
||||
return nil, fmt.Errorf("could not write to connection: %v", err) |
||||
} |
||||
|
||||
// wait for response
|
||||
msg := c.waitForResponse(chain, timeout, request.RequestId) |
||||
resp, ok := msg.(*BlockHeaders) |
||||
if !ok { |
||||
return nil, fmt.Errorf("unexpected message received: %s", pretty.Sdump(msg)) |
||||
} |
||||
headers := []*types.Header(resp.BlockHeadersRequest) |
||||
return headers, nil |
||||
} |
||||
|
||||
func (c *Conn) snapRequest(msg Message, id uint64, chain *Chain) (Message, error) { |
||||
defer c.SetReadDeadline(time.Time{}) |
||||
c.SetReadDeadline(time.Now().Add(5 * time.Second)) |
||||
if err := c.Write(msg); err != nil { |
||||
return nil, fmt.Errorf("could not write to connection: %v", err) |
||||
} |
||||
return c.ReadSnap(id) |
||||
} |
||||
|
||||
// headersMatch returns whether the received headers match the given request
|
||||
func headersMatch(expected []*types.Header, headers []*types.Header) bool { |
||||
return reflect.DeepEqual(expected, headers) |
||||
} |
||||
|
||||
// waitForResponse reads from the connection until a response with the expected
|
||||
// request ID is received.
|
||||
func (c *Conn) waitForResponse(chain *Chain, timeout time.Duration, requestID uint64) Message { |
||||
for { |
||||
msg := c.readAndServe(chain, timeout) |
||||
if msg.ReqID() == requestID { |
||||
return msg |
||||
} |
||||
} |
||||
} |
||||
|
||||
// sendNextBlock broadcasts the next block in the chain and waits
|
||||
// for the node to propagate the block and import it into its chain.
|
||||
func (s *Suite) sendNextBlock() error { |
||||
// set up sending and receiving connections
|
||||
sendConn, recvConn, err := s.createSendAndRecvConns() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer sendConn.Close() |
||||
defer recvConn.Close() |
||||
if err = sendConn.peer(s.chain, nil); err != nil { |
||||
return fmt.Errorf("peering failed: %v", err) |
||||
} |
||||
if err = recvConn.peer(s.chain, nil); err != nil { |
||||
return fmt.Errorf("peering failed: %v", err) |
||||
} |
||||
// create new block announcement
|
||||
nextBlock := s.fullChain.blocks[s.chain.Len()] |
||||
blockAnnouncement := &NewBlock{ |
||||
Block: nextBlock, |
||||
TD: s.fullChain.TotalDifficultyAt(s.chain.Len()), |
||||
} |
||||
// send announcement and wait for node to request the header
|
||||
if err = s.testAnnounce(sendConn, recvConn, blockAnnouncement); err != nil { |
||||
return fmt.Errorf("failed to announce block: %v", err) |
||||
} |
||||
// wait for client to update its chain
|
||||
if err = s.waitForBlockImport(recvConn, nextBlock); err != nil { |
||||
return fmt.Errorf("failed to receive confirmation of block import: %v", err) |
||||
} |
||||
// update test suite chain
|
||||
s.chain.blocks = append(s.chain.blocks, nextBlock) |
||||
return nil |
||||
} |
||||
|
||||
// testAnnounce writes a block announcement to the node and waits for the node
|
||||
// to propagate it.
|
||||
func (s *Suite) testAnnounce(sendConn, receiveConn *Conn, blockAnnouncement *NewBlock) error { |
||||
if err := sendConn.Write(blockAnnouncement); err != nil { |
||||
return fmt.Errorf("could not write to connection: %v", err) |
||||
} |
||||
return s.waitAnnounce(receiveConn, blockAnnouncement) |
||||
} |
||||
|
||||
// waitAnnounce waits for a NewBlock or NewBlockHashes announcement from the node.
|
||||
func (s *Suite) waitAnnounce(conn *Conn, blockAnnouncement *NewBlock) error { |
||||
for { |
||||
switch msg := conn.readAndServe(s.chain, timeout).(type) { |
||||
case *NewBlock: |
||||
if !reflect.DeepEqual(blockAnnouncement.Block.Header(), msg.Block.Header()) { |
||||
return fmt.Errorf("wrong header in block announcement: \nexpected %v "+ |
||||
"\ngot %v", blockAnnouncement.Block.Header(), msg.Block.Header()) |
||||
} |
||||
if !reflect.DeepEqual(blockAnnouncement.TD, msg.TD) { |
||||
return fmt.Errorf("wrong TD in announcement: expected %v, got %v", blockAnnouncement.TD, msg.TD) |
||||
} |
||||
return nil |
||||
case *NewBlockHashes: |
||||
hashes := *msg |
||||
if blockAnnouncement.Block.Hash() != hashes[0].Hash { |
||||
return fmt.Errorf("wrong block hash in announcement: expected %v, got %v", blockAnnouncement.Block.Hash(), hashes[0].Hash) |
||||
} |
||||
return nil |
||||
|
||||
// ignore tx announcements from previous tests
|
||||
case *NewPooledTransactionHashes66: |
||||
continue |
||||
case *NewPooledTransactionHashes: |
||||
continue |
||||
case *Transactions: |
||||
continue |
||||
|
||||
default: |
||||
return fmt.Errorf("unexpected: %s", pretty.Sdump(msg)) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (s *Suite) waitForBlockImport(conn *Conn, block *types.Block) error { |
||||
defer conn.SetReadDeadline(time.Time{}) |
||||
conn.SetReadDeadline(time.Now().Add(20 * time.Second)) |
||||
// create request
|
||||
req := &GetBlockHeaders{ |
||||
GetBlockHeadersRequest: ð.GetBlockHeadersRequest{ |
||||
Origin: eth.HashOrNumber{Hash: block.Hash()}, |
||||
Amount: 1, |
||||
}, |
||||
} |
||||
|
||||
// loop until BlockHeaders response contains desired block, confirming the
|
||||
// node imported the block
|
||||
for { |
||||
requestID := uint64(54) |
||||
headers, err := conn.headersRequest(req, s.chain, requestID) |
||||
if err != nil { |
||||
return fmt.Errorf("GetBlockHeader request failed: %v", err) |
||||
} |
||||
// if headers response is empty, node hasn't imported block yet, try again
|
||||
if len(headers) == 0 { |
||||
time.Sleep(100 * time.Millisecond) |
||||
continue |
||||
} |
||||
if !reflect.DeepEqual(block.Header(), headers[0]) { |
||||
return fmt.Errorf("wrong header returned: wanted %v, got %v", block.Header(), headers[0]) |
||||
} |
||||
return nil |
||||
} |
||||
} |
||||
|
||||
func (s *Suite) oldAnnounce() error { |
||||
sendConn, receiveConn, err := s.createSendAndRecvConns() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer sendConn.Close() |
||||
defer receiveConn.Close() |
||||
if err := sendConn.peer(s.chain, nil); err != nil { |
||||
return fmt.Errorf("peering failed: %v", err) |
||||
} |
||||
if err := receiveConn.peer(s.chain, nil); err != nil { |
||||
return fmt.Errorf("peering failed: %v", err) |
||||
} |
||||
// create old block announcement
|
||||
oldBlockAnnounce := &NewBlock{ |
||||
Block: s.chain.blocks[len(s.chain.blocks)/2], |
||||
TD: s.chain.blocks[len(s.chain.blocks)/2].Difficulty(), |
||||
} |
||||
if err := sendConn.Write(oldBlockAnnounce); err != nil { |
||||
return fmt.Errorf("could not write to connection: %v", err) |
||||
} |
||||
// wait to see if the announcement is propagated
|
||||
switch msg := receiveConn.readAndServe(s.chain, time.Second*8).(type) { |
||||
case *NewBlock: |
||||
block := *msg |
||||
if block.Block.Hash() == oldBlockAnnounce.Block.Hash() { |
||||
return fmt.Errorf("unexpected: block propagated: %s", pretty.Sdump(msg)) |
||||
} |
||||
case *NewBlockHashes: |
||||
hashes := *msg |
||||
for _, hash := range hashes { |
||||
if hash.Hash == oldBlockAnnounce.Block.Hash() { |
||||
return fmt.Errorf("unexpected: block announced: %s", pretty.Sdump(msg)) |
||||
} |
||||
} |
||||
case *Error: |
||||
errMsg := *msg |
||||
// check to make sure error is timeout (propagation didn't come through == test successful)
|
||||
if !strings.Contains(errMsg.String(), "timeout") { |
||||
return fmt.Errorf("unexpected error: %v", pretty.Sdump(msg)) |
||||
} |
||||
default: |
||||
return fmt.Errorf("unexpected: %s", pretty.Sdump(msg)) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (s *Suite) maliciousHandshakes(t *utesting.T) error { |
||||
conn, err := s.dial() |
||||
if err != nil { |
||||
return fmt.Errorf("dial failed: %v", err) |
||||
} |
||||
defer conn.Close() |
||||
|
||||
// write hello to client
|
||||
pub0 := crypto.FromECDSAPub(&conn.ourKey.PublicKey)[1:] |
||||
handshakes := []*Hello{ |
||||
{ |
||||
Version: 5, |
||||
Caps: []p2p.Cap{ |
||||
{Name: largeString(2), Version: 64}, |
||||
}, |
||||
ID: pub0, |
||||
}, |
||||
{ |
||||
Version: 5, |
||||
Caps: []p2p.Cap{ |
||||
{Name: "eth", Version: 64}, |
||||
{Name: "eth", Version: 65}, |
||||
}, |
||||
ID: append(pub0, byte(0)), |
||||
}, |
||||
{ |
||||
Version: 5, |
||||
Caps: []p2p.Cap{ |
||||
{Name: "eth", Version: 64}, |
||||
{Name: "eth", Version: 65}, |
||||
}, |
||||
ID: append(pub0, pub0...), |
||||
}, |
||||
{ |
||||
Version: 5, |
||||
Caps: []p2p.Cap{ |
||||
{Name: "eth", Version: 64}, |
||||
{Name: "eth", Version: 65}, |
||||
}, |
||||
ID: largeBuffer(2), |
||||
}, |
||||
{ |
||||
Version: 5, |
||||
Caps: []p2p.Cap{ |
||||
{Name: largeString(2), Version: 64}, |
||||
}, |
||||
ID: largeBuffer(2), |
||||
}, |
||||
} |
||||
for i, handshake := range handshakes { |
||||
t.Logf("Testing malicious handshake %v\n", i) |
||||
if err := conn.Write(handshake); err != nil { |
||||
return fmt.Errorf("could not write to connection: %v", err) |
||||
} |
||||
// check that the peer disconnected
|
||||
for i := 0; i < 2; i++ { |
||||
switch msg := conn.readAndServe(s.chain, 20*time.Second).(type) { |
||||
case *Disconnect: |
||||
case *Error: |
||||
case *Hello: |
||||
// Discard one hello as Hello's are sent concurrently
|
||||
continue |
||||
default: |
||||
return fmt.Errorf("unexpected: %s", pretty.Sdump(msg)) |
||||
} |
||||
} |
||||
// dial for the next round
|
||||
conn, err = s.dial() |
||||
if err != nil { |
||||
return fmt.Errorf("dial failed: %v", err) |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (s *Suite) maliciousStatus(conn *Conn) error { |
||||
if err := conn.handshake(); err != nil { |
||||
return fmt.Errorf("handshake failed: %v", err) |
||||
} |
||||
status := &Status{ |
||||
ProtocolVersion: uint32(conn.negotiatedProtoVersion), |
||||
NetworkID: s.chain.chainConfig.ChainID.Uint64(), |
||||
TD: largeNumber(2), |
||||
Head: s.chain.blocks[s.chain.Len()-1].Hash(), |
||||
Genesis: s.chain.blocks[0].Hash(), |
||||
ForkID: s.chain.ForkID(), |
||||
} |
||||
|
||||
// get status
|
||||
msg, err := conn.statusExchange(s.chain, status) |
||||
if err != nil { |
||||
return fmt.Errorf("status exchange failed: %v", err) |
||||
} |
||||
switch msg := msg.(type) { |
||||
case *Status: |
||||
default: |
||||
return fmt.Errorf("expected status, got: %#v ", msg) |
||||
} |
||||
|
||||
// wait for disconnect
|
||||
switch msg := conn.readAndServe(s.chain, timeout).(type) { |
||||
case *Disconnect: |
||||
return nil |
||||
case *Error: |
||||
return nil |
||||
default: |
||||
return fmt.Errorf("expected disconnect, got: %s", pretty.Sdump(msg)) |
||||
} |
||||
} |
||||
|
||||
func (s *Suite) hashAnnounce() error { |
||||
// create connections
|
||||
sendConn, recvConn, err := s.createSendAndRecvConns() |
||||
if err != nil { |
||||
return fmt.Errorf("failed to create connections: %v", err) |
||||
} |
||||
defer sendConn.Close() |
||||
defer recvConn.Close() |
||||
if err := sendConn.peer(s.chain, nil); err != nil { |
||||
return fmt.Errorf("peering failed: %v", err) |
||||
} |
||||
if err := recvConn.peer(s.chain, nil); err != nil { |
||||
return fmt.Errorf("peering failed: %v", err) |
||||
} |
||||
|
||||
// create NewBlockHashes announcement
|
||||
type anno struct { |
||||
Hash common.Hash // Hash of one particular block being announced
|
||||
Number uint64 // Number of one particular block being announced
|
||||
} |
||||
nextBlock := s.fullChain.blocks[s.chain.Len()] |
||||
announcement := anno{Hash: nextBlock.Hash(), Number: nextBlock.Number().Uint64()} |
||||
newBlockHash := &NewBlockHashes{announcement} |
||||
if err := sendConn.Write(newBlockHash); err != nil { |
||||
return fmt.Errorf("failed to write to connection: %v", err) |
||||
} |
||||
|
||||
// Announcement sent, now wait for a header request
|
||||
msg := sendConn.Read() |
||||
blockHeaderReq, ok := msg.(*GetBlockHeaders) |
||||
if !ok { |
||||
return fmt.Errorf("unexpected %s", pretty.Sdump(msg)) |
||||
} |
||||
if blockHeaderReq.Amount != 1 { |
||||
return fmt.Errorf("unexpected number of block headers requested: %v", blockHeaderReq.Amount) |
||||
} |
||||
if blockHeaderReq.Origin.Hash != announcement.Hash { |
||||
return fmt.Errorf("unexpected block header requested. Announced:\n %v\n Remote request:\n%v", |
||||
pretty.Sdump(announcement), |
||||
pretty.Sdump(blockHeaderReq)) |
||||
} |
||||
err = sendConn.Write(&BlockHeaders{ |
||||
RequestId: blockHeaderReq.ReqID(), |
||||
BlockHeadersRequest: eth.BlockHeadersRequest{nextBlock.Header()}, |
||||
}) |
||||
if err != nil { |
||||
return fmt.Errorf("failed to write to connection: %v", err) |
||||
} |
||||
|
||||
// wait for block announcement
|
||||
msg = recvConn.readAndServe(s.chain, timeout) |
||||
switch msg := msg.(type) { |
||||
case *NewBlockHashes: |
||||
hashes := *msg |
||||
if len(hashes) != 1 { |
||||
return fmt.Errorf("unexpected new block hash announcement: wanted 1 announcement, got %d", len(hashes)) |
||||
} |
||||
if nextBlock.Hash() != hashes[0].Hash { |
||||
return fmt.Errorf("unexpected block hash announcement, wanted %v, got %v", nextBlock.Hash(), |
||||
hashes[0].Hash) |
||||
} |
||||
|
||||
case *NewBlock: |
||||
// node should only propagate NewBlock without having requested the body if the body is empty
|
||||
nextBlockBody := nextBlock.Body() |
||||
if len(nextBlockBody.Transactions) != 0 || len(nextBlockBody.Uncles) != 0 { |
||||
return fmt.Errorf("unexpected non-empty new block propagated: %s", pretty.Sdump(msg)) |
||||
} |
||||
if msg.Block.Hash() != nextBlock.Hash() { |
||||
return fmt.Errorf("mismatched hash of propagated new block: wanted %v, got %v", |
||||
nextBlock.Hash(), msg.Block.Hash()) |
||||
} |
||||
// check to make sure header matches header that was sent to the node
|
||||
if !reflect.DeepEqual(nextBlock.Header(), msg.Block.Header()) { |
||||
return fmt.Errorf("incorrect header received: wanted %v, got %v", nextBlock.Header(), msg.Block.Header()) |
||||
} |
||||
default: |
||||
return fmt.Errorf("unexpected: %s", pretty.Sdump(msg)) |
||||
} |
||||
// confirm node imported block
|
||||
if err := s.waitForBlockImport(recvConn, nextBlock); err != nil { |
||||
return fmt.Errorf("error waiting for node to import new block: %v", err) |
||||
} |
||||
// update the chain
|
||||
s.chain.blocks = append(s.chain.blocks, nextBlock) |
||||
return nil |
||||
} |
@ -1,80 +0,0 @@ |
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethtest |
||||
|
||||
import ( |
||||
"crypto/rand" |
||||
"math/big" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/common/hexutil" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
) |
||||
|
||||
// largeNumber returns a very large big.Int.
|
||||
func largeNumber(megabytes int) *big.Int { |
||||
buf := make([]byte, megabytes*1024*1024) |
||||
rand.Read(buf) |
||||
bigint := new(big.Int) |
||||
bigint.SetBytes(buf) |
||||
return bigint |
||||
} |
||||
|
||||
// largeBuffer returns a very large buffer.
|
||||
func largeBuffer(megabytes int) []byte { |
||||
buf := make([]byte, megabytes*1024*1024) |
||||
rand.Read(buf) |
||||
return buf |
||||
} |
||||
|
||||
// largeString returns a very large string.
|
||||
func largeString(megabytes int) string { |
||||
buf := make([]byte, megabytes*1024*1024) |
||||
rand.Read(buf) |
||||
return hexutil.Encode(buf) |
||||
} |
||||
|
||||
func largeBlock() *types.Block { |
||||
return types.NewBlockWithHeader(largeHeader()) |
||||
} |
||||
|
||||
// Returns a random hash
|
||||
func randHash() common.Hash { |
||||
var h common.Hash |
||||
rand.Read(h[:]) |
||||
return h |
||||
} |
||||
|
||||
func largeHeader() *types.Header { |
||||
return &types.Header{ |
||||
MixDigest: randHash(), |
||||
ReceiptHash: randHash(), |
||||
TxHash: randHash(), |
||||
Nonce: types.BlockNonce{}, |
||||
Extra: []byte{}, |
||||
Bloom: types.Bloom{}, |
||||
GasUsed: 0, |
||||
Coinbase: common.Address{}, |
||||
GasLimit: 0, |
||||
UncleHash: types.EmptyUncleHash, |
||||
Time: 1337, |
||||
ParentHash: randHash(), |
||||
Root: randHash(), |
||||
Number: largeNumber(2), |
||||
Difficulty: largeNumber(2), |
||||
} |
||||
} |
@ -0,0 +1,9 @@ |
||||
#!/bin/sh |
||||
|
||||
hivechain generate \ |
||||
--fork-interval 6 \ |
||||
--tx-interval 1 \ |
||||
--length 500 \ |
||||
--outdir testdata \ |
||||
--lastfork cancun \ |
||||
--outputs accounts,genesis,chain,headstate,txinfo,headblock,headfcu,newpayload,forkenv |
@ -0,0 +1,87 @@ |
||||
// Copyright 2023 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
package ethtest |
||||
|
||||
import ( |
||||
"github.com/ethereum/go-ethereum/p2p" |
||||
"github.com/ethereum/go-ethereum/rlp" |
||||
) |
||||
|
||||
// Unexported devp2p message codes from p2p/peer.go.
|
||||
const ( |
||||
handshakeMsg = 0x00 |
||||
discMsg = 0x01 |
||||
pingMsg = 0x02 |
||||
pongMsg = 0x03 |
||||
) |
||||
|
||||
// Unexported devp2p protocol lengths from p2p package.
|
||||
const ( |
||||
baseProtoLen = 16 |
||||
ethProtoLen = 17 |
||||
snapProtoLen = 8 |
||||
) |
||||
|
||||
// Unexported handshake structure from p2p/peer.go.
|
||||
type protoHandshake struct { |
||||
Version uint64 |
||||
Name string |
||||
Caps []p2p.Cap |
||||
ListenPort uint64 |
||||
ID []byte |
||||
Rest []rlp.RawValue `rlp:"tail"` |
||||
} |
||||
|
||||
type Hello = protoHandshake |
||||
|
||||
// Proto is an enum representing devp2p protocol types.
|
||||
type Proto int |
||||
|
||||
const ( |
||||
baseProto Proto = iota |
||||
ethProto |
||||
snapProto |
||||
) |
||||
|
||||
// getProto returns the protocol a certain message code is associated with
|
||||
// (assuming the negotiated capabilities are exactly {eth,snap})
|
||||
func getProto(code uint64) Proto { |
||||
switch { |
||||
case code < baseProtoLen: |
||||
return baseProto |
||||
case code < baseProtoLen+ethProtoLen: |
||||
return ethProto |
||||
case code < baseProtoLen+ethProtoLen+snapProtoLen: |
||||
return snapProto |
||||
default: |
||||
panic("unhandled msg code beyond last protocol") |
||||
} |
||||
} |
||||
|
||||
// protoOffset will return the offset at which the specified protocol's messages
|
||||
// begin.
|
||||
func protoOffset(proto Proto) uint64 { |
||||
switch proto { |
||||
case baseProto: |
||||
return 0 |
||||
case ethProto: |
||||
return baseProtoLen |
||||
case snapProto: |
||||
return baseProtoLen + ethProtoLen |
||||
default: |
||||
panic("unhandled protocol") |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -1,60 +0,0 @@ |
||||
// Copyright 2022 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethtest |
||||
|
||||
import "github.com/ethereum/go-ethereum/eth/protocols/snap" |
||||
|
||||
// GetAccountRange represents an account range query.
|
||||
type GetAccountRange snap.GetAccountRangePacket |
||||
|
||||
func (msg GetAccountRange) Code() int { return 33 } |
||||
func (msg GetAccountRange) ReqID() uint64 { return msg.ID } |
||||
|
||||
type AccountRange snap.AccountRangePacket |
||||
|
||||
func (msg AccountRange) Code() int { return 34 } |
||||
func (msg AccountRange) ReqID() uint64 { return msg.ID } |
||||
|
||||
type GetStorageRanges snap.GetStorageRangesPacket |
||||
|
||||
func (msg GetStorageRanges) Code() int { return 35 } |
||||
func (msg GetStorageRanges) ReqID() uint64 { return msg.ID } |
||||
|
||||
type StorageRanges snap.StorageRangesPacket |
||||
|
||||
func (msg StorageRanges) Code() int { return 36 } |
||||
func (msg StorageRanges) ReqID() uint64 { return msg.ID } |
||||
|
||||
type GetByteCodes snap.GetByteCodesPacket |
||||
|
||||
func (msg GetByteCodes) Code() int { return 37 } |
||||
func (msg GetByteCodes) ReqID() uint64 { return msg.ID } |
||||
|
||||
type ByteCodes snap.ByteCodesPacket |
||||
|
||||
func (msg ByteCodes) Code() int { return 38 } |
||||
func (msg ByteCodes) ReqID() uint64 { return msg.ID } |
||||
|
||||
type GetTrieNodes snap.GetTrieNodesPacket |
||||
|
||||
func (msg GetTrieNodes) Code() int { return 39 } |
||||
func (msg GetTrieNodes) ReqID() uint64 { return msg.ID } |
||||
|
||||
type TrieNodes snap.TrieNodesPacket |
||||
|
||||
func (msg TrieNodes) Code() int { return 40 } |
||||
func (msg TrieNodes) ReqID() uint64 { return msg.ID } |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,62 @@ |
||||
{ |
||||
"0x0c2c51a0990aee1d73c1228de158688341557508": { |
||||
"key": "0xbfcd0e032489319f4e5ca03e643b2025db624be6cf99cbfed90c4502e3754850" |
||||
}, |
||||
"0x14e46043e63d0e3cdcf2530519f4cfaf35058cb2": { |
||||
"key": "0x457075f6822ac29481154792f65c5f1ec335b4fea9ca20f3fea8fa1d78a12c68" |
||||
}, |
||||
"0x16c57edf7fa9d9525378b0b81bf8a3ced0620c1c": { |
||||
"key": "0x865898edcf43206d138c93f1bbd86311f4657b057658558888aa5ac4309626a6" |
||||
}, |
||||
"0x1f4924b14f34e24159387c0a4cdbaa32f3ddb0cf": { |
||||
"key": "0xee7f7875d826d7443ccc5c174e38b2c436095018774248a8074ee92d8914dcdb" |
||||
}, |
||||
"0x1f5bde34b4afc686f136c7a3cb6ec376f7357759": { |
||||
"key": "0x25e6ce8611cefb5cd338aeaa9292ed2139714668d123a4fb156cabb42051b5b7" |
||||
}, |
||||
"0x2d389075be5be9f2246ad654ce152cf05990b209": { |
||||
"key": "0x19168cd7767604b3d19b99dc3da1302b9ccb6ee9ad61660859e07acd4a2625dd" |
||||
}, |
||||
"0x3ae75c08b4c907eb63a8960c45b86e1e9ab6123c": { |
||||
"key": "0x71aa7d299c7607dabfc3d0e5213d612b5e4a97455b596c2f642daac43fa5eeaa" |
||||
}, |
||||
"0x4340ee1b812acb40a1eb561c019c327b243b92df": { |
||||
"key": "0x47f666f20e2175606355acec0ea1b37870c15e5797e962340da7ad7972a537e8" |
||||
}, |
||||
"0x4a0f1452281bcec5bd90c3dce6162a5995bfe9df": { |
||||
"key": "0xa88293fefc623644969e2ce6919fb0dbd0fd64f640293b4bf7e1a81c97e7fc7f" |
||||
}, |
||||
"0x4dde844b71bcdf95512fb4dc94e84fb67b512ed8": { |
||||
"key": "0x6e1e16a9c15641c73bf6e237f9293ab1d4e7c12b9adf83cfc94bcf969670f72d" |
||||
}, |
||||
"0x5f552da00dfb4d3749d9e62dcee3c918855a86a0": { |
||||
"key": "0x41be4e00aac79f7ffbb3455053ec05e971645440d594c047cdcc56a3c7458bd6" |
||||
}, |
||||
"0x654aa64f5fbefb84c270ec74211b81ca8c44a72e": { |
||||
"key": "0xc825f31cd8792851e33a290b3d749e553983111fc1f36dfbbdb45f101973f6a9" |
||||
}, |
||||
"0x717f8aa2b982bee0e29f573d31df288663e1ce16": { |
||||
"key": "0x8d0faa04ae0f9bc3cd4c890aa025d5f40916f4729538b19471c0beefe11d9e19" |
||||
}, |
||||
"0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f": { |
||||
"key": "0x4552dbe6ca4699322b5d923d0c9bcdd24644f5db8bf89a085b67c6c49b8a1b91" |
||||
}, |
||||
"0x83c7e323d189f18725ac510004fdc2941f8c4a78": { |
||||
"key": "0x34391cbbf06956bb506f45ec179cdd84df526aa364e27bbde65db9c15d866d00" |
||||
}, |
||||
"0x84e75c28348fb86acea1a93a39426d7d60f4cc46": { |
||||
"key": "0xf6a8f1603b8368f3ca373292b7310c53bec7b508aecacd442554ebc1c5d0c856" |
||||
}, |
||||
"0xc7b99a164efd027a93f147376cc7da7c67c6bbe0": { |
||||
"key": "0x8d56bcbcf2c1b7109e1396a28d7a0234e33544ade74ea32c460ce4a443b239b1" |
||||
}, |
||||
"0xd803681e487e6ac18053afc5a6cd813c86ec3e4d": { |
||||
"key": "0xfc39d1c9ddbba176d806ebb42d7460189fe56ca163ad3eb6143bfc6beb6f6f72" |
||||
}, |
||||
"0xe7d13f7aa2a838d24c59b40186a0aca1e21cffcc": { |
||||
"key": "0x9ee3fd550664b246ad7cdba07162dd25530a3b1d51476dd1d85bbc29f0592684" |
||||
}, |
||||
"0xeda8645ba6948855e3b3cd596bbb07596d59c603": { |
||||
"key": "0x14cdde09d1640eb8c3cda063891b0453073f57719583381ff78811efa6d4199f" |
||||
} |
||||
} |
Binary file not shown.
@ -0,0 +1,20 @@ |
||||
{ |
||||
"HIVE_CANCUN_TIMESTAMP": "840", |
||||
"HIVE_CHAIN_ID": "3503995874084926", |
||||
"HIVE_FORK_ARROW_GLACIER": "60", |
||||
"HIVE_FORK_BERLIN": "48", |
||||
"HIVE_FORK_BYZANTIUM": "18", |
||||
"HIVE_FORK_CONSTANTINOPLE": "24", |
||||
"HIVE_FORK_GRAY_GLACIER": "66", |
||||
"HIVE_FORK_HOMESTEAD": "0", |
||||
"HIVE_FORK_ISTANBUL": "36", |
||||
"HIVE_FORK_LONDON": "54", |
||||
"HIVE_FORK_MUIR_GLACIER": "42", |
||||
"HIVE_FORK_PETERSBURG": "30", |
||||
"HIVE_FORK_SPURIOUS": "12", |
||||
"HIVE_FORK_TANGERINE": "6", |
||||
"HIVE_MERGE_BLOCK_ID": "72", |
||||
"HIVE_NETWORK_ID": "3503995874084926", |
||||
"HIVE_SHANGHAI_TIMESTAMP": "780", |
||||
"HIVE_TERMINAL_TOTAL_DIFFICULTY": "9454784" |
||||
} |
@ -1,27 +1,112 @@ |
||||
{ |
||||
"config": { |
||||
"chainId": 19763, |
||||
"homesteadBlock": 0, |
||||
"eip150Block": 0, |
||||
"eip155Block": 0, |
||||
"eip158Block": 0, |
||||
"byzantiumBlock": 0, |
||||
"terminalTotalDifficultyPassed": true, |
||||
"ethash": {} |
||||
}, |
||||
"nonce": "0xdeadbeefdeadbeef", |
||||
"timestamp": "0x0", |
||||
"extraData": "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||
"gasLimit": "0x80000000", |
||||
"difficulty": "0x20000", |
||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||
"coinbase": "0x0000000000000000000000000000000000000000", |
||||
"alloc": { |
||||
"71562b71999873db5b286df957af199ec94617f7": { |
||||
"balance": "0xffffffffffffffffffffffffff" |
||||
} |
||||
}, |
||||
"number": "0x0", |
||||
"gasUsed": "0x0", |
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" |
||||
} |
||||
"config": { |
||||
"chainId": 3503995874084926, |
||||
"homesteadBlock": 0, |
||||
"eip150Block": 6, |
||||
"eip155Block": 12, |
||||
"eip158Block": 12, |
||||
"byzantiumBlock": 18, |
||||
"constantinopleBlock": 24, |
||||
"petersburgBlock": 30, |
||||
"istanbulBlock": 36, |
||||
"muirGlacierBlock": 42, |
||||
"berlinBlock": 48, |
||||
"londonBlock": 54, |
||||
"arrowGlacierBlock": 60, |
||||
"grayGlacierBlock": 66, |
||||
"mergeNetsplitBlock": 72, |
||||
"shanghaiTime": 780, |
||||
"cancunTime": 840, |
||||
"terminalTotalDifficulty": 9454784, |
||||
"terminalTotalDifficultyPassed": true, |
||||
"ethash": {} |
||||
}, |
||||
"nonce": "0x0", |
||||
"timestamp": "0x0", |
||||
"extraData": "0x68697665636861696e", |
||||
"gasLimit": "0x23f3e20", |
||||
"difficulty": "0x20000", |
||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||
"coinbase": "0x0000000000000000000000000000000000000000", |
||||
"alloc": { |
||||
"000f3df6d732807ef1319fb7b8bb8522d0beac02": { |
||||
"code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500", |
||||
"balance": "0x2a" |
||||
}, |
||||
"0c2c51a0990aee1d73c1228de158688341557508": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"14e46043e63d0e3cdcf2530519f4cfaf35058cb2": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"16c57edf7fa9d9525378b0b81bf8a3ced0620c1c": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"1f4924b14f34e24159387c0a4cdbaa32f3ddb0cf": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"1f5bde34b4afc686f136c7a3cb6ec376f7357759": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"2d389075be5be9f2246ad654ce152cf05990b209": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"3ae75c08b4c907eb63a8960c45b86e1e9ab6123c": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"4340ee1b812acb40a1eb561c019c327b243b92df": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"4a0f1452281bcec5bd90c3dce6162a5995bfe9df": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"4dde844b71bcdf95512fb4dc94e84fb67b512ed8": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"5f552da00dfb4d3749d9e62dcee3c918855a86a0": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"654aa64f5fbefb84c270ec74211b81ca8c44a72e": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"717f8aa2b982bee0e29f573d31df288663e1ce16": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"7435ed30a8b4aeb0877cef0c6e8cffe834eb865f": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"83c7e323d189f18725ac510004fdc2941f8c4a78": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"84e75c28348fb86acea1a93a39426d7d60f4cc46": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"8bebc8ba651aee624937e7d897853ac30c95a067": { |
||||
"storage": { |
||||
"0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000000000000000000000000000000000000000000001", |
||||
"0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000000000000000000000000000000000000000000002", |
||||
"0x0000000000000000000000000000000000000000000000000000000000000003": "0x0000000000000000000000000000000000000000000000000000000000000003" |
||||
}, |
||||
"balance": "0x1", |
||||
"nonce": "0x1" |
||||
}, |
||||
"c7b99a164efd027a93f147376cc7da7c67c6bbe0": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"d803681e487e6ac18053afc5a6cd813c86ec3e4d": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"e7d13f7aa2a838d24c59b40186a0aca1e21cffcc": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
}, |
||||
"eda8645ba6948855e3b3cd596bbb07596d59c603": { |
||||
"balance": "0xc097ce7bc90715b34b9f1000000000" |
||||
} |
||||
}, |
||||
"number": "0x0", |
||||
"gasUsed": "0x0", |
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||
"baseFeePerGas": null, |
||||
"excessBlobGas": null, |
||||
"blobGasUsed": null |
||||
} |
Binary file not shown.
@ -0,0 +1,23 @@ |
||||
{ |
||||
"parentHash": "0x96a73007443980c5e0985dfbb45279aa496dadea16918ad42c65c0bf8122ec39", |
||||
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", |
||||
"miner": "0x0000000000000000000000000000000000000000", |
||||
"stateRoot": "0xea4c1f4d9fa8664c22574c5b2f948a78c4b1a753cebc1861e7fb5b1aa21c5a94", |
||||
"transactionsRoot": "0xecda39025fc4c609ce778d75eed0aa53b65ce1e3d1373b34bad8578cc31e5b48", |
||||
"receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", |
||||
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||
"difficulty": "0x0", |
||||
"number": "0x1f4", |
||||
"gasLimit": "0x47e7c40", |
||||
"gasUsed": "0x5208", |
||||
"timestamp": "0x1388", |
||||
"extraData": "0x", |
||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||
"nonce": "0x0000000000000000", |
||||
"baseFeePerGas": "0x7", |
||||
"withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", |
||||
"blobGasUsed": "0x0", |
||||
"excessBlobGas": "0x0", |
||||
"parentBeaconBlockRoot": "0xf653da50cdff4733f13f7a5e338290e883bdf04adf3f112709728063ea965d6c", |
||||
"hash": "0x36a166f0dcd160fc5e5c61c9a7c2d7f236d9175bf27f43aaa2150e291f092ef7" |
||||
} |
@ -0,0 +1,13 @@ |
||||
{ |
||||
"jsonrpc": "2.0", |
||||
"id": "fcu500", |
||||
"method": "engine_forkchoiceUpdatedV3", |
||||
"params": [ |
||||
{ |
||||
"headBlockHash": "0x36a166f0dcd160fc5e5c61c9a7c2d7f236d9175bf27f43aaa2150e291f092ef7", |
||||
"safeBlockHash": "0x36a166f0dcd160fc5e5c61c9a7c2d7f236d9175bf27f43aaa2150e291f092ef7", |
||||
"finalizedBlockHash": "0x36a166f0dcd160fc5e5c61c9a7c2d7f236d9175bf27f43aaa2150e291f092ef7" |
||||
}, |
||||
null |
||||
] |
||||
} |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,291 +0,0 @@ |
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethtest |
||||
|
||||
import ( |
||||
"crypto/ecdsa" |
||||
"errors" |
||||
"fmt" |
||||
"time" |
||||
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth" |
||||
"github.com/ethereum/go-ethereum/p2p" |
||||
"github.com/ethereum/go-ethereum/p2p/rlpx" |
||||
"github.com/ethereum/go-ethereum/rlp" |
||||
) |
||||
|
||||
type Message interface { |
||||
Code() int |
||||
ReqID() uint64 |
||||
} |
||||
|
||||
type Error struct { |
||||
err error |
||||
} |
||||
|
||||
func (e *Error) Unwrap() error { return e.err } |
||||
func (e *Error) Error() string { return e.err.Error() } |
||||
func (e *Error) String() string { return e.Error() } |
||||
|
||||
func (e *Error) Code() int { return -1 } |
||||
func (e *Error) ReqID() uint64 { return 0 } |
||||
|
||||
func errorf(format string, args ...interface{}) *Error { |
||||
return &Error{fmt.Errorf(format, args...)} |
||||
} |
||||
|
||||
// Hello is the RLP structure of the protocol handshake.
|
||||
type Hello struct { |
||||
Version uint64 |
||||
Name string |
||||
Caps []p2p.Cap |
||||
ListenPort uint64 |
||||
ID []byte // secp256k1 public key
|
||||
|
||||
// Ignore additional fields (for forward compatibility).
|
||||
Rest []rlp.RawValue `rlp:"tail"` |
||||
} |
||||
|
||||
func (msg Hello) Code() int { return 0x00 } |
||||
func (msg Hello) ReqID() uint64 { return 0 } |
||||
|
||||
// Disconnect is the RLP structure for a disconnect message.
|
||||
type Disconnect struct { |
||||
Reason p2p.DiscReason |
||||
} |
||||
|
||||
func (msg Disconnect) Code() int { return 0x01 } |
||||
func (msg Disconnect) ReqID() uint64 { return 0 } |
||||
|
||||
type Ping struct{} |
||||
|
||||
func (msg Ping) Code() int { return 0x02 } |
||||
func (msg Ping) ReqID() uint64 { return 0 } |
||||
|
||||
type Pong struct{} |
||||
|
||||
func (msg Pong) Code() int { return 0x03 } |
||||
func (msg Pong) ReqID() uint64 { return 0 } |
||||
|
||||
// Status is the network packet for the status message for eth/64 and later.
|
||||
type Status eth.StatusPacket |
||||
|
||||
func (msg Status) Code() int { return 16 } |
||||
func (msg Status) ReqID() uint64 { return 0 } |
||||
|
||||
// NewBlockHashes is the network packet for the block announcements.
|
||||
type NewBlockHashes eth.NewBlockHashesPacket |
||||
|
||||
func (msg NewBlockHashes) Code() int { return 17 } |
||||
func (msg NewBlockHashes) ReqID() uint64 { return 0 } |
||||
|
||||
type Transactions eth.TransactionsPacket |
||||
|
||||
func (msg Transactions) Code() int { return 18 } |
||||
func (msg Transactions) ReqID() uint64 { return 18 } |
||||
|
||||
// GetBlockHeaders represents a block header query.
|
||||
type GetBlockHeaders eth.GetBlockHeadersPacket |
||||
|
||||
func (msg GetBlockHeaders) Code() int { return 19 } |
||||
func (msg GetBlockHeaders) ReqID() uint64 { return msg.RequestId } |
||||
|
||||
type BlockHeaders eth.BlockHeadersPacket |
||||
|
||||
func (msg BlockHeaders) Code() int { return 20 } |
||||
func (msg BlockHeaders) ReqID() uint64 { return msg.RequestId } |
||||
|
||||
// GetBlockBodies represents a GetBlockBodies request
|
||||
type GetBlockBodies eth.GetBlockBodiesPacket |
||||
|
||||
func (msg GetBlockBodies) Code() int { return 21 } |
||||
func (msg GetBlockBodies) ReqID() uint64 { return msg.RequestId } |
||||
|
||||
// BlockBodies is the network packet for block content distribution.
|
||||
type BlockBodies eth.BlockBodiesPacket |
||||
|
||||
func (msg BlockBodies) Code() int { return 22 } |
||||
func (msg BlockBodies) ReqID() uint64 { return msg.RequestId } |
||||
|
||||
// NewBlock is the network packet for the block propagation message.
|
||||
type NewBlock eth.NewBlockPacket |
||||
|
||||
func (msg NewBlock) Code() int { return 23 } |
||||
func (msg NewBlock) ReqID() uint64 { return 0 } |
||||
|
||||
// NewPooledTransactionHashes66 is the network packet for the tx hash propagation message.
|
||||
type NewPooledTransactionHashes66 eth.NewPooledTransactionHashesPacket67 |
||||
|
||||
func (msg NewPooledTransactionHashes66) Code() int { return 24 } |
||||
func (msg NewPooledTransactionHashes66) ReqID() uint64 { return 0 } |
||||
|
||||
// NewPooledTransactionHashes is the network packet for the tx hash propagation message.
|
||||
type NewPooledTransactionHashes eth.NewPooledTransactionHashesPacket68 |
||||
|
||||
func (msg NewPooledTransactionHashes) Code() int { return 24 } |
||||
func (msg NewPooledTransactionHashes) ReqID() uint64 { return 0 } |
||||
|
||||
type GetPooledTransactions eth.GetPooledTransactionsPacket |
||||
|
||||
func (msg GetPooledTransactions) Code() int { return 25 } |
||||
func (msg GetPooledTransactions) ReqID() uint64 { return msg.RequestId } |
||||
|
||||
type PooledTransactions eth.PooledTransactionsPacket |
||||
|
||||
func (msg PooledTransactions) Code() int { return 26 } |
||||
func (msg PooledTransactions) ReqID() uint64 { return msg.RequestId } |
||||
|
||||
// Conn represents an individual connection with a peer
|
||||
type Conn struct { |
||||
*rlpx.Conn |
||||
ourKey *ecdsa.PrivateKey |
||||
negotiatedProtoVersion uint |
||||
negotiatedSnapProtoVersion uint |
||||
ourHighestProtoVersion uint |
||||
ourHighestSnapProtoVersion uint |
||||
caps []p2p.Cap |
||||
} |
||||
|
||||
// Read reads an eth66 packet from the connection.
|
||||
func (c *Conn) Read() Message { |
||||
code, rawData, _, err := c.Conn.Read() |
||||
if err != nil { |
||||
return errorf("could not read from connection: %v", err) |
||||
} |
||||
|
||||
var msg Message |
||||
switch int(code) { |
||||
case (Hello{}).Code(): |
||||
msg = new(Hello) |
||||
case (Ping{}).Code(): |
||||
msg = new(Ping) |
||||
case (Pong{}).Code(): |
||||
msg = new(Pong) |
||||
case (Disconnect{}).Code(): |
||||
msg = new(Disconnect) |
||||
case (Status{}).Code(): |
||||
msg = new(Status) |
||||
case (GetBlockHeaders{}).Code(): |
||||
ethMsg := new(eth.GetBlockHeadersPacket) |
||||
if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { |
||||
return errorf("could not rlp decode message: %v", err) |
||||
} |
||||
return (*GetBlockHeaders)(ethMsg) |
||||
case (BlockHeaders{}).Code(): |
||||
ethMsg := new(eth.BlockHeadersPacket) |
||||
if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { |
||||
return errorf("could not rlp decode message: %v", err) |
||||
} |
||||
return (*BlockHeaders)(ethMsg) |
||||
case (GetBlockBodies{}).Code(): |
||||
ethMsg := new(eth.GetBlockBodiesPacket) |
||||
if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { |
||||
return errorf("could not rlp decode message: %v", err) |
||||
} |
||||
return (*GetBlockBodies)(ethMsg) |
||||
case (BlockBodies{}).Code(): |
||||
ethMsg := new(eth.BlockBodiesPacket) |
||||
if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { |
||||
return errorf("could not rlp decode message: %v", err) |
||||
} |
||||
return (*BlockBodies)(ethMsg) |
||||
case (NewBlock{}).Code(): |
||||
msg = new(NewBlock) |
||||
case (NewBlockHashes{}).Code(): |
||||
msg = new(NewBlockHashes) |
||||
case (Transactions{}).Code(): |
||||
msg = new(Transactions) |
||||
case (NewPooledTransactionHashes66{}).Code(): |
||||
// Try decoding to eth68
|
||||
ethMsg := new(NewPooledTransactionHashes) |
||||
if err := rlp.DecodeBytes(rawData, ethMsg); err == nil { |
||||
return ethMsg |
||||
} |
||||
msg = new(NewPooledTransactionHashes66) |
||||
case (GetPooledTransactions{}.Code()): |
||||
ethMsg := new(eth.GetPooledTransactionsPacket) |
||||
if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { |
||||
return errorf("could not rlp decode message: %v", err) |
||||
} |
||||
return (*GetPooledTransactions)(ethMsg) |
||||
case (PooledTransactions{}.Code()): |
||||
ethMsg := new(eth.PooledTransactionsPacket) |
||||
if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { |
||||
return errorf("could not rlp decode message: %v", err) |
||||
} |
||||
return (*PooledTransactions)(ethMsg) |
||||
default: |
||||
msg = errorf("invalid message code: %d", code) |
||||
} |
||||
|
||||
if msg != nil { |
||||
if err := rlp.DecodeBytes(rawData, msg); err != nil { |
||||
return errorf("could not rlp decode message: %v", err) |
||||
} |
||||
return msg |
||||
} |
||||
return errorf("invalid message: %s", string(rawData)) |
||||
} |
||||
|
||||
// Write writes a eth packet to the connection.
|
||||
func (c *Conn) Write(msg Message) error { |
||||
payload, err := rlp.EncodeToBytes(msg) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
_, err = c.Conn.Write(uint64(msg.Code()), payload) |
||||
return err |
||||
} |
||||
|
||||
// ReadSnap reads a snap/1 response with the given id from the connection.
|
||||
func (c *Conn) ReadSnap(id uint64) (Message, error) { |
||||
respId := id + 1 |
||||
start := time.Now() |
||||
for respId != id && time.Since(start) < timeout { |
||||
code, rawData, _, err := c.Conn.Read() |
||||
if err != nil { |
||||
return nil, fmt.Errorf("could not read from connection: %v", err) |
||||
} |
||||
var snpMsg interface{} |
||||
switch int(code) { |
||||
case (GetAccountRange{}).Code(): |
||||
snpMsg = new(GetAccountRange) |
||||
case (AccountRange{}).Code(): |
||||
snpMsg = new(AccountRange) |
||||
case (GetStorageRanges{}).Code(): |
||||
snpMsg = new(GetStorageRanges) |
||||
case (StorageRanges{}).Code(): |
||||
snpMsg = new(StorageRanges) |
||||
case (GetByteCodes{}).Code(): |
||||
snpMsg = new(GetByteCodes) |
||||
case (ByteCodes{}).Code(): |
||||
snpMsg = new(ByteCodes) |
||||
case (GetTrieNodes{}).Code(): |
||||
snpMsg = new(GetTrieNodes) |
||||
case (TrieNodes{}).Code(): |
||||
snpMsg = new(TrieNodes) |
||||
default: |
||||
//return nil, fmt.Errorf("invalid message code: %d", code)
|
||||
continue |
||||
} |
||||
if err := rlp.DecodeBytes(rawData, snpMsg); err != nil { |
||||
return nil, fmt.Errorf("could not rlp decode message: %v", err) |
||||
} |
||||
return snpMsg.(Message), nil |
||||
} |
||||
return nil, errors.New("request timed out") |
||||
} |
@ -0,0 +1,81 @@ |
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package t8ntool |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"io" |
||||
"math/big" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/vm" |
||||
"github.com/ethereum/go-ethereum/eth/tracers" |
||||
"github.com/ethereum/go-ethereum/log" |
||||
) |
||||
|
||||
// traceWriter is an vm.EVMLogger which also holds an inner logger/tracer.
|
||||
// When the TxEnd event happens, the inner tracer result is written to the file, and
|
||||
// the file is closed.
|
||||
type traceWriter struct { |
||||
inner vm.EVMLogger |
||||
f io.WriteCloser |
||||
} |
||||
|
||||
// Compile-time interface check
|
||||
var _ = vm.EVMLogger((*traceWriter)(nil)) |
||||
|
||||
func (t *traceWriter) CaptureTxEnd(restGas uint64) { |
||||
t.inner.CaptureTxEnd(restGas) |
||||
defer t.f.Close() |
||||
|
||||
if tracer, ok := t.inner.(tracers.Tracer); ok { |
||||
result, err := tracer.GetResult() |
||||
if err != nil { |
||||
log.Warn("Error in tracer", "err", err) |
||||
return |
||||
} |
||||
err = json.NewEncoder(t.f).Encode(result) |
||||
if err != nil { |
||||
log.Warn("Error writing tracer output", "err", err) |
||||
return |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (t *traceWriter) CaptureTxStart(gasLimit uint64) { t.inner.CaptureTxStart(gasLimit) } |
||||
func (t *traceWriter) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { |
||||
t.inner.CaptureStart(env, from, to, create, input, gas, value) |
||||
} |
||||
|
||||
func (t *traceWriter) CaptureEnd(output []byte, gasUsed uint64, err error) { |
||||
t.inner.CaptureEnd(output, gasUsed, err) |
||||
} |
||||
|
||||
func (t *traceWriter) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { |
||||
t.inner.CaptureEnter(typ, from, to, input, gas, value) |
||||
} |
||||
|
||||
func (t *traceWriter) CaptureExit(output []byte, gasUsed uint64, err error) { |
||||
t.inner.CaptureExit(output, gasUsed, err) |
||||
} |
||||
|
||||
func (t *traceWriter) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { |
||||
t.inner.CaptureState(pc, op, gas, cost, scope, rData, depth, err) |
||||
} |
||||
func (t *traceWriter) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { |
||||
t.inner.CaptureFault(pc, op, gas, cost, scope, depth, err) |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue