diff --git a/Makefile b/Makefile index 020be5b5c4..a39be25aa7 100644 --- a/Makefile +++ b/Makefile @@ -32,6 +32,9 @@ bor: protoc: protoc --go_out=. --go-grpc_out=. ./internal/cli/server/proto/*.proto +generate-mocks: + go generate mockgen -destination=./tests/bor/mocks/IHeimdallClient.go -package=mocks ./consensus/bor IHeimdallClient + geth: $(GORUN) build/ci.go install ./cmd/geth @echo "Done building." diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index ac696f446b..2431b5644a 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -78,7 +78,7 @@ type SimulatedBackend struct { func NewSimulatedBackendWithDatabase(database ethdb.Database, alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend { genesis := core.Genesis{Config: params.AllEthashProtocolChanges, GasLimit: gasLimit, Alloc: alloc} genesis.MustCommit(database) - blockchain, _ := core.NewBlockChain(database, nil, genesis.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ := core.NewBlockChain(database, nil, genesis.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil) backend := &SimulatedBackend{ database: database, diff --git a/cmd/utils/bor_flags.go b/cmd/utils/bor_flags.go index 22e1b33d3e..34355532e1 100644 --- a/cmd/utils/bor_flags.go +++ b/cmd/utils/bor_flags.go @@ -5,11 +5,12 @@ import ( "io/ioutil" "os" + "gopkg.in/urfave/cli.v1" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" - "gopkg.in/urfave/cli.v1" ) var ( diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index e2bbdb17f8..1a593888d6 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -33,6 +33,10 @@ import ( "text/template" "time" + pcsclite "github.com/gballet/go-libpcsclite" + gopsutil "github.com/shirou/gopsutil/mem" + "gopkg.in/urfave/cli.v1" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" @@ -68,9 +72,6 @@ import ( "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/p2p/netutil" "github.com/ethereum/go-ethereum/params" - pcsclite "github.com/gballet/go-libpcsclite" - gopsutil "github.com/shirou/gopsutil/mem" - "gopkg.in/urfave/cli.v1" ) func init() { @@ -2072,7 +2073,7 @@ func MakeChain(ctx *cli.Context, stack *node.Node) (chain *core.BlockChain, chai // TODO(rjl493456442) disable snapshot generation/wiping if the chain is read only. // Disable transaction indexing/unindexing by default. - chain, err = core.NewBlockChain(chainDb, cache, config, engine, vmcfg, nil, nil) + chain, err = core.NewBlockChain(chainDb, cache, config, engine, vmcfg, nil, nil, nil) if err != nil { Fatalf("Can't create BlockChain: %v", err) } diff --git a/consensus/bor/bor.go b/consensus/bor/bor.go index 645fce60bb..dd3cff56fb 100644 --- a/consensus/bor/bor.go +++ b/consensus/bor/bor.go @@ -606,7 +606,7 @@ func (c *Bor) verifySeal(chain consensus.ChainHeaderReader, header *types.Header return err } - if !snap.ValidatorSet.HasAddress(signer.Bytes()) { + if !snap.ValidatorSet.HasAddress(signer) { // Check the UnauthorizedSignerError.Error() msg to see why we pass number-1 return &UnauthorizedSignerError{number - 1, signer.Bytes()} } @@ -623,13 +623,13 @@ func (c *Bor) verifySeal(chain consensus.ChainHeaderReader, header *types.Header parent = chain.GetHeader(header.ParentHash, number-1) } - if parent != nil && header.Time < parent.Time+CalcProducerDelay(number, succession, c.config) { + if IsBlockOnTime(parent, header, number, succession, c.config) { return &BlockTooSoonError{number, succession} } // Ensure that the difficulty corresponds to the turn-ness of the signer if !c.fakeDiff { - difficulty := snap.Difficulty(signer) + difficulty := Difficulty(snap.ValidatorSet, signer) if header.Difficulty.Uint64() != difficulty { return &WrongDifficultyError{number, difficulty, header.Difficulty.Uint64(), signer.Bytes()} } @@ -638,6 +638,10 @@ func (c *Bor) verifySeal(chain consensus.ChainHeaderReader, header *types.Header return nil } +func IsBlockOnTime(parent *types.Header, header *types.Header, number uint64, succession int, cfg *params.BorConfig) bool { + return parent != nil && header.Time < parent.Time+CalcProducerDelay(number, succession, cfg) +} + // Prepare implements consensus.Engine, preparing all the consensus fields of the // header for running the transactions on top. func (c *Bor) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error { @@ -653,7 +657,7 @@ func (c *Bor) Prepare(chain consensus.ChainHeaderReader, header *types.Header) e } // Set the correct difficulty - header.Difficulty = new(big.Int).SetUint64(snap.Difficulty(c.signer)) + header.Difficulty = new(big.Int).SetUint64(Difficulty(snap.ValidatorSet, c.signer)) // Ensure the extra data has all it's components if len(header.Extra) < extraVanity { @@ -716,7 +720,7 @@ func (c *Bor) Finalize(chain consensus.ChainHeaderReader, header *types.Header, headerNumber := header.Number.Uint64() - if headerNumber%c.config.Sprint == 0 { + if IsSprintStart(headerNumber, c.config.Sprint) { ctx := context.Background() cx := statefull.ChainContext{Chain: chain, Bor: c} @@ -727,7 +731,7 @@ func (c *Bor) Finalize(chain consensus.ChainHeaderReader, header *types.Header, } if c.HeimdallClient != nil { - // commit statees + // commit states stateSyncData, err = c.CommitStates(ctx, state, header, cx) if err != nil { log.Error("Error while committing states", "error", err) @@ -790,7 +794,7 @@ func (c *Bor) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *typ headerNumber := header.Number.Uint64() - if headerNumber%c.config.Sprint == 0 { + if IsSprintStart(headerNumber, c.config.Sprint) { ctx := context.Background() cx := statefull.ChainContext{Chain: chain, Bor: c} @@ -867,7 +871,7 @@ func (c *Bor) Seal(chain consensus.ChainHeaderReader, block *types.Block, result } // Bail out if we're unauthorized to sign a block - if !snap.ValidatorSet.HasAddress(signer.Bytes()) { + if !snap.ValidatorSet.HasAddress(signer) { // Check the UnauthorizedSignerError.Error() msg to see why we pass number-1 return &UnauthorizedSignerError{number - 1, signer.Bytes()} } @@ -945,7 +949,7 @@ func (c *Bor) CalcDifficulty(chain consensus.ChainHeaderReader, _ uint64, parent return nil } - return new(big.Int).SetUint64(snap.Difficulty(c.signer)) + return new(big.Int).SetUint64(Difficulty(snap.ValidatorSet, c.signer)) } // SealHash returns the hash of a block prior to it being sealed. @@ -1059,7 +1063,6 @@ func (c *Bor) CommitStates( header *types.Header, chain statefull.ChainContext, ) ([]*types.StateSyncData, error) { - stateSyncs := make([]*types.StateSyncData, 0) number := header.Number.Uint64() _lastStateID, err := c.GenesisContractsClient.LastStateId(number - 1) @@ -1087,15 +1090,17 @@ func (c *Bor) CommitStates( } totalGas := 0 /// limit on gas for state sync per block - chainID := c.chainConfig.ChainID.String() + stateSyncs := make([]*types.StateSyncData, len(eventRecords)) + + var gasUsed uint64 for _, eventRecord := range eventRecords { if eventRecord.ID <= lastStateID { continue } - if err := validateEventRecord(eventRecord, number, to, lastStateID, chainID); err != nil { + if err = validateEventRecord(eventRecord, number, to, lastStateID, chainID); err != nil { log.Error("while validating event record", "block", number, "to", to, "stateID", lastStateID, "error", err.Error()) break } @@ -1109,7 +1114,10 @@ func (c *Bor) CommitStates( stateSyncs = append(stateSyncs, &stateData) - gasUsed, err := c.GenesisContractsClient.CommitState(eventRecord, state, header, chain) + // we expect that this call MUST emit an event, otherwise we wouldn't make a receipt + // if the receiver address is not a contract then we'll skip the most of the execution and emitting an event as well + // https://github.com/maticnetwork/genesis-contracts/blob/master/contracts/StateReceiver.sol#L27 + gasUsed, err = c.GenesisContractsClient.CommitState(eventRecord, state, header, chain) if err != nil { return nil, err } diff --git a/consensus/bor/bor_test.go b/consensus/bor/bor_test.go index 6225532ca9..1e447ba872 100644 --- a/consensus/bor/bor_test.go +++ b/consensus/bor/bor_test.go @@ -1,13 +1,15 @@ package bor import ( + "context" "math/big" "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/consensus/bor/heimdall" //nolint:typecheck "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" @@ -55,11 +57,11 @@ func TestGenesisContractChange(t *testing.T) { genesis := genspec.MustCommit(db) statedb, err := state.New(genesis.Root(), state.NewDatabase(db), nil) - assert.NoError(t, err) + require.NoError(t, err) config := params.ChainConfig{} - chain, err := core.NewBlockChain(db, nil, &config, b, vm.Config{}, nil, nil) - assert.NoError(t, err) + chain, err := core.NewBlockChain(db, nil, &config, b, vm.Config{}, nil, nil, nil) + require.NoError(t, err) addBlock := func(root common.Hash, num int64) (common.Hash, *state.StateDB) { h := &types.Header{ @@ -70,37 +72,37 @@ func TestGenesisContractChange(t *testing.T) { // write state to database root, err := statedb.Commit(false) - assert.NoError(t, err) - assert.NoError(t, statedb.Database().TrieDB().Commit(root, true, nil)) + require.NoError(t, err) + require.NoError(t, statedb.Database().TrieDB().Commit(root, true, nil)) statedb, err := state.New(h.Root, state.NewDatabase(db), nil) - assert.NoError(t, err) + require.NoError(t, err) return root, statedb } - assert.Equal(t, statedb.GetCode(addr0), []byte{0x1, 0x1}) + require.Equal(t, statedb.GetCode(addr0), []byte{0x1, 0x1}) root := genesis.Root() // code does not change root, statedb = addBlock(root, 1) - assert.Equal(t, statedb.GetCode(addr0), []byte{0x1, 0x1}) + require.Equal(t, statedb.GetCode(addr0), []byte{0x1, 0x1}) // code changes 1st time root, statedb = addBlock(root, 2) - assert.Equal(t, statedb.GetCode(addr0), []byte{0x1, 0x2}) + require.Equal(t, statedb.GetCode(addr0), []byte{0x1, 0x2}) // code same as 1st change root, statedb = addBlock(root, 3) - assert.Equal(t, statedb.GetCode(addr0), []byte{0x1, 0x2}) + require.Equal(t, statedb.GetCode(addr0), []byte{0x1, 0x2}) // code changes 2nd time _, statedb = addBlock(root, 4) - assert.Equal(t, statedb.GetCode(addr0), []byte{0x1, 0x3}) + require.Equal(t, statedb.GetCode(addr0), []byte{0x1, 0x3}) // make sure balance change DOES NOT take effect - assert.Equal(t, statedb.GetBalance(addr0), big.NewInt(0)) + require.Equal(t, statedb.GetBalance(addr0), big.NewInt(0)) } func TestEncodeSigHeaderJaipur(t *testing.T) { @@ -124,19 +126,64 @@ func TestEncodeSigHeaderJaipur(t *testing.T) { // Jaipur NOT enabled and BaseFee not set hash := SealHash(h, ¶ms.BorConfig{JaipurBlock: 10}) - assert.Equal(t, hash, hashWithoutBaseFee) + require.Equal(t, hash, hashWithoutBaseFee) // Jaipur enabled (Jaipur=0) and BaseFee not set hash = SealHash(h, ¶ms.BorConfig{JaipurBlock: 0}) - assert.Equal(t, hash, hashWithoutBaseFee) + require.Equal(t, hash, hashWithoutBaseFee) h.BaseFee = big.NewInt(2) // Jaipur enabled (Jaipur=Header block) and BaseFee set hash = SealHash(h, ¶ms.BorConfig{JaipurBlock: 1}) - assert.Equal(t, hash, hashWithBaseFee) + require.Equal(t, hash, hashWithBaseFee) // Jaipur NOT enabled and BaseFee set hash = SealHash(h, ¶ms.BorConfig{JaipurBlock: 10}) - assert.Equal(t, hash, hashWithoutBaseFee) + require.Equal(t, hash, hashWithoutBaseFee) +} + +// TestCheckpoint can be used for to fetch checkpoint +// count and checkpoint for debugging purpose. +// Also, this is kept only for local use. +func TestCheckpoint(t *testing.T) { + t.Skip() + t.Parallel() + + ctx := context.Background() + + // TODO: For testing, add heimdall url here + h := heimdall.NewHeimdallClient("http://localhost:1317") + + count, err := h.FetchCheckpointCount(ctx) + if err != nil { + t.Error(err) + } + + t.Log("Count:", count) + + checkpoint1, err := h.FetchCheckpoint(ctx, count) + if err != nil { + t.Error(err) + } + + t.Log("Checkpoint1:", checkpoint1) + + checkpoint2, err := h.FetchCheckpoint(ctx, 10000) + if err != nil { + t.Error(err) + } + + t.Log("Checkpoint2:", checkpoint2) + + checkpoint3, err := h.FetchCheckpoint(ctx, -1) + if err != nil { + t.Error(err) + } + + t.Log("Checkpoint3:", checkpoint3) + + if checkpoint3.RootHash != checkpoint1.RootHash { + t.Fatal("Invalid root hash") + } } diff --git a/consensus/bor/contract/client.go b/consensus/bor/contract/client.go index dbf3fa0883..9e9e1392dd 100644 --- a/consensus/bor/contract/client.go +++ b/consensus/bor/contract/client.go @@ -102,7 +102,8 @@ func (gc *GenesisContractsClient) CommitState( func (gc *GenesisContractsClient) LastStateId(snapshotNumber uint64) (*big.Int, error) { blockNr := rpc.BlockNumber(snapshotNumber) - method := "lastStateId" + + const method = "lastStateId" data, err := gc.stateReceiverABI.Pack(method) if err != nil { diff --git a/consensus/bor/heimdall.go b/consensus/bor/heimdall.go index e640e47d8a..8934e07cb4 100644 --- a/consensus/bor/heimdall.go +++ b/consensus/bor/heimdall.go @@ -12,6 +12,7 @@ import ( type IHeimdallClient interface { StateSyncEvents(ctx context.Context, fromID uint64, to int64) ([]*clerk.EventRecordWithTime, error) Span(ctx context.Context, spanID uint64) (*span.HeimdallSpan, error) - FetchLatestCheckpoint(ctx context.Context) (*checkpoint.Checkpoint, error) + FetchCheckpoint(ctx context.Context, number int64) (*checkpoint.Checkpoint, error) + FetchCheckpointCount(ctx context.Context) (int64, error) Close() } diff --git a/consensus/bor/heimdall/checkpoint/checkpoint.go b/consensus/bor/heimdall/checkpoint/checkpoint.go index 77569293ad..3f85f2a233 100644 --- a/consensus/bor/heimdall/checkpoint/checkpoint.go +++ b/consensus/bor/heimdall/checkpoint/checkpoint.go @@ -20,3 +20,12 @@ type CheckpointResponse struct { Height string `json:"height"` Result Checkpoint `json:"result"` } + +type CheckpointCount struct { + Result int64 `json:"result"` +} + +type CheckpointCountResponse struct { + Height string `json:"height"` + Result CheckpointCount `json:"result"` +} diff --git a/consensus/bor/heimdall/client.go b/consensus/bor/heimdall/client.go index 64db4a6293..0f17cbc4d4 100644 --- a/consensus/bor/heimdall/client.go +++ b/consensus/bor/heimdall/client.go @@ -5,7 +5,7 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" + "io" "net/http" "net/url" "sort" @@ -59,7 +59,8 @@ func NewHeimdallClient(urlString string) *HeimdallClient { const ( fetchStateSyncEventsFormat = "from-id=%d&to-time=%d&limit=%d" fetchStateSyncEventsPath = "clerk/event-record/list" - fetchLatestCheckpoint = "/checkpoints/latest" + fetchCheckpoint = "/checkpoints/%s" + fetchCheckpointCount = "/checkpoints/count" fetchSpanFormat = "bor/span/%d" ) @@ -115,9 +116,9 @@ func (h *HeimdallClient) Span(ctx context.Context, spanID uint64) (*span.Heimdal return &response.Result, nil } -// FetchLatestCheckpoint fetches the latest bor submitted checkpoint from heimdall -func (h *HeimdallClient) FetchLatestCheckpoint(ctx context.Context) (*checkpoint.Checkpoint, error) { - url, err := latestCheckpointURL(h.urlString) +// FetchCheckpoint fetches the checkpoint from heimdall +func (h *HeimdallClient) FetchCheckpoint(ctx context.Context, number int64) (*checkpoint.Checkpoint, error) { + url, err := checkpointURL(h.urlString, number) if err != nil { return nil, err } @@ -130,6 +131,21 @@ func (h *HeimdallClient) FetchLatestCheckpoint(ctx context.Context) (*checkpoint return &response.Result, nil } +// FetchCheckpointCount fetches the checkpoint count from heimdall +func (h *HeimdallClient) FetchCheckpointCount(ctx context.Context) (int64, error) { + url, err := checkpointCountURL(h.urlString) + if err != nil { + return 0, err + } + + response, err := FetchWithRetry[checkpoint.CheckpointCountResponse](ctx, h.client, url, h.closeCh) + if err != nil { + return 0, err + } + + return response.Result.Result, nil +} + // FetchWithRetry returns data from heimdall with retry func FetchWithRetry[T any](ctx context.Context, client http.Client, url *url.URL, closeCh chan struct{}) (*T, error) { // request data once @@ -210,8 +226,19 @@ func stateSyncURL(urlString string, fromID uint64, to int64) (*url.URL, error) { return makeURL(urlString, fetchStateSyncEventsPath, queryParams) } -func latestCheckpointURL(urlString string) (*url.URL, error) { - return makeURL(urlString, fetchLatestCheckpoint, "") +func checkpointURL(urlString string, number int64) (*url.URL, error) { + url := "" + if number == -1 { + url = fmt.Sprintf(fetchCheckpoint, "latest") + } else { + url = fmt.Sprintf(fetchCheckpoint, fmt.Sprint(number)) + } + + return makeURL(urlString, url, "") +} + +func checkpointCountURL(urlString string) (*url.URL, error) { + return makeURL(urlString, fetchCheckpointCount, "") } func makeURL(urlString, rawPath, rawQuery string) (*url.URL, error) { @@ -251,7 +278,7 @@ func internalFetch(ctx context.Context, client http.Client, u *url.URL) ([]byte, } // get response - body, err := ioutil.ReadAll(res.Body) + body, err := io.ReadAll(res.Body) if err != nil { return nil, err } diff --git a/consensus/bor/heimdall/span/spanner.go b/consensus/bor/heimdall/span/spanner.go index 1007b19f03..e0f2d66c6b 100644 --- a/consensus/bor/heimdall/span/spanner.go +++ b/consensus/bor/heimdall/span/spanner.go @@ -44,7 +44,7 @@ func (c *ChainSpanner) GetCurrentSpan(ctx context.Context, headerHash common.Has blockNr := rpc.BlockNumberOrHashWithHash(headerHash, false) // method - method := "getCurrentSpan" + const method = "getCurrentSpan" data, err := c.validatorSet.Pack(method) if err != nil { diff --git a/consensus/bor/merkle.go b/consensus/bor/merkle.go index ef1b4eb87e..a2ce1d4023 100644 --- a/consensus/bor/merkle.go +++ b/consensus/bor/merkle.go @@ -39,7 +39,7 @@ func convertTo32(input []byte) (output [32]byte) { return } -func convert(input []([32]byte)) [][]byte { +func convert(input [][32]byte) [][]byte { output := make([][]byte, 0, len(input)) for _, in := range input { diff --git a/consensus/bor/snapshot.go b/consensus/bor/snapshot.go index f71ceae0ad..c95031e783 100644 --- a/consensus/bor/snapshot.go +++ b/consensus/bor/snapshot.go @@ -131,7 +131,7 @@ func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) { } // check if signer is in validator set - if !snap.ValidatorSet.HasAddress(signer.Bytes()) { + if !snap.ValidatorSet.HasAddress(signer) { return nil, &UnauthorizedSignerError{number, signer.Bytes()} } @@ -201,18 +201,18 @@ func (s *Snapshot) signers() []common.Address { } // Difficulty returns the difficulty for a particular signer at the current snapshot number -func (s *Snapshot) Difficulty(signer common.Address) uint64 { +func Difficulty(validatorSet *valset.ValidatorSet, signer common.Address) uint64 { // if signer is empty if signer == (common.Address{}) { return 1 } - validators := s.ValidatorSet.Validators - proposer := s.ValidatorSet.GetProposer().Address + validators := validatorSet.Validators + proposer := validatorSet.GetProposer().Address totalValidators := len(validators) - proposerIndex, _ := s.ValidatorSet.GetByAddress(proposer) - signerIndex, _ := s.ValidatorSet.GetByAddress(signer) + proposerIndex, _ := validatorSet.GetByAddress(proposer) + signerIndex, _ := validatorSet.GetByAddress(signer) // temp index tempIndex := signerIndex diff --git a/consensus/bor/snapshot_test.go b/consensus/bor/snapshot_test.go index 1a3e967613..de576c18d6 100644 --- a/consensus/bor/snapshot_test.go +++ b/consensus/bor/snapshot_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "pgregory.net/rapid" "github.com/ethereum/go-ethereum/common" @@ -34,7 +34,7 @@ func TestGetSignerSuccessionNumber_ProposerIsSigner(t *testing.T) { t.Fatalf("%s", err) } - assert.Equal(t, 0, successionNumber) + require.Equal(t, 0, successionNumber) } func TestGetSignerSuccessionNumber_SignerIndexIsLarger(t *testing.T) { @@ -60,7 +60,7 @@ func TestGetSignerSuccessionNumber_SignerIndexIsLarger(t *testing.T) { t.Fatalf("%s", err) } - assert.Equal(t, signerIndex-proposerIndex, successionNumber) + require.Equal(t, signerIndex-proposerIndex, successionNumber) } func TestGetSignerSuccessionNumber_SignerIndexIsSmaller(t *testing.T) { @@ -82,7 +82,7 @@ func TestGetSignerSuccessionNumber_SignerIndexIsSmaller(t *testing.T) { t.Fatalf("%s", err) } - assert.Equal(t, signerIndex+numVals-proposerIndex, successionNumber) + require.Equal(t, signerIndex+numVals-proposerIndex, successionNumber) } func TestGetSignerSuccessionNumber_ProposerNotFound(t *testing.T) { @@ -93,6 +93,8 @@ func TestGetSignerSuccessionNumber_ProposerNotFound(t *testing.T) { ValidatorSet: valset.NewValidatorSet(validators), } + require.Len(t, snap.ValidatorSet.Validators, numVals) + dummyProposerAddress := randomAddress() snap.ValidatorSet.Proposer = &valset.Validator{Address: dummyProposerAddress} @@ -100,11 +102,11 @@ func TestGetSignerSuccessionNumber_ProposerNotFound(t *testing.T) { signer := snap.ValidatorSet.Validators[3].Address _, err := snap.GetSignerSuccessionNumber(signer) - assert.NotNil(t, err) + require.NotNil(t, err) e, ok := err.(*UnauthorizedProposerError) - assert.True(t, ok) - assert.Equal(t, dummyProposerAddress.Bytes(), e.Proposer) + require.True(t, ok) + require.Equal(t, dummyProposerAddress.Bytes(), e.Proposer) } func TestGetSignerSuccessionNumber_SignerNotFound(t *testing.T) { @@ -116,10 +118,10 @@ func TestGetSignerSuccessionNumber_SignerNotFound(t *testing.T) { } dummySignerAddress := randomAddress() _, err := snap.GetSignerSuccessionNumber(dummySignerAddress) - assert.NotNil(t, err) + require.NotNil(t, err) e, ok := err.(*UnauthorizedSignerError) - assert.True(t, ok) - assert.Equal(t, dummySignerAddress.Bytes(), e.Signer) + require.True(t, ok) + require.Equal(t, dummySignerAddress.Bytes(), e.Signer) } // nolint: unparam diff --git a/consensus/bor/valset/validator.go b/consensus/bor/valset/validator.go index 250206c1f3..be3f46c830 100644 --- a/consensus/bor/valset/validator.go +++ b/consensus/bor/valset/validator.go @@ -47,21 +47,26 @@ func (v *Validator) Cmp(other *Validator) *Validator { return v } - // nolint:nestif if v.ProposerPriority > other.ProposerPriority { return v - } else if v.ProposerPriority < other.ProposerPriority { - return other - } else { - result := bytes.Compare(v.Address.Bytes(), other.Address.Bytes()) - if result < 0 { - return v - } else if result > 0 { - return other - } else { - panic("Cannot compare identical validators") - } } + + if v.ProposerPriority < other.ProposerPriority { + return other + } + + result := bytes.Compare(v.Address.Bytes(), other.Address.Bytes()) + + if result == 0 { + panic("Cannot compare identical validators") + } + + if result < 0 { + return v + } + + // result > 0 + return other } func (v *Validator) String() string { diff --git a/consensus/bor/valset/validator_set.go b/consensus/bor/valset/validator_set.go index 19e6c681fe..772d2c5ef8 100644 --- a/consensus/bor/valset/validator_set.go +++ b/consensus/bor/valset/validator_set.go @@ -46,6 +46,7 @@ type ValidatorSet struct { // cached (unexported) totalVotingPower int64 + validatorsMap map[common.Address]int // address -> index } // NewValidatorSet initializes a ValidatorSet by copying over the @@ -54,7 +55,7 @@ type ValidatorSet struct { // The addresses of validators in `valz` must be unique otherwise the // function panics. func NewValidatorSet(valz []*Validator) *ValidatorSet { - vals := &ValidatorSet{} + vals := &ValidatorSet{validatorsMap: make(map[common.Address]int)} err := vals.updateWithChangeSet(valz, false) if err != nil { @@ -232,30 +233,34 @@ func validatorListCopy(valsList []*Validator) []*Validator { // Copy each validator into a new ValidatorSet. func (vals *ValidatorSet) Copy() *ValidatorSet { + valCopy := validatorListCopy(vals.Validators) + validatorsMap := make(map[common.Address]int, len(vals.Validators)) + + for i, val := range valCopy { + validatorsMap[val.Address] = i + } + return &ValidatorSet{ Validators: validatorListCopy(vals.Validators), Proposer: vals.Proposer, totalVotingPower: vals.totalVotingPower, + validatorsMap: validatorsMap, } } // HasAddress returns true if address given is in the validator set, false - // otherwise. -func (vals *ValidatorSet) HasAddress(address []byte) bool { - idx := sort.Search(len(vals.Validators), func(i int) bool { - return bytes.Compare(address, vals.Validators[i].Address.Bytes()) <= 0 - }) +func (vals *ValidatorSet) HasAddress(address common.Address) bool { + _, ok := vals.validatorsMap[address] - return idx < len(vals.Validators) && bytes.Equal(vals.Validators[idx].Address.Bytes(), address) + return ok } // GetByAddress returns an index of the validator with address and validator // itself if found. Otherwise, -1 and nil are returned. func (vals *ValidatorSet) GetByAddress(address common.Address) (index int, val *Validator) { - idx := sort.Search(len(vals.Validators), func(i int) bool { - return bytes.Compare(address.Bytes(), vals.Validators[i].Address.Bytes()) <= 0 - }) - if idx < len(vals.Validators) && vals.Validators[idx].Address == address { + idx, ok := vals.validatorsMap[address] + if ok { return idx, vals.Validators[idx].Copy() } @@ -265,14 +270,14 @@ func (vals *ValidatorSet) GetByAddress(address common.Address) (index int, val * // GetByIndex returns the validator's address and validator itself by index. // It returns nil values if index is less than 0 or greater or equal to // len(ValidatorSet.Validators). -func (vals *ValidatorSet) GetByIndex(index int) (address []byte, val *Validator) { +func (vals *ValidatorSet) GetByIndex(index int) (address common.Address, val *Validator) { if index < 0 || index >= len(vals.Validators) { - return nil, nil + return common.Address{}, nil } val = vals.Validators[index] - return val.Address.Bytes(), val.Copy() + return val.Address, val.Copy() } // Size returns the length of the validator set. @@ -328,7 +333,7 @@ func (vals *ValidatorSet) GetProposer() (proposer *Validator) { func (vals *ValidatorSet) findProposer() *Validator { var proposer *Validator for _, val := range vals.Validators { - if proposer == nil || !bytes.Equal(val.Address.Bytes(), proposer.Address.Bytes()) { + if proposer == nil || val.Address != proposer.Address { proposer = proposer.Cmp(val) } } @@ -371,14 +376,19 @@ func processChanges(origChanges []*Validator) (updates, removals []*Validator, e changes := validatorListCopy(origChanges) sort.Sort(ValidatorsByAddress(changes)) - removals = make([]*Validator, 0, len(changes)) - updates = make([]*Validator, 0, len(changes)) + sliceCap := len(changes) / 2 + if sliceCap == 0 { + sliceCap = 1 + } + + removals = make([]*Validator, 0, sliceCap) + updates = make([]*Validator, 0, sliceCap) var prevAddr common.Address // Scan changes by address and append valid validators to updates or removals lists. for _, valUpdate := range changes { - if bytes.Equal(valUpdate.Address.Bytes(), prevAddr.Bytes()) { + if valUpdate.Address == prevAddr { err = fmt.Errorf("duplicate entry %v in %v", valUpdate, changes) return nil, nil, err } @@ -489,10 +499,11 @@ func (vals *ValidatorSet) applyUpdates(updates []*Validator) { } else { // Apply add or update. merged[i] = updates[0] - if bytes.Equal(existing[0].Address.Bytes(), updates[0].Address.Bytes()) { + if existing[0].Address == updates[0].Address { // Validator is present in both, advance existing. existing = existing[1:] } + updates = updates[1:] } i++ @@ -503,6 +514,7 @@ func (vals *ValidatorSet) applyUpdates(updates []*Validator) { merged[i] = existing[j] i++ } + // OR add updates which are left. for j := 0; j < len(updates); j++ { merged[i] = updates[j] @@ -541,7 +553,7 @@ func (vals *ValidatorSet) applyRemovals(deletes []*Validator) { // Loop over deletes until we removed all of them. for len(deletes) > 0 { - if bytes.Equal(existing[0].Address.Bytes(), deletes[0].Address.Bytes()) { + if existing[0].Address == deletes[0].Address { deletes = deletes[1:] } else { // Leave it in the resulting slice. merged[i] = existing[0] @@ -599,8 +611,7 @@ func (vals *ValidatorSet) updateWithChangeSet(changes []*Validator, allowDeletes computeNewPriorities(updates, vals, updatedTotalVotingPower) // Apply updates and removals. - vals.applyUpdates(updates) - vals.applyRemovals(deletes) + vals.updateValidators(updates, deletes) if err := vals.UpdateTotalVotingPower(); err != nil { return err @@ -613,6 +624,17 @@ func (vals *ValidatorSet) updateWithChangeSet(changes []*Validator, allowDeletes return nil } +func (vals *ValidatorSet) updateValidators(updates []*Validator, deletes []*Validator) { + vals.applyUpdates(updates) + vals.applyRemovals(deletes) + + vals.validatorsMap = make(map[common.Address]int, len(vals.Validators)) + + for i, val := range vals.Validators { + vals.validatorsMap[val.Address] = i + } +} + // UpdateWithChangeSet attempts to update the validator set with 'changes'. // It performs the following steps: // - validates the changes making sure there are no duplicates and splits them in updates and deletes @@ -661,7 +683,7 @@ func (vals *ValidatorSet) StringIndented(indent string) string { return "nil-ValidatorSet" } - var valStrings []string + valStrings := make([]string, 0, len(vals.Validators)) vals.Iterate(func(index int, val *Validator) bool { valStrings = append(valStrings, val.String()) diff --git a/consensus/bor/valset/validator_set_test.go b/consensus/bor/valset/validator_set_test.go new file mode 100644 index 0000000000..9397ca3e92 --- /dev/null +++ b/consensus/bor/valset/validator_set_test.go @@ -0,0 +1,199 @@ +package valset + +import ( + "testing" + + "github.com/stretchr/testify/require" + "gotest.tools/assert" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +func NewValidatorFromKey(key string, votingPower int64) *Validator { + privKey, _ := crypto.HexToECDSA(key) + + return NewValidator(crypto.PubkeyToAddress(privKey.PublicKey), votingPower) +} + +func GetValidators() [4]*Validator { + const ( + // addr0 = 0x96C42C56fdb78294F96B0cFa33c92bed7D75F96a + signer0 = "c8deb0bea5c41afe8e37b4d1bd84e31adff11b09c8c96ff4b605003cce067cd9" + + // addr1 = 0x98925BE497f6dFF6A5a33dDA8B5933cA35262d69 + signer1 = "c8deb0bea5c41afe8e37b4d1bd84e31adff11b09c8c96ff4b605003cce067cd8" + + //addr2 = 0x648Cf2A5b119E2c04061021834F8f75735B1D36b + signer2 = "c8deb0bea5c41afe8e37b4d1bd84e31adff11b09c8c96ff4b605003cce067cd7" + + //addr3 = 0x168f220B3b313D456eD4797520eFdFA9c57E6C45 + signer3 = "c8deb0bea5c41afe8e37b4d1bd84e31adff11b09c8c96ff4b605003cce067cd6" + ) + + return [4]*Validator{ + NewValidatorFromKey(signer0, 100), + NewValidatorFromKey(signer1, 200), + NewValidatorFromKey(signer2, 300), + NewValidatorFromKey(signer3, 400), + } +} + +func TestIncrementProposerPriority(t *testing.T) { + t.Parallel() + + vals := GetValidators() + + // Validator set length = 1 + valSet := NewValidatorSet(vals[:1]) + + expectedPropsers := []*Validator{vals[0], vals[0], vals[0], vals[0], vals[0], vals[0], vals[0], vals[0], vals[0], vals[0]} + + for i := 0; i < 10; i++ { + valSet.IncrementProposerPriority(1) + + require.Equal(t, expectedPropsers[i].Address, valSet.GetProposer().Address) + } + + // Validator set length = 2 + valSet = NewValidatorSet(vals[:2]) + + expectedPropsers = []*Validator{vals[0], vals[1], vals[1], vals[0], vals[1], vals[1], vals[0], vals[1], vals[1], vals[0]} + + for i := 0; i < 10; i++ { + valSet.IncrementProposerPriority(1) + + require.Equal(t, expectedPropsers[i].Address, valSet.GetProposer().Address) + } + + // Validator set length = 3 + valSet = NewValidatorSet(vals[:3]) + + expectedPropsers = []*Validator{vals[1], vals[2], vals[0], vals[1], vals[2], vals[2], vals[1], vals[2], vals[0], vals[1]} + + for i := 0; i < 10; i++ { + valSet.IncrementProposerPriority(1) + + require.Equal(t, expectedPropsers[i].Address, valSet.GetProposer().Address) + } + + // Validator set length = 4 + valSet = NewValidatorSet(vals[:4]) + + expectedPropsers = []*Validator{vals[2], vals[1], vals[3], vals[2], vals[0], vals[3], vals[1], vals[2], vals[3], vals[3]} + + for i := 0; i < 10; i++ { + valSet.IncrementProposerPriority(1) + + require.Equal(t, expectedPropsers[i].Address, valSet.GetProposer().Address) + } +} + +func TestRescalePriorities(t *testing.T) { + t.Parallel() + + vals := GetValidators() + + // Validator set length = 1 + valSet := NewValidatorSet(vals[:1]) + + valSet.RescalePriorities(10) + + expectedPriorities := []int64{0} + for i, val := range valSet.Validators { + require.Equal(t, expectedPriorities[i], val.ProposerPriority) + } + + // Validator set length = 2 + + valSet = NewValidatorSet(vals[:2]) + + valSet.RescalePriorities(100) + + expectedPriorities = []int64{50, -50} + for i, val := range valSet.Validators { + require.Equal(t, expectedPriorities[i], val.ProposerPriority) + } + + // Validator set length = 3 + + valSet = NewValidatorSet(vals[:3]) + + valSet.RescalePriorities(30) + + expectedPriorities = []int64{-17, 5, 11} + for i, val := range valSet.Validators { + require.Equal(t, expectedPriorities[i], val.ProposerPriority) + } + + // Validator set length = 4 + + valSet = NewValidatorSet(vals[:4]) + + valSet.RescalePriorities(10) + + expectedPriorities = []int64{-6, 3, 1, 2} + for i, val := range valSet.Validators { + require.Equal(t, expectedPriorities[i], val.ProposerPriority) + } +} + +func TestGetValidatorByAddressAndIndex(t *testing.T) { + t.Parallel() + + vals := GetValidators() + valSet := NewValidatorSet(vals[:4]) + + for _, val := range valSet.Validators { + idx, valByAddress := valSet.GetByAddress(val.Address) + addr, valByIndex := valSet.GetByIndex(idx) + + assert.DeepEqual(t, val, valByIndex) + assert.DeepEqual(t, val, valByAddress) + assert.DeepEqual(t, val.Address, addr) + } + + tempAddress := common.HexToAddress("0x12345") + + // Negative Testcase + idx, _ := valSet.GetByAddress(tempAddress) + require.Equal(t, idx, -1) + + // checking for validator index out of range + addr, _ := valSet.GetByIndex(100) + require.Equal(t, addr, common.Address{}) +} + +func TestUpdateWithChangeSet(t *testing.T) { + t.Parallel() + + vals := GetValidators() + valSet := NewValidatorSet(vals[:4]) + + // halved the power of vals[2] and doubled the power of vals[3] + vals[2].VotingPower = 150 + vals[3].VotingPower = 800 + + // Adding new temp validator in the set + const tempSigner = "c8deb0bea5c41afe8e37b4d1bd84e31adff11b09c8c96ff4b605003cce067cd5" + + tempVal := NewValidatorFromKey(tempSigner, 250) + + // check totalVotingPower before updating validator set + require.Equal(t, int64(1000), valSet.TotalVotingPower()) + + err := valSet.UpdateWithChangeSet([]*Validator{vals[2], vals[3], tempVal}) + require.NoError(t, err) + + // check totalVotingPower after updating validator set + require.Equal(t, int64(1500), valSet.TotalVotingPower()) + + _, updatedVal2 := valSet.GetByAddress(vals[2].Address) + require.Equal(t, int64(150), updatedVal2.VotingPower) + + _, updatedVal3 := valSet.GetByAddress(vals[3].Address) + require.Equal(t, int64(800), updatedVal3.VotingPower) + + _, updatedTempVal := valSet.GetByAddress(tempVal.Address) + require.Equal(t, int64(250), updatedTempVal.VotingPower) +} diff --git a/consensus/clique/clique_test.go b/consensus/clique/clique_test.go index 1bd32acd37..b2850587ea 100644 --- a/consensus/clique/clique_test.go +++ b/consensus/clique/clique_test.go @@ -55,7 +55,7 @@ func TestReimportMirroredState(t *testing.T) { genesis := genspec.MustCommit(db) // Generate a batch of blocks, each properly signed - chain, _ := core.NewBlockChain(db, nil, params.AllCliqueProtocolChanges, engine, vm.Config{}, nil, nil) + chain, _ := core.NewBlockChain(db, nil, params.AllCliqueProtocolChanges, engine, vm.Config{}, nil, nil, nil) defer chain.Stop() blocks, _ := core.GenerateChain(params.AllCliqueProtocolChanges, genesis, engine, db, 3, func(i int, block *core.BlockGen) { @@ -89,7 +89,7 @@ func TestReimportMirroredState(t *testing.T) { db = rawdb.NewMemoryDatabase() genspec.MustCommit(db) - chain, _ = core.NewBlockChain(db, nil, params.AllCliqueProtocolChanges, engine, vm.Config{}, nil, nil) + chain, _ = core.NewBlockChain(db, nil, params.AllCliqueProtocolChanges, engine, vm.Config{}, nil, nil, nil) defer chain.Stop() if _, err := chain.InsertChain(blocks[:2]); err != nil { @@ -102,7 +102,7 @@ func TestReimportMirroredState(t *testing.T) { // Simulate a crash by creating a new chain on top of the database, without // flushing the dirty states out. Insert the last block, triggering a sidechain // reimport. - chain, _ = core.NewBlockChain(db, nil, params.AllCliqueProtocolChanges, engine, vm.Config{}, nil, nil) + chain, _ = core.NewBlockChain(db, nil, params.AllCliqueProtocolChanges, engine, vm.Config{}, nil, nil, nil) defer chain.Stop() if _, err := chain.InsertChain(blocks[2:]); err != nil { diff --git a/consensus/clique/snapshot_test.go b/consensus/clique/snapshot_test.go index 094868ca74..54621718e0 100644 --- a/consensus/clique/snapshot_test.go +++ b/consensus/clique/snapshot_test.go @@ -450,7 +450,7 @@ func TestClique(t *testing.T) { batches[len(batches)-1] = append(batches[len(batches)-1], block) } // Pass all the headers through clique and ensure tallying succeeds - chain, err := core.NewBlockChain(db, nil, &config, engine, vm.Config{}, nil, nil) + chain, err := core.NewBlockChain(db, nil, &config, engine, vm.Config{}, nil, nil, nil) if err != nil { t.Errorf("test %d: failed to create test chain: %v", i, err) continue diff --git a/core/bench_test.go b/core/bench_test.go index ad6179d0a8..3d4cf31a4e 100644 --- a/core/bench_test.go +++ b/core/bench_test.go @@ -201,7 +201,7 @@ func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) { // Time the insertion of the new chain. // State and blocks are stored in the same DB. - chainman, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + chainman, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil) defer chainman.Stop() b.ReportAllocs() b.ResetTimer() @@ -317,7 +317,8 @@ func benchReadChain(b *testing.B, full bool, count uint64) { if err != nil { b.Fatalf("error opening database at %v: %v", dir, err) } - chain, err := NewBlockChain(db, &cacheConfig, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil) + + chain, err := NewBlockChain(db, &cacheConfig, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil, nil) if err != nil { b.Fatalf("error creating chain: %v", err) } diff --git a/core/block_validator_test.go b/core/block_validator_test.go index 0f183ba527..c777d31c4a 100644 --- a/core/block_validator_test.go +++ b/core/block_validator_test.go @@ -49,7 +49,7 @@ func TestHeaderVerification(t *testing.T) { headers[i] = block.Header() } // Run the header checker for blocks one-by-one, checking for both valid and invalid nonces - chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil) + chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil, nil) defer chain.Stop() for i := 0; i < len(blocks); i++ { @@ -168,7 +168,7 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) { t.Logf("Log header after the merging %d: %v", block.NumberU64(), string(blob)) } // Run the header checker for blocks one-by-one, checking for both valid and invalid nonces - chain, _ := NewBlockChain(testdb, nil, chainConfig, runEngine, vm.Config{}, nil, nil) + chain, _ := NewBlockChain(testdb, nil, chainConfig, runEngine, vm.Config{}, nil, nil, nil) defer chain.Stop() // Verify the blocks before the merging @@ -279,11 +279,11 @@ func testHeaderConcurrentVerification(t *testing.T, threads int) { var results <-chan error if valid { - chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil) + chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil, nil) _, results = chain.engine.VerifyHeaders(chain, headers, seals) chain.Stop() } else { - chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFakeFailer(uint64(len(headers)-1)), vm.Config{}, nil, nil) + chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFakeFailer(uint64(len(headers)-1)), vm.Config{}, nil, nil, nil) _, results = chain.engine.VerifyHeaders(chain, headers, seals) chain.Stop() } @@ -346,7 +346,7 @@ func testHeaderConcurrentAbortion(t *testing.T, threads int) { defer runtime.GOMAXPROCS(old) // Start the verifications and immediately abort - chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFakeDelayer(time.Millisecond), vm.Config{}, nil, nil) + chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFakeDelayer(time.Millisecond), vm.Config{}, nil, nil, nil) defer chain.Stop() abort, results := chain.engine.VerifyHeaders(chain, headers, seals) diff --git a/core/blockchain.go b/core/blockchain.go index 8a4c581f66..fe8172e41e 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -29,6 +29,7 @@ import ( lru "github.com/hashicorp/golang-lru" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/common/prque" @@ -38,6 +39,7 @@ import ( "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/downloader/whitelist" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/internal/syncx" @@ -221,7 +223,8 @@ type BlockChain struct { // NewBlockChain returns a fully initialised block chain using information // available in the database. It initialises the default Ethereum Validator // and Processor. -func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *params.ChainConfig, engine consensus.Engine, vmConfig vm.Config, shouldPreserve func(header *types.Header) bool, txLookupLimit *uint64) (*BlockChain, error) { +//nolint:gocognit +func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *params.ChainConfig, engine consensus.Engine, vmConfig vm.Config, shouldPreserve func(header *types.Header) bool, txLookupLimit *uint64, checker ethereum.ChainValidator) (*BlockChain, error) { if cacheConfig == nil { cacheConfig = DefaultCacheConfig } @@ -257,7 +260,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par borReceiptsCache: borReceiptsCache, } - bc.forker = NewForkChoice(bc, shouldPreserve) + bc.forker = NewForkChoice(bc, shouldPreserve, checker) bc.validator = NewBlockValidator(chainConfig, bc, engine) bc.prefetcher = newStatePrefetcher(chainConfig, bc, engine) bc.processor = NewStateProcessor(chainConfig, bc, engine) @@ -908,6 +911,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ ancientBlocks, liveBlocks types.Blocks ancientReceipts, liveReceipts []types.Receipts ) + // Do a sanity check that the provided chain is actually ordered and linked for i := 0; i < len(blockChain); i++ { if i != 0 { @@ -933,7 +937,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ // updateHead updates the head fast sync block if the inserted blocks are better // and returns an indicator whether the inserted blocks are canonical. - updateHead := func(head *types.Block) bool { + updateHead := func(head *types.Block, headers []*types.Header) bool { if !bc.chainmu.TryLock() { return false } @@ -948,6 +952,14 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ } else if !reorg { return false } + + isValid, err := bc.forker.ValidateReorg(bc.CurrentFastBlock().Header(), headers) + if err != nil { + log.Warn("Reorg failed", "err", err) + return false + } else if !isValid { + return false + } rawdb.WriteHeadFastBlockHash(bc.db, head.Hash()) bc.currentFastBlock.Store(head) headFastBlockGauge.Update(int64(head.NumberU64())) @@ -985,10 +997,14 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ return 0, fmt.Errorf("containing header #%d [%x..] unknown", last.Number(), last.Hash().Bytes()[:4]) } - // BOR: Retrieve all the bor receipts. + // BOR: Retrieve all the bor receipts and also maintain the array of headers + // for bor specific reorg check. borReceipts := []types.Receipts{} + + var headers []*types.Header for _, block := range blockChain { borReceipts = append(borReceipts, []*types.Receipt{bc.GetBorReceiptByHash(block.Hash())}) + headers = append(headers, block.Header()) } // Write all chain data to ancients. @@ -1039,7 +1055,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ } // Update the current fast block because all block data is now present in DB. previousFastBlock := bc.CurrentFastBlock().NumberU64() - if !updateHead(blockChain[len(blockChain)-1]) { + if !updateHead(blockChain[len(blockChain)-1], headers) { // We end up here if the header chain has reorg'ed, and the blocks/receipts // don't match the canonical chain. if err := bc.db.TruncateHead(previousFastBlock + 1); err != nil { @@ -1075,7 +1091,11 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ writeLive := func(blockChain types.Blocks, receiptChain []types.Receipts) (int, error) { skipPresenceCheck := false batch := bc.db.NewBatch() + headers := make([]*types.Header, 0, len(blockChain)) for i, block := range blockChain { + // Update the headers for bor specific reorg check + headers = append(headers, block.Header()) + // Short circuit insertion if shutting down or processing failed if bc.insertStopped() { return 0, errInsertionInterrupted @@ -1122,7 +1142,8 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ return 0, err } } - updateHead(blockChain[len(blockChain)-1]) + + updateHead(blockChain[len(blockChain)-1], headers) return 0, nil } @@ -1491,6 +1512,18 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool) it := newInsertIterator(chain, results, bc.validator) block, err := it.next() + // Check the validity of incoming chain + isValid, err1 := bc.forker.ValidateReorg(bc.CurrentBlock().Header(), headers) + if err1 != nil { + return it.index, err1 + } + + if !isValid { + // The chain to be imported is invalid as the blocks doesn't match with + // the whitelisted checkpoints. + return it.index, whitelist.ErrCheckpointMismatch + } + // Left-trim all the known blocks that don't need to build snapshot if bc.skipBlock(err, it) { // First block (and state) is known @@ -1833,6 +1866,7 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i externTd *big.Int lastBlock = block current = bc.CurrentBlock() + headers []*types.Header ) // The first sidechain block error is already verified to be ErrPrunedAncestor. // Since we don't import them here, we expect ErrUnknownAncestor for the remaining @@ -1840,6 +1874,7 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i // to disk. err := consensus.ErrPrunedAncestor for ; block != nil && errors.Is(err, consensus.ErrPrunedAncestor); block, err = it.next() { + headers = append(headers, block.Header()) // Check the canonical state root for that number if number := block.NumberU64(); current.NumberU64() >= number { canonical := bc.GetBlockByNumber(number) @@ -1895,7 +1930,13 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i if err != nil { return it.index, err } - if !reorg { + + isValid, err := bc.forker.ValidateReorg(current.Header(), headers) + if err != nil { + return it.index, err + } + + if !reorg || !isValid { localTd := bc.GetTd(current.Hash(), current.NumberU64()) log.Info("Sidechain written to disk", "start", it.first().NumberU64(), "end", it.previous().Number, "sidetd", externTd, "localtd", localTd) return it.index, err diff --git a/core/blockchain_bor_test.go b/core/blockchain_bor_test.go index 1b0373c41a..6bc38ec4ab 100644 --- a/core/blockchain_bor_test.go +++ b/core/blockchain_bor_test.go @@ -27,7 +27,7 @@ func TestChain2HeadEvent(t *testing.T) { signer = types.LatestSigner(gspec.Config) ) - blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil) defer blockchain.Stop() chain2HeadCh := make(chan Chain2HeadEvent, 64) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index de97000918..b2c14f81ba 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -59,7 +59,7 @@ func newCanonical(engine consensus.Engine, n int, full bool) (ethdb.Database, *B ) // Initialize a fresh chain with only a genesis block - blockchain, _ := NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) + blockchain, _ := NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil, nil) // Create and inject the requested chain if n == 0 { return db, blockchain, nil @@ -655,7 +655,7 @@ func testReorgBadHashes(t *testing.T, full bool) { blockchain.Stop() // Create a new BlockChain and check that it rolled back the state. - ncm, err := NewBlockChain(blockchain.db, nil, blockchain.chainConfig, ethash.NewFaker(), vm.Config{}, nil, nil) + ncm, err := NewBlockChain(blockchain.db, nil, blockchain.chainConfig, ethash.NewFaker(), vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("failed to create new chain manager: %v", err) } @@ -768,7 +768,7 @@ func TestFastVsFullChains(t *testing.T) { // Import the chain as an archive node for the comparison baseline archiveDb := rawdb.NewMemoryDatabase() gspec.MustCommit(archiveDb) - archive, _ := NewBlockChain(archiveDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + archive, _ := NewBlockChain(archiveDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil) defer archive.Stop() if n, err := archive.InsertChain(blocks); err != nil { @@ -777,7 +777,7 @@ func TestFastVsFullChains(t *testing.T) { // Fast import the chain as a non-archive node to test fastDb := rawdb.NewMemoryDatabase() gspec.MustCommit(fastDb) - fast, _ := NewBlockChain(fastDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + fast, _ := NewBlockChain(fastDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil) defer fast.Stop() headers := make([]*types.Header, len(blocks)) @@ -801,7 +801,7 @@ func TestFastVsFullChains(t *testing.T) { t.Fatalf("failed to create temp freezer db: %v", err) } gspec.MustCommit(ancientDb) - ancient, _ := NewBlockChain(ancientDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + ancient, _ := NewBlockChain(ancientDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil) defer ancient.Stop() if n, err := ancient.InsertHeaderChain(headers, 1); err != nil { @@ -923,7 +923,7 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) { archiveCaching := *DefaultCacheConfig archiveCaching.TrieDirtyDisabled = true - archive, _ := NewBlockChain(archiveDb, &archiveCaching, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + archive, _ := NewBlockChain(archiveDb, &archiveCaching, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil) if n, err := archive.InsertChain(blocks); err != nil { t.Fatalf("failed to process block %d: %v", n, err) } @@ -936,7 +936,8 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) { // Import the chain as a non-archive node and ensure all pointers are updated fastDb, delfn := makeDb() defer delfn() - fast, _ := NewBlockChain(fastDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + + fast, _ := NewBlockChain(fastDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil) defer fast.Stop() headers := make([]*types.Header, len(blocks)) @@ -956,7 +957,8 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) { // Import the chain as a ancient-first node and ensure all pointers are updated ancientDb, delfn := makeDb() defer delfn() - ancient, _ := NewBlockChain(ancientDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + + ancient, _ := NewBlockChain(ancientDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil) defer ancient.Stop() if n, err := ancient.InsertHeaderChain(headers, 1); err != nil { @@ -975,7 +977,8 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) { // Import the chain as a light node and ensure all pointers are updated lightDb, delfn := makeDb() defer delfn() - light, _ := NewBlockChain(lightDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + + light, _ := NewBlockChain(lightDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil) if n, err := light.InsertHeaderChain(headers, 1); err != nil { t.Fatalf("failed to insert header %d: %v", n, err) } @@ -1044,7 +1047,7 @@ func TestChainTxReorgs(t *testing.T) { } }) // Import the chain. This runs all block validation rules. - blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil) if i, err := blockchain.InsertChain(chain); err != nil { t.Fatalf("failed to insert original chain[%d]: %v", i, err) } @@ -1114,7 +1117,7 @@ func TestLogReorgs(t *testing.T) { signer = types.LatestSigner(gspec.Config) ) - blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil) defer blockchain.Stop() rmLogsCh := make(chan RemovedLogsEvent) @@ -1167,7 +1170,7 @@ func TestLogRebirth(t *testing.T) { genesis = gspec.MustCommit(db) signer = types.LatestSigner(gspec.Config) engine = ethash.NewFaker() - blockchain, _ = NewBlockChain(db, nil, gspec.Config, engine, vm.Config{}, nil, nil) + blockchain, _ = NewBlockChain(db, nil, gspec.Config, engine, vm.Config{}, nil, nil, nil) ) defer blockchain.Stop() @@ -1230,7 +1233,7 @@ func TestSideLogRebirth(t *testing.T) { gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000000)}}} genesis = gspec.MustCommit(db) signer = types.LatestSigner(gspec.Config) - blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil) ) defer blockchain.Stop() @@ -1305,7 +1308,7 @@ func TestReorgSideEvent(t *testing.T) { signer = types.LatestSigner(gspec.Config) ) - blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil) defer blockchain.Stop() chain, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 3, func(i int, gen *BlockGen) {}) @@ -1437,7 +1440,7 @@ func TestEIP155Transition(t *testing.T) { genesis = gspec.MustCommit(db) ) - blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil) defer blockchain.Stop() blocks, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 4, func(i int, block *BlockGen) { @@ -1545,7 +1548,8 @@ func TestEIP161AccountRemoval(t *testing.T) { } genesis = gspec.MustCommit(db) ) - blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + + blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil) defer blockchain.Stop() blocks, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 3, func(i int, block *BlockGen) { @@ -1620,7 +1624,7 @@ func TestBlockchainHeaderchainReorgConsistency(t *testing.T) { diskdb := rawdb.NewMemoryDatabase() (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(diskdb) - chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -1664,7 +1668,7 @@ func TestTrieForkGC(t *testing.T) { diskdb := rawdb.NewMemoryDatabase() (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(diskdb) - chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -1703,7 +1707,7 @@ func TestLargeReorgTrieGC(t *testing.T) { diskdb := rawdb.NewMemoryDatabase() (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(diskdb) - chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -1764,7 +1768,7 @@ func TestBlockchainRecovery(t *testing.T) { t.Fatalf("failed to create temp freezer db: %v", err) } gspec.MustCommit(ancientDb) - ancient, _ := NewBlockChain(ancientDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + ancient, _ := NewBlockChain(ancientDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil) headers := make([]*types.Header, len(blocks)) for i, block := range blocks { @@ -1784,7 +1788,7 @@ func TestBlockchainRecovery(t *testing.T) { rawdb.WriteHeadFastBlockHash(ancientDb, midBlock.Hash()) // Reopen broken blockchain again - ancient, _ = NewBlockChain(ancientDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + ancient, _ = NewBlockChain(ancientDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil) defer ancient.Stop() if num := ancient.CurrentBlock().NumberU64(); num != 0 { t.Errorf("head block mismatch: have #%v, want #%v", num, 0) @@ -1836,7 +1840,7 @@ func TestInsertReceiptChainRollback(t *testing.T) { } gspec := Genesis{Config: params.AllEthashProtocolChanges} gspec.MustCommit(ancientDb) - ancientChain, _ := NewBlockChain(ancientDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + ancientChain, _ := NewBlockChain(ancientDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil) defer ancientChain.Stop() // Import the canonical header chain. @@ -1897,7 +1901,7 @@ func TestLowDiffLongChain(t *testing.T) { diskdb := rawdb.NewMemoryDatabase() (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(diskdb) - chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -1963,7 +1967,7 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon // Generate and import the canonical chain diskdb := rawdb.NewMemoryDatabase() gspec.MustCommit(diskdb) - chain, err := NewBlockChain(diskdb, nil, &chainConfig, runEngine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(diskdb, nil, &chainConfig, runEngine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -2102,7 +2106,7 @@ func testInsertKnownChainData(t *testing.T, typ string) { (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(chaindb) defer os.RemoveAll(dir) - chain, err := NewBlockChain(chaindb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(chaindb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -2266,7 +2270,7 @@ func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight i (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(chaindb) defer os.RemoveAll(dir) - chain, err := NewBlockChain(chaindb, nil, &chainConfig, runEngine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(chaindb, nil, &chainConfig, runEngine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -2384,7 +2388,7 @@ func getLongAndShortChains() (bc *BlockChain, longChain []*types.Block, heavyCha diskdb := rawdb.NewMemoryDatabase() (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(diskdb) - chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil, nil) if err != nil { return nil, nil, nil, fmt.Errorf("failed to create tester chain: %v", err) } @@ -2577,7 +2581,7 @@ func TestTransactionIndices(t *testing.T) { // Import all blocks into ancient db l := uint64(0) - chain, err := NewBlockChain(ancientDb, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, &l) + chain, err := NewBlockChain(ancientDb, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, &l, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -2602,7 +2606,7 @@ func TestTransactionIndices(t *testing.T) { t.Fatalf("failed to create temp freezer db: %v", err) } gspec.MustCommit(ancientDb) - chain, err = NewBlockChain(ancientDb, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, &l) + chain, err = NewBlockChain(ancientDb, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, &l, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -2626,7 +2630,7 @@ func TestTransactionIndices(t *testing.T) { limit = []uint64{0, 64 /* drop stale */, 32 /* shorten history */, 64 /* extend history */, 0 /* restore all */} tails := []uint64{0, 67 /* 130 - 64 + 1 */, 100 /* 131 - 32 + 1 */, 69 /* 132 - 64 + 1 */, 0} for i, l := range limit { - chain, err = NewBlockChain(ancientDb, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, &l) + chain, err = NewBlockChain(ancientDb, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, &l, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -2704,7 +2708,7 @@ func TestSkipStaleTxIndicesInSnapSync(t *testing.T) { // Import all blocks into ancient db, only HEAD-32 indices are kept. l := uint64(32) - chain, err := NewBlockChain(ancientDb, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, &l) + chain, err := NewBlockChain(ancientDb, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, &l, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -2768,7 +2772,7 @@ func benchmarkLargeNumberOfValueToNonexisting(b *testing.B, numTxs, numBlocks in diskdb := rawdb.NewMemoryDatabase() gspec.MustCommit(diskdb) - chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil, nil) if err != nil { b.Fatalf("failed to create tester chain: %v", err) } @@ -2851,7 +2855,7 @@ func TestSideImportPrunedBlocks(t *testing.T) { diskdb := rawdb.NewMemoryDatabase() (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(diskdb) - chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -2945,7 +2949,7 @@ func TestDeleteCreateRevert(t *testing.T) { diskdb := rawdb.NewMemoryDatabase() gspec.MustCommit(diskdb) - chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -3059,7 +3063,7 @@ func TestDeleteRecreateSlots(t *testing.T) { chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{ Debug: true, Tracer: logger.NewJSONLogger(nil, os.Stdout), - }, nil, nil) + }, nil, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -3139,7 +3143,7 @@ func TestDeleteRecreateAccount(t *testing.T) { chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{ Debug: true, Tracer: logger.NewJSONLogger(nil, os.Stdout), - }, nil, nil) + }, nil, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -3312,7 +3316,7 @@ func TestDeleteRecreateSlotsAcrossManyBlocks(t *testing.T) { chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{ //Debug: true, //Tracer: vm.NewJSONLogger(nil, os.Stdout), - }, nil, nil) + }, nil, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -3446,7 +3450,7 @@ func TestInitThenFailCreateContract(t *testing.T) { chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{ //Debug: true, //Tracer: vm.NewJSONLogger(nil, os.Stdout), - }, nil, nil) + }, nil, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -3533,7 +3537,7 @@ func TestEIP2718Transition(t *testing.T) { diskdb := rawdb.NewMemoryDatabase() gspec.MustCommit(diskdb) - chain, err := NewBlockChain(diskdb, nil, gspec.Config, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(diskdb, nil, gspec.Config, engine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -3628,7 +3632,7 @@ func TestEIP1559Transition(t *testing.T) { diskdb := rawdb.NewMemoryDatabase() gspec.MustCommit(diskdb) - chain, err := NewBlockChain(diskdb, nil, gspec.Config, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(diskdb, nil, gspec.Config, engine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } diff --git a/core/chain_makers_test.go b/core/chain_makers_test.go index 85a029f7c7..d3024cf90a 100644 --- a/core/chain_makers_test.go +++ b/core/chain_makers_test.go @@ -79,7 +79,7 @@ func ExampleGenerateChain() { }) // Import the chain. This runs all block validation rules. - blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil) defer blockchain.Stop() if i, err := blockchain.InsertChain(chain); err != nil { diff --git a/core/dao_test.go b/core/dao_test.go index c9c765a383..b43589ff51 100644 --- a/core/dao_test.go +++ b/core/dao_test.go @@ -45,7 +45,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { proConf.DAOForkBlock = forkBlock proConf.DAOForkSupport = true - proBc, _ := NewBlockChain(proDb, nil, &proConf, ethash.NewFaker(), vm.Config{}, nil, nil) + proBc, _ := NewBlockChain(proDb, nil, &proConf, ethash.NewFaker(), vm.Config{}, nil, nil, nil) defer proBc.Stop() conDb := rawdb.NewMemoryDatabase() @@ -55,7 +55,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { conConf.DAOForkBlock = forkBlock conConf.DAOForkSupport = false - conBc, _ := NewBlockChain(conDb, nil, &conConf, ethash.NewFaker(), vm.Config{}, nil, nil) + conBc, _ := NewBlockChain(conDb, nil, &conConf, ethash.NewFaker(), vm.Config{}, nil, nil, nil) defer conBc.Stop() if _, err := proBc.InsertChain(prefix); err != nil { @@ -69,7 +69,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { // Create a pro-fork block, and try to feed into the no-fork chain db = rawdb.NewMemoryDatabase() gspec.MustCommit(db) - bc, _ := NewBlockChain(db, nil, &conConf, ethash.NewFaker(), vm.Config{}, nil, nil) + bc, _ := NewBlockChain(db, nil, &conConf, ethash.NewFaker(), vm.Config{}, nil, nil, nil) defer bc.Stop() blocks := conBc.GetBlocksFromHash(conBc.CurrentBlock().Hash(), int(conBc.CurrentBlock().NumberU64())) @@ -94,7 +94,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { // Create a no-fork block, and try to feed into the pro-fork chain db = rawdb.NewMemoryDatabase() gspec.MustCommit(db) - bc, _ = NewBlockChain(db, nil, &proConf, ethash.NewFaker(), vm.Config{}, nil, nil) + bc, _ = NewBlockChain(db, nil, &proConf, ethash.NewFaker(), vm.Config{}, nil, nil, nil) defer bc.Stop() blocks = proBc.GetBlocksFromHash(proBc.CurrentBlock().Hash(), int(proBc.CurrentBlock().NumberU64())) @@ -120,7 +120,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { // Verify that contra-forkers accept pro-fork extra-datas after forking finishes db = rawdb.NewMemoryDatabase() gspec.MustCommit(db) - bc, _ := NewBlockChain(db, nil, &conConf, ethash.NewFaker(), vm.Config{}, nil, nil) + bc, _ := NewBlockChain(db, nil, &conConf, ethash.NewFaker(), vm.Config{}, nil, nil, nil) defer bc.Stop() blocks := conBc.GetBlocksFromHash(conBc.CurrentBlock().Hash(), int(conBc.CurrentBlock().NumberU64())) @@ -140,7 +140,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { // Verify that pro-forkers accept contra-fork extra-datas after forking finishes db = rawdb.NewMemoryDatabase() gspec.MustCommit(db) - bc, _ = NewBlockChain(db, nil, &proConf, ethash.NewFaker(), vm.Config{}, nil, nil) + bc, _ = NewBlockChain(db, nil, &proConf, ethash.NewFaker(), vm.Config{}, nil, nil, nil) defer bc.Stop() blocks = proBc.GetBlocksFromHash(proBc.CurrentBlock().Hash(), int(proBc.CurrentBlock().NumberU64())) diff --git a/core/forkchoice.go b/core/forkchoice.go index b0dbb200ec..a2ce4cd7d9 100644 --- a/core/forkchoice.go +++ b/core/forkchoice.go @@ -22,6 +22,7 @@ import ( "math/big" mrand "math/rand" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/types" @@ -54,18 +55,21 @@ type ForkChoice struct { // local td is equal to the extern one. It can be nil for light // client preserve func(header *types.Header) bool + + validator ethereum.ChainValidator } -func NewForkChoice(chainReader ChainReader, preserve func(header *types.Header) bool) *ForkChoice { +func NewForkChoice(chainReader ChainReader, preserve func(header *types.Header) bool, validator ethereum.ChainValidator) *ForkChoice { // Seed a fast but crypto originating random generator seed, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64)) if err != nil { log.Crit("Failed to initialize random seed", "err", err) } return &ForkChoice{ - chain: chainReader, - rand: mrand.New(mrand.NewSource(seed.Int64())), - preserve: preserve, + chain: chainReader, + rand: mrand.New(mrand.NewSource(seed.Int64())), //nolint:gosec + preserve: preserve, + validator: validator, } } @@ -106,3 +110,15 @@ func (f *ForkChoice) ReorgNeeded(current *types.Header, header *types.Header) (b } return reorg, nil } + +// ValidateReorg calls the chain validator service to check if the reorg is valid or not +func (f *ForkChoice) ValidateReorg(current *types.Header, chain []*types.Header) (bool, error) { + // Call the bor chain validator service + if f.validator != nil { + if isValid := f.validator.IsValidChain(current, chain); !isValid { + return false, nil + } + } + + return true, nil +} diff --git a/core/forkchoice_test.go b/core/forkchoice_test.go new file mode 100644 index 0000000000..2e7b40d8ff --- /dev/null +++ b/core/forkchoice_test.go @@ -0,0 +1,240 @@ +package core + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" +) + +// chainValidatorFake is a mock for the chain validator service +type chainValidatorFake struct { + validate func(currentHeader *types.Header, chain []*types.Header) bool +} + +// chainReaderFake is a mock for the chain reader service +type chainReaderFake struct { + getTd func(hash common.Hash, number uint64) *big.Int +} + +func newChainValidatorFake(validate func(currentHeader *types.Header, chain []*types.Header) bool) *chainValidatorFake { + return &chainValidatorFake{validate: validate} +} + +func newChainReaderFake(getTd func(hash common.Hash, number uint64) *big.Int) *chainReaderFake { + return &chainReaderFake{getTd: getTd} +} + +func TestPastChainInsert(t *testing.T) { + t.Parallel() + + var ( + db = rawdb.NewMemoryDatabase() + genesis = (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db) + ) + + hc, err := NewHeaderChain(db, params.AllEthashProtocolChanges, ethash.NewFaker(), func() bool { return false }) + if err != nil { + t.Fatal(err) + } + + // Create mocks for forker + getTd := func(hash common.Hash, number uint64) *big.Int { + return big.NewInt(int64(number)) + } + validate := func(currentHeader *types.Header, chain []*types.Header) bool { + // Put all explicit conditions here + // If canonical chain is empty and we're importing a chain of 64 blocks + if currentHeader.Number.Uint64() == uint64(0) && len(chain) == 64 { + return true + } + // If canonical chain is of len 64 and we're importing a past chain from 54-64, then accept it + if currentHeader.Number.Uint64() == uint64(64) && chain[0].Number.Uint64() == 55 && len(chain) == 10 { + return true + } + + return false + } + mockChainReader := newChainReaderFake(getTd) + mockChainValidator := newChainValidatorFake(validate) + mockForker := NewForkChoice(mockChainReader, nil, mockChainValidator) + + // chain A: G->A1->A2...A64 + chainA := makeHeaderChain(genesis.Header(), 64, ethash.NewFaker(), db, 10) + + // Inserting 64 headers on an empty chain + // expecting 1 write status with no error + testInsert(t, hc, chainA, CanonStatTy, nil, mockForker) + + // The current chain is: G->A1->A2...A64 + // chain B: G->A1->A2...A44->B45->B46...B64 + chainB := makeHeaderChain(chainA[43], 20, ethash.NewFaker(), db, 10) + + // The current chain is: G->A1->A2...A64 + // chain C: G->A1->A2...A54->C55->C56...C64 + chainC := makeHeaderChain(chainA[53], 10, ethash.NewFaker(), db, 10) + + // Update the function to consider chainC with higher difficulty + getTd = func(hash common.Hash, number uint64) *big.Int { + td := big.NewInt(int64(number)) + if hash == chainB[len(chainB)-1].Hash() || hash == chainC[len(chainC)-1].Hash() { + td = big.NewInt(65) + } + + return td + } + mockChainReader = newChainReaderFake(getTd) + mockForker = NewForkChoice(mockChainReader, nil, mockChainValidator) + + // Inserting 20 blocks from chainC on canonical chain + // expecting 2 write status with no error + testInsert(t, hc, chainB, SideStatTy, nil, mockForker) + + // Inserting 10 blocks from chainB on canonical chain + // expecting 1 write status with no error + testInsert(t, hc, chainC, CanonStatTy, nil, mockForker) +} + +func TestFutureChainInsert(t *testing.T) { + t.Parallel() + + var ( + db = rawdb.NewMemoryDatabase() + genesis = (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db) + ) + + hc, err := NewHeaderChain(db, params.AllEthashProtocolChanges, ethash.NewFaker(), func() bool { return false }) + if err != nil { + t.Fatal(err) + } + + // Create mocks for forker + getTd := func(hash common.Hash, number uint64) *big.Int { + return big.NewInt(int64(number)) + } + validate := func(currentHeader *types.Header, chain []*types.Header) bool { + // Put all explicit conditions here + // If canonical chain is empty and we're importing a chain of 64 blocks + if currentHeader.Number.Uint64() == uint64(0) && len(chain) == 64 { + return true + } + // If length of future chains > some value, they should not be accepted + if currentHeader.Number.Uint64() == uint64(64) && len(chain) <= 10 { + return true + } + + return false + } + mockChainReader := newChainReaderFake(getTd) + mockChainValidator := newChainValidatorFake(validate) + mockForker := NewForkChoice(mockChainReader, nil, mockChainValidator) + + // chain A: G->A1->A2...A64 + chainA := makeHeaderChain(genesis.Header(), 64, ethash.NewFaker(), db, 10) + + // Inserting 64 headers on an empty chain + // expecting 1 write status with no error + testInsert(t, hc, chainA, CanonStatTy, nil, mockForker) + + // The current chain is: G->A1->A2...A64 + // chain B: G->A1->A2...A64->B65->B66...B84 + chainB := makeHeaderChain(chainA[63], 20, ethash.NewFaker(), db, 10) + + // Inserting 20 headers on the canonical chain + // expecting 0 write status with no error + testInsert(t, hc, chainB, SideStatTy, nil, mockForker) + + // The current chain is: G->A1->A2...A64 + // chain C: G->A1->A2...A64->C65->C66...C74 + chainC := makeHeaderChain(chainA[63], 10, ethash.NewFaker(), db, 10) + + // Inserting 10 headers on the canonical chain + // expecting 0 write status with no error + testInsert(t, hc, chainC, CanonStatTy, nil, mockForker) +} + +func TestOverlappingChainInsert(t *testing.T) { + t.Parallel() + + var ( + db = rawdb.NewMemoryDatabase() + genesis = (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db) + ) + + hc, err := NewHeaderChain(db, params.AllEthashProtocolChanges, ethash.NewFaker(), func() bool { return false }) + if err != nil { + t.Fatal(err) + } + + // Create mocks for forker + getTd := func(hash common.Hash, number uint64) *big.Int { + return big.NewInt(int64(number)) + } + validate := func(currentHeader *types.Header, chain []*types.Header) bool { + // Put all explicit conditions here + // If canonical chain is empty and we're importing a chain of 64 blocks + if currentHeader.Number.Uint64() == uint64(0) && len(chain) == 64 { + return true + } + // If length of chain is > some fixed value then don't accept it + if currentHeader.Number.Uint64() == uint64(64) && len(chain) <= 20 { + return true + } + + return false + } + mockChainReader := newChainReaderFake(getTd) + mockChainValidator := newChainValidatorFake(validate) + mockForker := NewForkChoice(mockChainReader, nil, mockChainValidator) + + // chain A: G->A1->A2...A64 + chainA := makeHeaderChain(genesis.Header(), 64, ethash.NewFaker(), db, 10) + + // Inserting 64 headers on an empty chain + // expecting 1 write status with no error + testInsert(t, hc, chainA, CanonStatTy, nil, mockForker) + + // The current chain is: G->A1->A2...A64 + // chain B: G->A1->A2...A54->B55->B56...B84 + chainB := makeHeaderChain(chainA[53], 30, ethash.NewFaker(), db, 10) + + // Inserting 20 blocks on canonical chain + // expecting 2 write status with no error + testInsert(t, hc, chainB, SideStatTy, nil, mockForker) + + // The current chain is: G->A1->A2...A64 + // chain C: G->A1->A2...A54->C55->C56...C74 + chainC := makeHeaderChain(chainA[53], 20, ethash.NewFaker(), db, 10) + + // Inserting 10 blocks on canonical chain + // expecting 1 write status with no error + testInsert(t, hc, chainC, CanonStatTy, nil, mockForker) +} + +// Mock chain reader functions +func (c *chainReaderFake) Config() *params.ChainConfig { + return ¶ms.ChainConfig{TerminalTotalDifficulty: nil} +} +func (c *chainReaderFake) GetTd(hash common.Hash, number uint64) *big.Int { + return c.getTd(hash, number) +} + +// Mock chain validator functions +func (w *chainValidatorFake) IsValidPeer(remoteHeader *types.Header, fetchHeadersByNumber func(number uint64, amount int, skip int, reverse bool) ([]*types.Header, []common.Hash, error)) (bool, error) { + return true, nil +} +func (w *chainValidatorFake) IsValidChain(current *types.Header, headers []*types.Header) bool { + return w.validate(current, headers) +} +func (w *chainValidatorFake) ProcessCheckpoint(endBlockNum uint64, endBlockHash common.Hash) {} +func (w *chainValidatorFake) GetCheckpointWhitelist() map[uint64]common.Hash { + return nil +} +func (w *chainValidatorFake) PurgeCheckpointWhitelist() {} +func (w *chainValidatorFake) GetCheckpoints(current, sidechainHeader *types.Header, sidechainCheckpoints []*types.Header) (map[uint64]*types.Header, error) { + return map[uint64]*types.Header{}, nil +} diff --git a/core/genesis_test.go b/core/genesis_test.go index e8010e3d4e..3d3480bff8 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -117,7 +117,7 @@ func TestSetupGenesis(t *testing.T) { // Advance to block #4, past the homestead transition block of customg. genesis := oldcustomg.MustCommit(db) - bc, _ := NewBlockChain(db, nil, oldcustomg.Config, ethash.NewFullFaker(), vm.Config{}, nil, nil) + bc, _ := NewBlockChain(db, nil, oldcustomg.Config, ethash.NewFullFaker(), vm.Config{}, nil, nil, nil) defer bc.Stop() blocks, _ := GenerateChain(oldcustomg.Config, genesis, ethash.NewFaker(), db, 4, nil) diff --git a/core/headerchain.go b/core/headerchain.go index 99364f638f..83e1125305 100644 --- a/core/headerchain.go +++ b/core/headerchain.go @@ -283,8 +283,10 @@ func (hc *HeaderChain) writeHeadersAndSetHead(headers []*types.Header, forker *F lastHeader: lastHeader, } ) + // Ask the fork choicer if the reorg is necessary - if reorg, err := forker.ReorgNeeded(hc.CurrentHeader(), lastHeader); err != nil { + reorg, err := forker.ReorgNeeded(hc.CurrentHeader(), lastHeader) + if err != nil { return nil, err } else if !reorg { if inserted != 0 { @@ -292,6 +294,16 @@ func (hc *HeaderChain) writeHeadersAndSetHead(headers []*types.Header, forker *F } return result, nil } + + isValid, err := forker.ValidateReorg(hc.CurrentHeader(), headers) + if err != nil { + return nil, err + } else if !isValid { + if inserted != 0 { + result.status = SideStatTy + } + return result, nil + } // Special case, all the inserted headers are already on the canonical // header chain, skip the reorg operation. if hc.GetCanonicalHash(lastHeader.Number.Uint64()) == lastHash && lastHeader.Number.Uint64() <= hc.CurrentHeader().Number.Uint64() { diff --git a/core/headerchain_test.go b/core/headerchain_test.go index ed0522671f..85432876fa 100644 --- a/core/headerchain_test.go +++ b/core/headerchain_test.go @@ -84,7 +84,7 @@ func TestHeaderInsertion(t *testing.T) { chainB := makeHeaderChain(chainA[0], 128, ethash.NewFaker(), db, 10) log.Root().SetHandler(log.StdoutHandler) - forker := NewForkChoice(hc, nil) + forker := NewForkChoice(hc, nil, nil) // Inserting 64 headers on an empty chain, expecting // 1 callbacks, 1 canon-status, 0 sidestatus, testInsert(t, hc, chainA[:64], CanonStatTy, nil, forker) diff --git a/core/rawdb/bor_receipt.go b/core/rawdb/bor_receipt.go index 63c4be12a1..e225083741 100644 --- a/core/rawdb/bor_receipt.go +++ b/core/rawdb/bor_receipt.go @@ -18,7 +18,11 @@ var ( getDerivedBorTxHash = types.GetDerivedBorTxHash // borTxLookupPrefix + hash -> transaction/receipt lookup metadata - borTxLookupPrefix = []byte("matic-bor-tx-lookup-") + borTxLookupPrefix = []byte(borTxLookupPrefixStr) +) + +const ( + borTxLookupPrefixStr = "matic-bor-tx-lookup-" // freezerBorReceiptTable indicates the name of the freezer bor receipts table. freezerBorReceiptTable = "matic-bor-receipts" diff --git a/core/state/statedb.go b/core/state/statedb.go index 1d31cf470b..c236a79b5a 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -601,8 +601,8 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) // CreateAccount is called during the EVM CREATE operation. The situation might arise that // a contract does the following: // -// 1. sends funds to sha(account ++ (nonce + 1)) -// 2. tx_create(sha(account ++ nonce)) (note that this gets the address of 1) +// 1. sends funds to sha(account ++ (nonce + 1)) +// 2. tx_create(sha(account ++ nonce)) (note that this gets the address of 1) // // Carrying over the balance ensures that Ether doesn't disappear. func (s *StateDB) CreateAccount(addr common.Address) { diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 158e8c1eeb..5dc076a11c 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -94,7 +94,7 @@ func TestStateProcessorErrors(t *testing.T) { }, } genesis = gspec.MustCommit(db) - blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil) ) defer blockchain.Stop() bigNumber := new(big.Int).SetBytes(common.FromHex("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) @@ -235,7 +235,7 @@ func TestStateProcessorErrors(t *testing.T) { }, } genesis = gspec.MustCommit(db) - blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil) ) defer blockchain.Stop() for i, tt := range []struct { @@ -275,7 +275,7 @@ func TestStateProcessorErrors(t *testing.T) { }, } genesis = gspec.MustCommit(db) - blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil) ) defer blockchain.Stop() for i, tt := range []struct { diff --git a/core/tests/blockchain_repair_test.go b/core/tests/blockchain_repair_test.go index 15ab67dd1c..d99978a134 100644 --- a/core/tests/blockchain_repair_test.go +++ b/core/tests/blockchain_repair_test.go @@ -1891,7 +1891,7 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) { defer db.Close() - newChain, err := core.NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) + newChain, err := core.NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -1969,7 +1969,7 @@ func TestIssue23496(t *testing.T) { } ) - chain, err := core.NewBlockChain(db, config, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) + chain, err := core.NewBlockChain(db, config, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("Failed to create chain: %v", err) } @@ -2024,7 +2024,7 @@ func TestIssue23496(t *testing.T) { defer db.Close() - chain, err = core.NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) + chain, err = core.NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } diff --git a/core/tests/blockchain_sethead_test.go b/core/tests/blockchain_sethead_test.go index 3a2d87c4eb..ad6e78697d 100644 --- a/core/tests/blockchain_sethead_test.go +++ b/core/tests/blockchain_sethead_test.go @@ -1987,7 +1987,7 @@ func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) { config.SnapshotWait = true } - chain, err := core.NewBlockChain(db, config, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) + chain, err := core.NewBlockChain(db, config, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("Failed to create chain: %v", err) } diff --git a/core/tests/blockchain_snapshot_test.go b/core/tests/blockchain_snapshot_test.go index fb4c09867c..26ef58f99f 100644 --- a/core/tests/blockchain_snapshot_test.go +++ b/core/tests/blockchain_snapshot_test.go @@ -84,7 +84,7 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*core.BlockChain, []*type cacheConfig = core.DefaultCacheConfig ) - chain, err := core.NewBlockChain(db, cacheConfig, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) + chain, err := core.NewBlockChain(db, cacheConfig, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("Failed to create chain: %v", err) } @@ -246,7 +246,7 @@ func (snaptest *snapshotTest) test(t *testing.T) { // Restart the chain normally chain.Stop() - newchain, err := core.NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + newchain, err := core.NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -283,13 +283,13 @@ func (snaptest *crashSnapshotTest) test(t *testing.T) { // the crash, we do restart twice here: one after the crash and one // after the normal stop. It's used to ensure the broken snapshot // can be detected all the time. - newchain, err := core.NewBlockChain(newdb, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + newchain, err := core.NewBlockChain(newdb, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } newchain.Stop() - newchain, err = core.NewBlockChain(newdb, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + newchain, err = core.NewBlockChain(newdb, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -327,7 +327,7 @@ func (snaptest *gappedSnapshotTest) test(t *testing.T) { SnapshotLimit: 0, } - newchain, err := core.NewBlockChain(snaptest.db, cacheConfig, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + newchain, err := core.NewBlockChain(snaptest.db, cacheConfig, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -336,7 +336,7 @@ func (snaptest *gappedSnapshotTest) test(t *testing.T) { newchain.Stop() // Restart the chain with enabling the snapshot - newchain, err = core.NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + newchain, err = core.NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -365,7 +365,7 @@ func (snaptest *setHeadSnapshotTest) test(t *testing.T) { chain.SetHead(snaptest.setHead) chain.Stop() - newchain, err := core.NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + newchain, err := core.NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -396,7 +396,7 @@ func (snaptest *restartCrashSnapshotTest) test(t *testing.T) { // and state committed. chain.Stop() - newchain, err := core.NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + newchain, err := core.NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -414,7 +414,7 @@ func (snaptest *restartCrashSnapshotTest) test(t *testing.T) { // journal and latest state will be committed // Restart the chain after the crash - newchain, err = core.NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + newchain, err = core.NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -449,8 +449,7 @@ func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) { TrieTimeLimit: 5 * time.Minute, SnapshotLimit: 0, } - - newchain, err := core.NewBlockChain(snaptest.db, config, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + newchain, err := core.NewBlockChain(snaptest.db, config, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -467,14 +466,13 @@ func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) { SnapshotLimit: 256, SnapshotWait: false, // Don't wait rebuild } - - _, err = core.NewBlockChain(snaptest.db, config, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + _, err = core.NewBlockChain(snaptest.db, config, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } // Simulate the blockchain crash. - newchain, err = core.NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + newchain, err = core.NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } diff --git a/docs/cli/server.md b/docs/cli/server.md index 0803208e3a..ac43555275 100644 --- a/docs/cli/server.md +++ b/docs/cli/server.md @@ -6,7 +6,7 @@ The ```bor server``` command runs the Bor client. - ```chain```: Name of the chain to sync -- ```name```: Name/Identity of the node +- ```identity```: Name/Identity of the node - ```log-level```: Set log level for the server @@ -22,7 +22,7 @@ The ```bor server``` command runs the Bor client. - ```requiredblocks```: Comma separated block number-to-hash mappings to enforce (=) -- ```no-snapshot```: Disables the snapshot-database mode (default = false) +- ```snapshot```: Disables/Enables the snapshot-database mode (default = true) - ```bor.heimdall```: URL of Heimdall service @@ -88,9 +88,17 @@ The ```bor server``` command runs the Bor client. - ```ipcpath```: Filename for IPC socket/pipe within the datadir (explicit paths escape it) -- ```jsonrpc.corsdomain```: Comma separated list of domains from which to accept cross origin requests (browser enforced) +- ```http.corsdomain```: Comma separated list of domains from which to accept cross origin requests (browser enforced) -- ```jsonrpc.vhosts```: Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard. +- ```http.vhosts```: Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard. + +- ```ws.corsdomain```: Comma separated list of domains from which to accept cross origin requests (browser enforced) + +- ```ws.vhosts```: Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard. + +- ```graphql.corsdomain```: Comma separated list of domains from which to accept cross origin requests (browser enforced) + +- ```graphql.vhosts```: Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard. - ```http```: Enable the HTTP-RPC server @@ -100,7 +108,7 @@ The ```bor server``` command runs the Bor client. - ```http.rpcprefix```: HTTP path path prefix on which JSON-RPC is served. Use '/' to serve on all paths. -- ```http.modules```: API's offered over the HTTP-RPC interface +- ```http.api```: API's offered over the HTTP-RPC interface - ```ws```: Enable the WS-RPC server @@ -110,7 +118,7 @@ The ```bor server``` command runs the Bor client. - ```ws.rpcprefix```: HTTP path prefix on which JSON-RPC is served. Use '/' to serve on all paths. -- ```ws.modules```: API's offered over the WS-RPC interface +- ```ws.api```: API's offered over the WS-RPC interface - ```graphql```: Enable GraphQL on the HTTP-RPC server. Note that GraphQL can only be started if an HTTP server is started as well. diff --git a/eth/backend.go b/eth/backend.go index e3376056a2..8f2ae1ea2c 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -41,6 +41,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/downloader/whitelist" "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/eth/gasprice" @@ -219,7 +220,10 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { Preimages: config.Preimages, } ) - eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, chainConfig, eth.engine, vmConfig, eth.shouldPreserve, &config.TxLookupLimit) + + checker := whitelist.NewService(10) + + eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, chainConfig, eth.engine, vmConfig, eth.shouldPreserve, &config.TxLookupLimit, checker) if err != nil { return nil, err } @@ -260,6 +264,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { Checkpoint: checkpoint, EthAPI: ethAPI, PeerRequiredBlocks: config.PeerRequiredBlocks, + checker: checker, }); err != nil { return nil, err } @@ -622,6 +627,13 @@ func (s *Ethereum) Start() error { return nil } +var ( + ErrNotBorConsensus = errors.New("not bor consensus was given") + ErrBorConsensusWithoutHeimdall = errors.New("bor consensus without heimdall") + + whitelistTimeout = 30 * time.Second +) + // StartCheckpointWhitelistService starts the goroutine to fetch checkpoints and update the // checkpoint whitelist map. func (s *Ethereum) startCheckpointWhitelistService() { @@ -633,8 +645,11 @@ func (s *Ethereum) startCheckpointWhitelistService() { } // first run the checkpoint whitelist - // TODO: add context timeout if needed - err := s.handleWhitelistCheckpoint(context.Background()) + firstCtx, cancel := context.WithTimeout(context.Background(), whitelistTimeout) + err := s.handleWhitelistCheckpoint(firstCtx, true) + + cancel() + if err != nil { if errors.Is(err, ErrBorConsensusWithoutHeimdall) || errors.Is(err, ErrNotBorConsensus) { return @@ -649,8 +664,11 @@ func (s *Ethereum) startCheckpointWhitelistService() { for { select { case <-ticker.C: - // TODO: add context timeout if needed - err = s.handleWhitelistCheckpoint(context.Background()) + ctx, cancel := context.WithTimeout(context.Background(), whitelistTimeout) + err := s.handleWhitelistCheckpoint(ctx, false) + + cancel() + if err != nil { log.Warn("unable to whitelist checkpoint", "err", err) } @@ -660,13 +678,8 @@ func (s *Ethereum) startCheckpointWhitelistService() { } } -var ( - ErrNotBorConsensus = errors.New("not bor consensus was given") - ErrBorConsensusWithoutHeimdall = errors.New("bor consensus without heimdall") -) - // handleWhitelistCheckpoint handles the checkpoint whitelist mechanism. -func (s *Ethereum) handleWhitelistCheckpoint(ctx context.Context) error { +func (s *Ethereum) handleWhitelistCheckpoint(ctx context.Context, first bool) error { ethHandler := (*ethHandler)(s.handler) bor, ok := ethHandler.chain.Engine().(*bor.Bor) @@ -678,13 +691,18 @@ func (s *Ethereum) handleWhitelistCheckpoint(ctx context.Context) error { return ErrBorConsensusWithoutHeimdall } - endBlockNum, endBlockHash, err := ethHandler.fetchWhitelistCheckpoint(ctx, bor) - if err != nil { + blockNums, blockHashes, err := ethHandler.fetchWhitelistCheckpoints(ctx, bor, first) + // If the array is empty, we're bound to receive an error. Non-nill error and non-empty array + // means that array has partial elements and it failed for some block. We'll add those partial + // elements anyway. + if len(blockNums) == 0 { return err } // Update the checkpoint whitelist map. - ethHandler.downloader.ProcessCheckpoint(endBlockNum, endBlockHash) + for i := 0; i < len(blockNums); i++ { + ethHandler.downloader.ProcessCheckpoint(blockNums[i], blockHashes[i]) + } return nil } diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 1a9d815ccd..34b8c95715 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -144,7 +144,7 @@ type Downloader struct { quitCh chan struct{} // Quit channel to signal termination quitLock sync.Mutex // Lock to prevent double closes - ChainValidator + ethereum.ChainValidator // Testing hooks syncInitHook func(uint64, uint64) // Method to call upon initiating a new sync run @@ -153,14 +153,6 @@ type Downloader struct { chainInsertHook func([]*fetchResult) // Method to call upon inserting a chain of blocks (possibly in multiple invocations) } -// interface for whitelist service -type ChainValidator interface { - IsValidChain(remoteHeader *types.Header, fetchHeadersByNumber func(number uint64, amount int, skip int, reverse bool) ([]*types.Header, []common.Hash, error)) (bool, error) - ProcessCheckpoint(endBlockNum uint64, endBlockHash common.Hash) - GetCheckpointWhitelist() map[uint64]common.Hash - PurgeCheckpointWhitelist() -} - // LightChain encapsulates functions required to synchronise a light chain. type LightChain interface { // HasHeader verifies a header's presence in the local chain. @@ -216,7 +208,7 @@ type BlockChain interface { // New creates a new downloader to fetch hashes and blocks from remote peers. //nolint: staticcheck -func New(checkpoint uint64, stateDb ethdb.Database, mux *event.TypeMux, chain BlockChain, lightchain LightChain, dropPeer peerDropFn, success func(), whitelistService ChainValidator) *Downloader { +func New(checkpoint uint64, stateDb ethdb.Database, mux *event.TypeMux, chain BlockChain, lightchain LightChain, dropPeer peerDropFn, success func(), whitelistService ethereum.ChainValidator) *Downloader { if lightchain == nil { lightchain = chain } @@ -799,9 +791,11 @@ func (d *Downloader) getFetchHeadersByNumber(p *peerConnection) func(number uint // In the rare scenario when we ended up on a long reorganisation (i.e. none of // the head links match), we do a binary search to find the common ancestor. func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) (uint64, error) { - // Check the validity of chain to be downloaded - if _, err := d.IsValidChain(remoteHeader, d.getFetchHeadersByNumber(p)); err != nil { - return 0, err + // Check the validity of peer from which the chain is to be downloaded + if d.ChainValidator != nil { + if _, err := d.IsValidPeer(remoteHeader, d.getFetchHeadersByNumber(p)); err != nil { + return 0, err + } } // Figure out the valid ancestor range to prevent rewrite attacks diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index 37b07424dd..d8765ef077 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -71,7 +71,7 @@ func newTester() *downloadTester { core.GenesisBlockForTesting(db, testAddress, big.NewInt(1000000000000000)) - chain, err := core.NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil) + chain, err := core.NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil, nil) if err != nil { panic(err) } @@ -88,7 +88,7 @@ func newTester() *downloadTester { return tester } -func (dl *downloadTester) setWhitelist(w ChainValidator) { +func (dl *downloadTester) setWhitelist(w ethereum.ChainValidator) { dl.downloader.ChainValidator = w } @@ -1416,9 +1416,9 @@ func newWhitelistFake(validate func(count int) (bool, error)) *whitelistFake { return &whitelistFake{0, validate} } -// IsValidChain is the mock function which the downloader will use to validate the chain +// IsValidPeer is the mock function which the downloader will use to validate the chain // to be received from a peer. -func (w *whitelistFake) IsValidChain(_ *types.Header, _ func(number uint64, amount int, skip int, reverse bool) ([]*types.Header, []common.Hash, error)) (bool, error) { +func (w *whitelistFake) IsValidPeer(_ *types.Header, _ func(number uint64, amount int, skip int, reverse bool) ([]*types.Header, []common.Hash, error)) (bool, error) { defer func() { w.count++ }() @@ -1426,13 +1426,18 @@ func (w *whitelistFake) IsValidChain(_ *types.Header, _ func(number uint64, amou return w.validate(w.count) } +func (w *whitelistFake) IsValidChain(current *types.Header, headers []*types.Header) bool { + return true +} func (w *whitelistFake) ProcessCheckpoint(_ uint64, _ common.Hash) {} func (w *whitelistFake) GetCheckpointWhitelist() map[uint64]common.Hash { return nil } - func (w *whitelistFake) PurgeCheckpointWhitelist() {} +func (w *whitelistFake) GetCheckpoints(current, sidechainHeader *types.Header, sidechainCheckpoints []*types.Header) (map[uint64]*types.Header, error) { + return map[uint64]*types.Header{}, nil +} // TestFakedSyncProgress66WhitelistMismatch tests if in case of whitelisted // checkpoint mismatch with opposite peer, the sync should fail. diff --git a/eth/downloader/testchain_test.go b/eth/downloader/testchain_test.go index 8b873343ca..cd4d9e6550 100644 --- a/eth/downloader/testchain_test.go +++ b/eth/downloader/testchain_test.go @@ -214,7 +214,7 @@ func newTestBlockchain(blocks []*types.Block) *core.BlockChain { db := rawdb.NewMemoryDatabase() core.GenesisBlockForTesting(db, testAddress, big.NewInt(1000000000000000)) - chain, err := core.NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil) + chain, err := core.NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil, nil) if err != nil { panic(err) } diff --git a/eth/downloader/whitelist/service.go b/eth/downloader/whitelist/service.go index 7036f24a8f..0e905cce28 100644 --- a/eth/downloader/whitelist/service.go +++ b/eth/downloader/whitelist/service.go @@ -15,7 +15,8 @@ type Service struct { m sync.Mutex checkpointWhitelist map[uint64]common.Hash // Checkpoint whitelist, populated by reaching out to heimdall checkpointOrder []uint64 // Checkpoint order, populated by reaching out to heimdall - maxCapacity uint + maxCapacity uint // Max capacity of the whitelist + checkpointInterval uint64 // Checkpoint interval, until which we can allow importing } func NewService(maxCapacity uint) *Service { @@ -23,6 +24,7 @@ func NewService(maxCapacity uint) *Service { checkpointWhitelist: make(map[uint64]common.Hash), checkpointOrder: []uint64{}, maxCapacity: maxCapacity, + checkpointInterval: 256, // TODO: make it configurable through params? } } @@ -31,9 +33,9 @@ var ( ErrNoRemoteCheckoint = errors.New("remote peer doesn't have a checkoint") ) -// IsValidChain checks if the chain we're about to receive from this peer is valid or not +// IsValidPeer checks if the chain we're about to receive from a peer is valid or not // in terms of reorgs. We won't reorg beyond the last bor checkpoint submitted to mainchain. -func (w *Service) IsValidChain(remoteHeader *types.Header, fetchHeadersByNumber func(number uint64, amount int, skip int, reverse bool) ([]*types.Header, []common.Hash, error)) (bool, error) { +func (w *Service) IsValidPeer(remoteHeader *types.Header, fetchHeadersByNumber func(number uint64, amount int, skip int, reverse bool) ([]*types.Header, []common.Hash, error)) (bool, error) { // We want to validate the chain by comparing the last checkpointed block // we're storing in `checkpointWhitelist` with the peer's block. // @@ -70,6 +72,84 @@ func (w *Service) IsValidChain(remoteHeader *types.Header, fetchHeadersByNumber return false, ErrCheckpointMismatch } +// IsValidChain checks the validity of chain by comparing it +// against the local checkpoint entries +func (w *Service) IsValidChain(currentHeader *types.Header, chain []*types.Header) bool { + // Check if we have checkpoints to validate incoming chain in memory + if len(w.checkpointWhitelist) == 0 { + // We don't have any entries, no additional validation will be possible + return true + } + + // Return if we've received empty chain + if len(chain) == 0 { + return false + } + + var ( + oldestCheckpointNumber uint64 = w.checkpointOrder[0] + current uint64 = currentHeader.Number.Uint64() + ) + + // Check if we have whitelist entries in required range + if chain[len(chain)-1].Number.Uint64() < oldestCheckpointNumber { + // We have future whitelisted entries, so no additional validation will be possible + // This case will occur when bor is in middle of sync, but heimdall is ahead/fully synced. + return true + } + + // Split the chain into past and future chain + pastChain, futureChain := splitChain(current, chain) + + // Add an offset to future chain if it's not in continuity + offset := 0 + if len(futureChain) != 0 { + offset += int(futureChain[0].Number.Uint64()-currentHeader.Number.Uint64()) - 1 + } + + // Don't accept future chain of unacceptable length (from current block) + if len(futureChain)+offset > int(w.checkpointInterval) { + return false + } + + // Iterate over the chain and validate against the last checkpoint + // It will handle all cases where the incoming chain has atleast one checkpoint + for i := len(pastChain) - 1; i >= 0; i-- { + if _, ok := w.checkpointWhitelist[pastChain[i].Number.Uint64()]; ok { + return pastChain[i].Hash() == w.checkpointWhitelist[pastChain[i].Number.Uint64()] + } + } + + return true +} + +func splitChain(current uint64, chain []*types.Header) ([]*types.Header, []*types.Header) { + var ( + pastChain []*types.Header + futureChain []*types.Header + first uint64 = chain[0].Number.Uint64() + last uint64 = chain[len(chain)-1].Number.Uint64() + ) + + if current >= first { + if len(chain) == 1 || current >= last { + pastChain = chain + } else { + pastChain = chain[:current-first+1] + } + } + + if current < last { + if len(chain) == 1 || current < first { + futureChain = chain + } else { + futureChain = chain[current-first+1:] + } + } + + return pastChain, futureChain +} + func (w *Service) ProcessCheckpoint(endBlockNum uint64, endBlockHash common.Hash) { w.m.Lock() defer w.m.Unlock() @@ -116,7 +196,7 @@ func (w *Service) dequeueCheckpointWhitelist() { log.Debug("Dequeing checkpoint whitelist", "block number", w.checkpointOrder[0], "block hash", w.checkpointWhitelist[w.checkpointOrder[0]]) delete(w.checkpointWhitelist, w.checkpointOrder[0]) - w.checkpointOrder = w.checkpointOrder[1:] + w.checkpointOrder = w.checkpointOrder[1:] // fixme: this slice is growing infinitely and never will be released. also a panic is possible if the last element is going to be removed } } diff --git a/eth/downloader/whitelist/service_test.go b/eth/downloader/whitelist/service_test.go index ca202cc3ad..c21490d125 100644 --- a/eth/downloader/whitelist/service_test.go +++ b/eth/downloader/whitelist/service_test.go @@ -2,21 +2,26 @@ package whitelist import ( "errors" + "fmt" "math/big" + "reflect" + "sort" "testing" + "time" - "gotest.tools/assert" + "github.com/stretchr/testify/require" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" ) // NewMockService creates a new mock whitelist service -func NewMockService(maxCapacity uint) *Service { +func NewMockService(maxCapacity uint, checkpointInterval uint64) *Service { return &Service{ checkpointWhitelist: make(map[uint64]common.Hash), checkpointOrder: []uint64{}, maxCapacity: maxCapacity, + checkpointInterval: checkpointInterval, } } @@ -24,34 +29,34 @@ func NewMockService(maxCapacity uint) *Service { func TestWhitelistCheckpoint(t *testing.T) { t.Parallel() - s := NewMockService(10) + s := NewMockService(10, 10) for i := 0; i < 10; i++ { s.enqueueCheckpointWhitelist(uint64(i), common.Hash{}) } - assert.Equal(t, s.length(), 10, "expected 10 items in whitelist") + require.Equal(t, s.length(), 10, "expected 10 items in whitelist") s.enqueueCheckpointWhitelist(11, common.Hash{}) s.dequeueCheckpointWhitelist() - assert.Equal(t, s.length(), 10, "expected 10 items in whitelist") + require.Equal(t, s.length(), 10, "expected 10 items in whitelist") } -// TestIsValidChain checks che IsValidChain function in isolation +// TestIsValidPeer checks the IsValidPeer function in isolation // for different cases by providing a mock fetchHeadersByNumber function -func TestIsValidChain(t *testing.T) { +func TestIsValidPeer(t *testing.T) { t.Parallel() - s := NewMockService(10) + s := NewMockService(10, 10) // case1: no checkpoint whitelist, should consider the chain as valid - res, err := s.IsValidChain(nil, nil) - assert.NilError(t, err, "expected no error") - assert.Equal(t, res, true, "expected chain to be valid") + res, err := s.IsValidPeer(nil, nil) + require.NoError(t, err, "expected no error") + require.Equal(t, res, true, "expected chain to be valid") // add checkpoint entries and mock fetchHeadersByNumber function s.ProcessCheckpoint(uint64(0), common.Hash{}) s.ProcessCheckpoint(uint64(1), common.Hash{}) - assert.Equal(t, s.length(), 2, "expected 2 items in whitelist") + require.Equal(t, s.length(), 2, "expected 2 items in whitelist") // create a false function, returning absolutely nothing falseFetchHeadersByNumber := func(number uint64, amount int, skip int, reverse bool) ([]*types.Header, []common.Hash, error) { @@ -60,7 +65,7 @@ func TestIsValidChain(t *testing.T) { // case2: false fetchHeadersByNumber function provided, should consider the chain as invalid // and throw `ErrNoRemoteCheckoint` error - res, err = s.IsValidChain(nil, falseFetchHeadersByNumber) + res, err = s.IsValidPeer(nil, falseFetchHeadersByNumber) if err == nil { t.Fatal("expected error, got nil") } @@ -69,7 +74,7 @@ func TestIsValidChain(t *testing.T) { t.Fatalf("expected error ErrNoRemoteCheckoint, got %v", err) } - assert.Equal(t, res, false, "expected chain to be invalid") + require.Equal(t, res, false, "expected chain to be invalid") // case3: correct fetchHeadersByNumber function provided, should consider the chain as valid // create a mock function, returning a the required header @@ -91,17 +96,316 @@ func TestIsValidChain(t *testing.T) { } } - res, err = s.IsValidChain(nil, fetchHeadersByNumber) - assert.NilError(t, err, "expected no error") - assert.Equal(t, res, true, "expected chain to be valid") + res, err = s.IsValidPeer(nil, fetchHeadersByNumber) + require.NoError(t, err, "expected no error") + require.Equal(t, res, true, "expected chain to be valid") // add one more checkpoint whitelist entry s.ProcessCheckpoint(uint64(2), common.Hash{}) - assert.Equal(t, s.length(), 3, "expected 3 items in whitelist") + require.Equal(t, s.length(), 3, "expected 3 items in whitelist") // case4: correct fetchHeadersByNumber function provided with wrong header // for block number 2. Should consider the chain as invalid and throw an error - res, err = s.IsValidChain(nil, fetchHeadersByNumber) - assert.Equal(t, err, ErrCheckpointMismatch, "expected checkpoint mismatch error") - assert.Equal(t, res, false, "expected chain to be invalid") + res, err = s.IsValidPeer(nil, fetchHeadersByNumber) + require.Equal(t, err, ErrCheckpointMismatch, "expected checkpoint mismatch error") + require.Equal(t, res, false, "expected chain to be invalid") +} + +// TestIsValidChain checks the IsValidChain function in isolation +// for different cases by providing a mock current header and chain +func TestIsValidChain(t *testing.T) { + t.Parallel() + + s := NewMockService(10, 10) + chainA := createMockChain(1, 20) // A1->A2...A19->A20 + // case1: no checkpoint whitelist, should consider the chain as valid + res := s.IsValidChain(nil, chainA) + require.Equal(t, res, true, "expected chain to be valid") + + tempChain := createMockChain(21, 22) // A21->A22 + + // add mock checkpoint entries + s.ProcessCheckpoint(tempChain[0].Number.Uint64(), tempChain[0].Hash()) + s.ProcessCheckpoint(tempChain[1].Number.Uint64(), tempChain[1].Hash()) + + require.Equal(t, s.length(), 2, "expected 2 items in whitelist") + + // case2: We're behind the oldest whitelisted block entry, should consider + // the chain as valid as we're still far behind the latest blocks + res = s.IsValidChain(chainA[len(chainA)-1], chainA) + require.Equal(t, res, true, "expected chain to be valid") + + // Clear checkpoint whitelist and add blocks A5 and A15 in whitelist + s.PurgeCheckpointWhitelist() + s.ProcessCheckpoint(chainA[5].Number.Uint64(), chainA[5].Hash()) + s.ProcessCheckpoint(chainA[15].Number.Uint64(), chainA[15].Hash()) + + require.Equal(t, s.length(), 2, "expected 2 items in whitelist") + + // case3: Try importing a past chain having valid checkpoint, should + // consider the chain as valid + res = s.IsValidChain(chainA[len(chainA)-1], chainA) + require.Equal(t, res, true, "expected chain to be valid") + + // Clear checkpoint whitelist and mock blocks in whitelist + tempChain = createMockChain(20, 20) // A20 + + s.PurgeCheckpointWhitelist() + s.ProcessCheckpoint(tempChain[0].Number.Uint64(), tempChain[0].Hash()) + + require.Equal(t, s.length(), 1, "expected 1 items in whitelist") + + // case4: Try importing a past chain having invalid checkpoint + res = s.IsValidChain(chainA[len(chainA)-1], chainA) + require.Equal(t, res, false, "expected chain to be invalid") + + // create a future chain to be imported of length <= `checkpointInterval` + chainB := createMockChain(21, 30) // B21->B22...B29->B30 + + // case5: Try importing a future chain of acceptable length + res = s.IsValidChain(chainA[len(chainA)-1], chainB) + require.Equal(t, res, true, "expected chain to be valid") + + // create a future chain to be imported of length > `checkpointInterval` + chainB = createMockChain(21, 40) // C21->C22...C39->C40 + + // case5: Try importing a future chain of unacceptable length + res = s.IsValidChain(chainA[len(chainA)-1], chainB) + require.Equal(t, res, false, "expected chain to be invalid") +} + +func TestSplitChain(t *testing.T) { + t.Parallel() + + type Result struct { + pastStart uint64 + pastEnd uint64 + futureStart uint64 + futureEnd uint64 + pastLength int + futureLength int + } + + // Current chain is at block: X + // Incoming chain is represented as [N, M] + testCases := []struct { + name string + current uint64 + chain []*types.Header + result Result + }{ + {name: "X = 10, N = 11, M = 20", current: uint64(10), chain: createMockChain(11, 20), result: Result{futureStart: 11, futureEnd: 20, futureLength: 10}}, + {name: "X = 10, N = 13, M = 20", current: uint64(10), chain: createMockChain(13, 20), result: Result{futureStart: 13, futureEnd: 20, futureLength: 8}}, + {name: "X = 10, N = 2, M = 10", current: uint64(10), chain: createMockChain(2, 10), result: Result{pastStart: 2, pastEnd: 10, pastLength: 9}}, + {name: "X = 10, N = 2, M = 9", current: uint64(10), chain: createMockChain(2, 9), result: Result{pastStart: 2, pastEnd: 9, pastLength: 8}}, + {name: "X = 10, N = 2, M = 8", current: uint64(10), chain: createMockChain(2, 8), result: Result{pastStart: 2, pastEnd: 8, pastLength: 7}}, + {name: "X = 10, N = 5, M = 15", current: uint64(10), chain: createMockChain(5, 15), result: Result{pastStart: 5, pastEnd: 10, pastLength: 6, futureStart: 11, futureEnd: 15, futureLength: 5}}, + {name: "X = 10, N = 10, M = 20", current: uint64(10), chain: createMockChain(10, 20), result: Result{pastStart: 10, pastEnd: 10, pastLength: 1, futureStart: 11, futureEnd: 20, futureLength: 10}}, + } + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + past, future := splitChain(tc.current, tc.chain) + require.Equal(t, len(past), tc.result.pastLength) + require.Equal(t, len(future), tc.result.futureLength) + + if len(past) > 0 { + // Check if we have expected block/s + require.Equal(t, past[0].Number.Uint64(), tc.result.pastStart) + require.Equal(t, past[len(past)-1].Number.Uint64(), tc.result.pastEnd) + } + + if len(future) > 0 { + // Check if we have expected block/s + require.Equal(t, future[0].Number.Uint64(), tc.result.futureStart) + require.Equal(t, future[len(future)-1].Number.Uint64(), tc.result.futureEnd) + } + }) + } +} + +//nolint:gocognit +func TestSplitChainProperties(t *testing.T) { + t.Parallel() + + // Current chain is at block: X + // Incoming chain is represented as [N, M] + + currentChain := []int{0, 1, 2, 3, 10, 100} // blocks starting from genesis + blockDiffs := []int{0, 1, 2, 3, 4, 5, 9, 10, 11, 12, 90, 100, 101, 102} + + caseParams := make(map[int]map[int]map[int]struct{}) // X -> N -> M + + for _, current := range currentChain { + // past cases only + past to current + for _, diff := range blockDiffs { + from := current - diff + + // use int type for everything to not care about underflow + if from < 0 { + continue + } + + for _, diff := range blockDiffs { + to := current - diff + + if to >= from { + addTestCaseParams(caseParams, current, from, to) + } + } + } + + // future only + current to future + for _, diff := range blockDiffs { + from := current + diff + + if from < 0 { + continue + } + + for _, diff := range blockDiffs { + to := current + diff + + if to >= from { + addTestCaseParams(caseParams, current, from, to) + } + } + } + + // past-current-future + for _, diff := range blockDiffs { + from := current - diff + + if from < 0 { + continue + } + + for _, diff := range blockDiffs { + to := current + diff + + if to >= from { + addTestCaseParams(caseParams, current, from, to) + } + } + } + } + + type testCase struct { + current int + remoteStart int + remoteEnd int + } + + var ts []testCase + + // X -> N -> M + for x, nm := range caseParams { + for n, mMap := range nm { + for m := range mMap { + ts = append(ts, testCase{x, n, m}) + } + } + } + + //nolint:paralleltest + for i, tc := range ts { + tc := tc + + name := fmt.Sprintf("test case: index = %d, X = %d, N = %d, M = %d", i, tc.current, tc.remoteStart, tc.remoteEnd) + + t.Run(name, func(t *testing.T) { + t.Parallel() + + chain := createMockChain(uint64(tc.remoteStart), uint64(tc.remoteEnd)) + + past, future := splitChain(uint64(tc.current), chain) + + // properties + if len(past) > 0 { + // Check if the chain is ordered + isOrdered := sort.SliceIsSorted(past, func(i, j int) bool { + return past[i].Number.Uint64() < past[j].Number.Uint64() + }) + + require.True(t, isOrdered, "an ordered past chain expected: %v", past) + + isSequential := sort.SliceIsSorted(past, func(i, j int) bool { + return past[i].Number.Uint64() == past[j].Number.Uint64()-1 + }) + + require.True(t, isSequential, "a sequential past chain expected: %v", past) + + // Check if current block >= past chain's last block + require.Equal(t, past[len(past)-1].Number.Uint64() <= uint64(tc.current), true) + } + + if len(future) > 0 { + // Check if the chain is ordered + isOrdered := sort.SliceIsSorted(future, func(i, j int) bool { + return future[i].Number.Uint64() < future[j].Number.Uint64() + }) + + require.True(t, isOrdered, "an ordered future chain expected: %v", future) + + isSequential := sort.SliceIsSorted(future, func(i, j int) bool { + return future[i].Number.Uint64() == future[j].Number.Uint64()-1 + }) + + require.True(t, isSequential, "a sequential future chain expected: %v", future) + + // Check if future chain's first block > current block + require.Equal(t, future[len(future)-1].Number.Uint64() > uint64(tc.current), true) + } + + // Check if both chains are continuous + if len(past) > 0 && len(future) > 0 { + require.Equal(t, past[len(past)-1].Number.Uint64(), future[0].Number.Uint64()-1) + } + + // Check if we get the original chain on appending both + gotChain := append(past, future...) + require.Equal(t, reflect.DeepEqual(gotChain, chain), true) + }) + } +} + +// createMockChain returns a chain with dummy headers +// starting from `start` to `end` (inclusive) +func createMockChain(start, end uint64) []*types.Header { + var ( + i uint64 + idx uint64 + chain []*types.Header = make([]*types.Header, end-start+1) + ) + + for i = start; i <= end; i++ { + header := &types.Header{ + Number: big.NewInt(int64(i)), + Time: uint64(time.Now().UnixMicro()) + i, + } + chain[idx] = header + idx++ + } + + return chain +} + +// mXNM should be initialized +func addTestCaseParams(mXNM map[int]map[int]map[int]struct{}, x, n, m int) { + //nolint:ineffassign + mNM, ok := mXNM[x] + if !ok { + mNM = make(map[int]map[int]struct{}) + mXNM[x] = mNM + } + + //nolint:ineffassign + _, ok = mNM[n] + if !ok { + mM := make(map[int]struct{}) + mNM[n] = mM + } + + mXNM[x][n][m] = struct{}{} } diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 091600d8b5..d51e293413 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -30,7 +30,7 @@ import ( "github.com/ethereum/go-ethereum/consensus/beacon" "github.com/ethereum/go-ethereum/consensus/bor" "github.com/ethereum/go-ethereum/consensus/bor/contract" - "github.com/ethereum/go-ethereum/consensus/bor/heimdall" + "github.com/ethereum/go-ethereum/consensus/bor/heimdall" //nolint:typecheck "github.com/ethereum/go-ethereum/consensus/bor/heimdall/span" "github.com/ethereum/go-ethereum/consensus/clique" "github.com/ethereum/go-ethereum/consensus/ethash" diff --git a/eth/filters/bor_api.go b/eth/filters/bor_api.go index 517ff894f4..d6f0aea45a 100644 --- a/eth/filters/bor_api.go +++ b/eth/filters/bor_api.go @@ -59,8 +59,9 @@ func (api *PublicFilterAPI) NewDeposits(ctx context.Context, crit ethereum.State } rpcSub := notifier.CreateSubscription() + go func() { - stateSyncData := make(chan *types.StateSyncData) + stateSyncData := make(chan *types.StateSyncData, 10) stateSyncSub := api.events.SubscribeNewDeposits(stateSyncData) for { diff --git a/eth/gasprice/gasprice_test.go b/eth/gasprice/gasprice_test.go index c0d3c6b603..134168b885 100644 --- a/eth/gasprice/gasprice_test.go +++ b/eth/gasprice/gasprice_test.go @@ -144,7 +144,7 @@ func newTestBackend(t *testing.T, londonBlock *big.Int, pending bool) *testBacke // Construct testing chain diskdb := rawdb.NewMemoryDatabase() gspec.Commit(diskdb) - chain, err := core.NewBlockChain(diskdb, &core.CacheConfig{TrieCleanNoPrefetch: true}, gspec.Config, engine, vm.Config{}, nil, nil) + chain, err := core.NewBlockChain(diskdb, &core.CacheConfig{TrieCleanNoPrefetch: true}, gspec.Config, engine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("Failed to create local chain, %v", err) } diff --git a/eth/handler.go b/eth/handler.go index ab95f5f769..a9c4f4eb1f 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -24,6 +24,7 @@ import ( "sync/atomic" "time" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/beacon" @@ -31,7 +32,6 @@ import ( "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/downloader" - "github.com/ethereum/go-ethereum/eth/downloader/whitelist" "github.com/ethereum/go-ethereum/eth/fetcher" "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/eth/protocols/snap" @@ -91,6 +91,7 @@ type handlerConfig struct { EthAPI *ethapi.PublicBlockChainAPI // EthAPI to interact PeerRequiredBlocks map[uint64]common.Hash // Hard coded map of required block hashes for sync challenges + checker ethereum.ChainValidator } type handler struct { @@ -202,7 +203,7 @@ func newHandler(config *handlerConfig) (*handler, error) { // sync is requested. The downloader is responsible for deallocating the state // bloom when it's done. // todo: it'd better to extract maxCapacity into config - h.downloader = downloader.New(h.checkpointNumber, config.Database, h.eventMux, h.chain, nil, h.removePeer, success, whitelist.NewService(10)) + h.downloader = downloader.New(h.checkpointNumber, config.Database, h.eventMux, h.chain, nil, h.removePeer, success, config.checker) // Construct the fetcher (short sync) validator := func(header *types.Header) error { diff --git a/eth/handler_bor.go b/eth/handler_bor.go index eca4b0c7eb..35d6e00bfc 100644 --- a/eth/handler_bor.go +++ b/eth/handler_bor.go @@ -13,6 +13,14 @@ import ( ) var ( + // errCheckpointCount is returned when we are unable to fetch + // the checkpoint count from local heimdall. + errCheckpointCount = errors.New("failed to fetch checkpoint count") + + // errNoCheckpoint is returned when there is not checkpoint proposed + // by heimdall yet or heimdall is not in sync + errNoCheckpoint = errors.New("no checkpoint proposed") + // errCheckpoint is returned when we are unable to fetch the // latest checkpoint from the local heimdall. errCheckpoint = errors.New("failed to fetch latest checkpoint") @@ -33,43 +41,78 @@ var ( errEndBlock = errors.New("failed to get end block") ) -// fetchWhitelistCheckpoint fetched the latest checkpoint from it's local heimdall +// fetchWhitelistCheckpoints fetches the latest checkpoint/s from it's local heimdall // and verifies the data against bor data. -func (h *ethHandler) fetchWhitelistCheckpoint(ctx context.Context, bor *bor.Bor) (uint64, common.Hash, error) { - // check for checkpoint whitelisting: bor - checkpoint, err := bor.HeimdallClient.FetchLatestCheckpoint(ctx) +func (h *ethHandler) fetchWhitelistCheckpoints(ctx context.Context, bor *bor.Bor, first bool) ([]uint64, []common.Hash, error) { + // Create an array for block number and block hashes + //nolint:prealloc + var ( + blockNums []uint64 = make([]uint64, 0) + blockHashes []common.Hash = make([]common.Hash, 0) + ) + + // Fetch the checkpoint count from heimdall + count, err := bor.HeimdallClient.FetchCheckpointCount(ctx) if err != nil { - log.Debug("Failed to fetch latest checkpoint for whitelisting") - return 0, common.Hash{}, errCheckpoint + log.Debug("Failed to fetch checkpoint count for whitelisting", "err", err) + return blockNums, blockHashes, errCheckpointCount } - // check if we have the checkpoint blocks - head := h.ethAPI.BlockNumber() - if head < hexutil.Uint64(checkpoint.EndBlock.Uint64()) { - log.Debug("Head block behind checkpoint block", "head", head, "checkpoint end block", checkpoint.EndBlock) - return 0, common.Hash{}, errMissingCheckpoint + if count == 0 { + return blockNums, blockHashes, errNoCheckpoint } - // verify the root hash of checkpoint - roothash, err := h.ethAPI.GetRootHash(ctx, checkpoint.StartBlock.Uint64(), checkpoint.EndBlock.Uint64()) - if err != nil { - log.Debug("Failed to get root hash of checkpoint while whitelisting") - return 0, common.Hash{}, errRootHash + // If we're in the first iteration, we'll fetch last 10 checkpoints, else only the latest one + iterations := 1 + if first { + iterations = 10 } - if roothash != checkpoint.RootHash.String()[2:] { - log.Warn("Checkpoint root hash mismatch while whitelisting", "expected", checkpoint.RootHash.String()[2:], "got", roothash) - return 0, common.Hash{}, errCheckpointRootHashMismatch + for i := 0; i < iterations; i++ { + // If we don't have any checkpoints in heimdall, break + if count == 0 { + break + } + + // fetch `count` indexed checkpoint from heimdall + checkpoint, err := bor.HeimdallClient.FetchCheckpoint(ctx, count) + if err != nil { + log.Debug("Failed to fetch latest checkpoint for whitelisting", "err", err) + return blockNums, blockHashes, errCheckpoint + } + + // check if we have the checkpoint blocks + head := h.ethAPI.BlockNumber() + if head < hexutil.Uint64(checkpoint.EndBlock.Uint64()) { + log.Debug("Head block behind checkpoint block", "head", head, "checkpoint end block", checkpoint.EndBlock) + return blockNums, blockHashes, errMissingCheckpoint + } + + // verify the root hash of checkpoint + roothash, err := h.ethAPI.GetRootHash(ctx, checkpoint.StartBlock.Uint64(), checkpoint.EndBlock.Uint64()) + if err != nil { + log.Debug("Failed to get root hash of checkpoint while whitelisting", "err", err) + return blockNums, blockHashes, errRootHash + } + + if roothash != checkpoint.RootHash.String()[2:] { + log.Warn("Checkpoint root hash mismatch while whitelisting", "expected", checkpoint.RootHash.String()[2:], "got", roothash) + return blockNums, blockHashes, errCheckpointRootHashMismatch + } + + // fetch the end checkpoint block hash + block, err := h.ethAPI.GetBlockByNumber(ctx, rpc.BlockNumber(checkpoint.EndBlock.Uint64()), false) + if err != nil { + log.Debug("Failed to get end block hash of checkpoint while whitelisting", "err", err) + return blockNums, blockHashes, errEndBlock + } + + hash := fmt.Sprintf("%v", block["hash"]) + + blockNums = append(blockNums, checkpoint.EndBlock.Uint64()) + blockHashes = append(blockHashes, common.HexToHash(hash)) + count-- } - // fetch the end checkpoint block hash - block, err := h.ethAPI.GetBlockByNumber(context.Background(), rpc.BlockNumber(checkpoint.EndBlock.Uint64()), false) - if err != nil { - log.Debug("Failed to get end block hash of checkpoint while whitelisting") - return 0, common.Hash{}, errEndBlock - } - - hash := fmt.Sprintf("%v", block["hash"]) - - return checkpoint.EndBlock.Uint64(), common.HexToHash(hash), nil + return blockNums, blockHashes, nil } diff --git a/eth/handler_eth_test.go b/eth/handler_eth_test.go index 7d5027ae77..6634b26871 100644 --- a/eth/handler_eth_test.go +++ b/eth/handler_eth_test.go @@ -105,8 +105,8 @@ func testForkIDSplit(t *testing.T, protocol uint) { genesisNoFork = gspecNoFork.MustCommit(dbNoFork) genesisProFork = gspecProFork.MustCommit(dbProFork) - chainNoFork, _ = core.NewBlockChain(dbNoFork, nil, configNoFork, engine, vm.Config{}, nil, nil) - chainProFork, _ = core.NewBlockChain(dbProFork, nil, configProFork, engine, vm.Config{}, nil, nil) + chainNoFork, _ = core.NewBlockChain(dbNoFork, nil, configNoFork, engine, vm.Config{}, nil, nil, nil) + chainProFork, _ = core.NewBlockChain(dbProFork, nil, configProFork, engine, vm.Config{}, nil, nil, nil) blocksNoFork, _ = core.GenerateChain(configNoFork, genesisNoFork, engine, dbNoFork, 2, nil) blocksProFork, _ = core.GenerateChain(configProFork, genesisProFork, engine, dbProFork, 2, nil) diff --git a/eth/handler_test.go b/eth/handler_test.go index d967b6df93..c6d7811d10 100644 --- a/eth/handler_test.go +++ b/eth/handler_test.go @@ -138,7 +138,7 @@ func newTestHandlerWithBlocks(blocks int) *testHandler { Alloc: core.GenesisAlloc{testAddr: {Balance: big.NewInt(1000000)}}, }).MustCommit(db) - chain, _ := core.NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil) + chain, _ := core.NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil, nil) bs, _ := core.GenerateChain(params.TestChainConfig, chain.Genesis(), ethash.NewFaker(), db, blocks, nil) if _, err := chain.InsertChain(bs); err != nil { diff --git a/eth/protocols/eth/handler_test.go b/eth/protocols/eth/handler_test.go index 55e612b801..0bbe9f66a9 100644 --- a/eth/protocols/eth/handler_test.go +++ b/eth/protocols/eth/handler_test.go @@ -68,7 +68,7 @@ func newTestBackendWithGenerator(blocks int, generator func(int, *core.BlockGen) Alloc: core.GenesisAlloc{testAddr: {Balance: big.NewInt(100_000_000_000_000_000)}}, }).MustCommit(db) - chain, _ := core.NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil) + chain, _ := core.NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil, nil) bs, _ := core.GenerateChain(params.TestChainConfig, chain.Genesis(), ethash.NewFaker(), db, blocks, generator) if _, err := chain.InsertChain(bs); err != nil { diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index a3c0a72494..d2ed9c2179 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -81,7 +81,7 @@ func newTestBackend(t *testing.T, n int, gspec *core.Genesis, generator func(i i SnapshotLimit: 0, TrieDirtyDisabled: true, // Archive mode } - chain, err := core.NewBlockChain(backend.chaindb, cacheConfig, backend.chainConfig, backend.engine, vm.Config{}, nil, nil) + chain, err := core.NewBlockChain(backend.chaindb, cacheConfig, backend.chainConfig, backend.engine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } diff --git a/interfaces.go b/interfaces.go index 94a2492ae1..ff6d80b1ec 100644 --- a/interfaces.go +++ b/interfaces.go @@ -238,3 +238,12 @@ type StateSyncFilter struct { ID uint64 Contract common.Address } + +// interface for whitelist service +type ChainValidator interface { + IsValidPeer(remoteHeader *types.Header, fetchHeadersByNumber func(number uint64, amount int, skip int, reverse bool) ([]*types.Header, []common.Hash, error)) (bool, error) + IsValidChain(currentHeader *types.Header, chain []*types.Header) bool + ProcessCheckpoint(endBlockNum uint64, endBlockHash common.Hash) + GetCheckpointWhitelist() map[uint64]common.Hash + PurgeCheckpointWhitelist() +} diff --git a/internal/cli/bootnode.go b/internal/cli/bootnode.go index 9e1a0fcde9..d1dc1c2fd9 100644 --- a/internal/cli/bootnode.go +++ b/internal/cli/bootnode.go @@ -4,7 +4,6 @@ import ( "crypto/ecdsa" "errors" "fmt" - "io/ioutil" "net" "os" "os/signal" @@ -162,7 +161,7 @@ func (b *BootnodeCommand) Run(args []string) int { } // save the public key pubRaw := fmt.Sprintf("%x", crypto.FromECDSAPub(&nodeKey.PublicKey)[1:]) - if err := ioutil.WriteFile(filepath.Join(path, "pub.key"), []byte(pubRaw), 0600); err != nil { + if err := os.WriteFile(filepath.Join(path, "pub.key"), []byte(pubRaw), 0600); err != nil { b.UI.Error(fmt.Sprintf("failed to write node pub key: %v", err)) return 1 } diff --git a/internal/cli/debug_test.go b/internal/cli/debug_test.go index f77cf839ac..94765ddba7 100644 --- a/internal/cli/debug_test.go +++ b/internal/cli/debug_test.go @@ -8,7 +8,7 @@ import ( "time" "github.com/mitchellh/cli" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/ethereum/go-ethereum/internal/cli/server" ) @@ -30,7 +30,7 @@ func TestCommand_DebugBlock(t *testing.T) { // start the mock server srv, err := server.CreateMockServer(config) - assert.NoError(t, err) + require.NoError(t, err) defer server.CloseMockServer(srv) @@ -53,7 +53,7 @@ func TestCommand_DebugBlock(t *testing.T) { start := time.Now() dst1 := path.Join(output, prefix+time.Now().UTC().Format("2006-01-02-150405Z"), "block.json") res := traceBlock(port, 1, output) - assert.Equal(t, 0, res) + require.Equal(t, 0, res) t.Logf("Completed trace of block %d in %d ms at %s", 1, time.Since(start).Milliseconds(), dst1) // adding this to avoid debug directory name conflicts @@ -64,14 +64,14 @@ func TestCommand_DebugBlock(t *testing.T) { latestBlock := srv.GetLatestBlockNumber().Int64() dst2 := path.Join(output, prefix+time.Now().UTC().Format("2006-01-02-150405Z"), "block.json") res = traceBlock(port, latestBlock, output) - assert.Equal(t, 0, res) + require.Equal(t, 0, res) t.Logf("Completed trace of block %d in %d ms at %s", latestBlock, time.Since(start).Milliseconds(), dst2) // verify if the trace files are created done := verify(dst1) - assert.Equal(t, true, done) + require.Equal(t, true, done) done = verify(dst2) - assert.Equal(t, true, done) + require.Equal(t, true, done) // delete the traces deleteTraces(output) diff --git a/internal/cli/flagset/flagset.go b/internal/cli/flagset/flagset.go index b57fff9996..0c19fa9758 100644 --- a/internal/cli/flagset/flagset.go +++ b/internal/cli/flagset/flagset.go @@ -202,10 +202,11 @@ func (f *Flagset) BigIntFlag(b *BigIntFlag) { } type SliceStringFlag struct { - Name string - Usage string - Value *[]string - Group string + Name string + Usage string + Value *[]string + Default []string + Group string } func (i *SliceStringFlag) String() string { @@ -217,8 +218,8 @@ func (i *SliceStringFlag) String() string { } func (i *SliceStringFlag) Set(value string) error { - *i.Value = append(*i.Value, strings.Split(value, ",")...) - + // overwritting insted of appending + *i.Value = strings.Split(value, ",") return nil } diff --git a/internal/cli/flagset/flagset_test.go b/internal/cli/flagset/flagset_test.go index 2f046c3248..118361320d 100644 --- a/internal/cli/flagset/flagset_test.go +++ b/internal/cli/flagset/flagset_test.go @@ -23,14 +23,17 @@ func TestFlagsetBool(t *testing.T) { func TestFlagsetSliceString(t *testing.T) { f := NewFlagSet("") - value := []string{} + value := []string{"a", "b", "c"} f.SliceStringFlag(&SliceStringFlag{ - Name: "flag", - Value: &value, + Name: "flag", + Value: &value, + Default: value, }) - assert.NoError(t, f.Parse([]string{"--flag", "a,b", "--flag", "c"})) + assert.NoError(t, f.Parse([]string{})) assert.Equal(t, value, []string{"a", "b", "c"}) + assert.NoError(t, f.Parse([]string{"--flag", "a,b"})) + assert.Equal(t, value, []string{"a", "b"}) } func TestFlagsetDuration(t *testing.T) { diff --git a/internal/cli/markdown_test.go b/internal/cli/markdown_test.go index 30c272a220..24a0e40e11 100644 --- a/internal/cli/markdown_test.go +++ b/internal/cli/markdown_test.go @@ -3,12 +3,12 @@ package cli import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestCodeBlock(t *testing.T) { t.Parallel() - assert := assert.New(t) + assert := require.New(t) lines := []string{ "abc", diff --git a/internal/cli/server/chains/chain.go b/internal/cli/server/chains/chain.go index d6717f5893..582ef64d21 100644 --- a/internal/cli/server/chains/chain.go +++ b/internal/cli/server/chains/chain.go @@ -4,7 +4,6 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" "os" "github.com/ethereum/go-ethereum/common" @@ -50,7 +49,7 @@ func GetChain(name string) (*Chain, error) { } func ImportFromFile(filename string) (*Chain, error) { - data, err := ioutil.ReadFile(filename) + data, err := os.ReadFile(filename) if err != nil { return nil, err } diff --git a/internal/cli/server/config.go b/internal/cli/server/config.go index 347287e22f..fdf253a37e 100644 --- a/internal/cli/server/config.go +++ b/internal/cli/server/config.go @@ -40,8 +40,8 @@ type Config struct { // Chain is the chain to sync with Chain string `hcl:"chain,optional"` - // Name, or identity of the node - Name string `hcl:"name,optional"` + // Identity of the node + Identity string `hcl:"identity,optional"` // RequiredBlocks is a list of required (block number, hash) pairs to accept RequiredBlocks map[string]string `hcl:"requiredblocks,optional"` @@ -61,8 +61,8 @@ type Config struct { // GcMode selects the garbage collection mode for the trie GcMode string `hcl:"gc-mode,optional"` - // NoSnapshot disables the snapshot database mode - NoSnapshot bool `hcl:"no-snapshot,optional"` + // Snapshot disables/enables the snapshot database mode + Snapshot bool `hcl:"snapshot,optional"` // Ethstats is the address of the ethstats server to send telemetry Ethstats string `hcl:"ethstats,optional"` @@ -217,12 +217,6 @@ type JsonRPCConfig struct { // IPCPath is the path of the ipc endpoint IPCPath string `hcl:"ipc-path,optional"` - // VHost is the list of valid virtual hosts - VHost []string `hcl:"vhost,optional"` - - // Cors is the list of Cors endpoints - Cors []string `hcl:"cors,optional"` - // GasCap is the global gas cap for eth-call variants. GasCap uint64 `hcl:"gas-cap,optional"` @@ -232,10 +226,10 @@ type JsonRPCConfig struct { // Http has the json-rpc http related settings Http *APIConfig `hcl:"http,block"` - // Http has the json-rpc websocket related settings + // Ws has the json-rpc websocket related settings Ws *APIConfig `hcl:"ws,block"` - // Http has the json-rpc graphql related settings + // Graphql has the json-rpc graphql related settings Graphql *APIConfig `hcl:"graphql,block"` } @@ -258,7 +252,13 @@ type APIConfig struct { Host string `hcl:"host,optional"` // Modules is the list of enabled api modules - Modules []string `hcl:"modules,optional"` + API []string `hcl:"modules,optional"` + + // VHost is the list of valid virtual hosts + VHost []string `hcl:"vhost,optional"` + + // Cors is the list of Cors endpoints + Cors []string `hcl:"cors,optional"` } type GpoConfig struct { @@ -387,7 +387,7 @@ type DeveloperConfig struct { func DefaultConfig() *Config { return &Config{ Chain: "mainnet", - Name: Hostname(), + Identity: Hostname(), RequiredBlocks: map[string]string{}, LogLevel: "INFO", DataDir: defaultDataDir(), @@ -412,9 +412,9 @@ func DefaultConfig() *Config { URL: "http://localhost:1317", Without: false, }, - SyncMode: "full", - GcMode: "full", - NoSnapshot: false, + SyncMode: "full", + GcMode: "full", + Snapshot: true, TxPool: &TxPoolConfig{ Locals: []string{}, NoLocals: false, @@ -444,8 +444,6 @@ func DefaultConfig() *Config { JsonRPC: &JsonRPCConfig{ IPCDisable: false, IPCPath: "", - Cors: []string{"*"}, - VHost: []string{"*"}, GasCap: ethconfig.Defaults.RPCGasCap, TxFeeCap: ethconfig.Defaults.RPCTxFeeCap, Http: &APIConfig{ @@ -453,17 +451,23 @@ func DefaultConfig() *Config { Port: 8545, Prefix: "", Host: "localhost", - Modules: []string{"eth", "net", "web3", "txpool", "bor"}, + API: []string{"eth", "net", "web3", "txpool", "bor"}, + Cors: []string{"*"}, + VHost: []string{"*"}, }, Ws: &APIConfig{ Enabled: false, Port: 8546, Prefix: "", Host: "localhost", - Modules: []string{"web3", "net"}, + API: []string{"web3", "net"}, + Cors: []string{"*"}, + VHost: []string{"*"}, }, Graphql: &APIConfig{ Enabled: false, + Cors: []string{"*"}, + VHost: []string{"*"}, }, }, Ethstats: "", @@ -864,7 +868,7 @@ func (c *Config) buildEth(stack *node.Node, accountManager *accounts.Manager) (* } // snapshot disable check - if c.NoSnapshot { + if !c.Snapshot { if n.SyncMode == downloader.SnapSync { log.Info("Snap sync requested, enabling --snapshot") } else { @@ -908,15 +912,15 @@ func (c *Config) buildNode() (*node.Config, error) { ListenAddr: c.P2P.Bind + ":" + strconv.Itoa(int(c.P2P.Port)), DiscoveryV5: c.P2P.Discovery.V5Enabled, }, - HTTPModules: c.JsonRPC.Http.Modules, - HTTPCors: c.JsonRPC.Cors, - HTTPVirtualHosts: c.JsonRPC.VHost, + HTTPModules: c.JsonRPC.Http.API, + HTTPCors: c.JsonRPC.Http.Cors, + HTTPVirtualHosts: c.JsonRPC.Http.VHost, HTTPPathPrefix: c.JsonRPC.Http.Prefix, - WSModules: c.JsonRPC.Ws.Modules, - WSOrigins: c.JsonRPC.Cors, + WSModules: c.JsonRPC.Ws.API, + WSOrigins: c.JsonRPC.Ws.Cors, WSPathPrefix: c.JsonRPC.Ws.Prefix, - GraphQLCors: c.JsonRPC.Cors, - GraphQLVirtualHosts: c.JsonRPC.VHost, + GraphQLCors: c.JsonRPC.Graphql.Cors, + GraphQLVirtualHosts: c.JsonRPC.Graphql.VHost, } // dev mode @@ -999,7 +1003,7 @@ func (c *Config) buildNode() (*node.Config, error) { func (c *Config) Merge(cc ...*Config) error { for _, elem := range cc { - if err := mergo.Merge(c, elem, mergo.WithOverwriteWithEmptyValue, mergo.WithAppendSlice); err != nil { + if err := mergo.Merge(c, elem, mergo.WithOverwriteWithEmptyValue); err != nil { return fmt.Errorf("failed to merge configurations: %v", err) } } diff --git a/internal/cli/server/config_test.go b/internal/cli/server/config_test.go index 94c4496c03..c1c6c4ef4a 100644 --- a/internal/cli/server/config_test.go +++ b/internal/cli/server/config_test.go @@ -22,8 +22,8 @@ func TestConfigDefault(t *testing.T) { func TestConfigMerge(t *testing.T) { c0 := &Config{ - Chain: "0", - NoSnapshot: true, + Chain: "0", + Snapshot: true, RequiredBlocks: map[string]string{ "a": "b", }, @@ -54,8 +54,8 @@ func TestConfigMerge(t *testing.T) { } expected := &Config{ - Chain: "1", - NoSnapshot: false, + Chain: "1", + Snapshot: false, RequiredBlocks: map[string]string{ "a": "b", "b": "c", @@ -64,7 +64,6 @@ func TestConfigMerge(t *testing.T) { MaxPeers: 10, Discovery: &P2PDiscovery{ StaticNodes: []string{ - "a", "b", }, }, diff --git a/internal/cli/server/flags.go b/internal/cli/server/flags.go index 21e9a38cb9..e85428b9ee 100644 --- a/internal/cli/server/flags.go +++ b/internal/cli/server/flags.go @@ -16,10 +16,10 @@ func (c *Command) Flags() *flagset.Flagset { Default: c.cliConfig.Chain, }) f.StringFlag(&flagset.StringFlag{ - Name: "name", + Name: "identity", Usage: "Name/Identity of the node", - Value: &c.cliConfig.Name, - Default: c.cliConfig.Name, + Value: &c.cliConfig.Identity, + Default: c.cliConfig.Identity, }) f.StringFlag(&flagset.StringFlag{ Name: "log-level", @@ -61,10 +61,10 @@ func (c *Command) Flags() *flagset.Flagset { Value: &c.cliConfig.RequiredBlocks, }) f.BoolFlag(&flagset.BoolFlag{ - Name: "no-snapshot", - Usage: `Disables the snapshot-database mode (default = false)`, - Value: &c.cliConfig.NoSnapshot, - Default: c.cliConfig.NoSnapshot, + Name: "snapshot", + Usage: `Disables/Enables the snapshot-database mode (default = true)`, + Value: &c.cliConfig.Snapshot, + Default: c.cliConfig.Snapshot, }) // heimdall @@ -83,10 +83,11 @@ func (c *Command) Flags() *flagset.Flagset { // txpool options f.SliceStringFlag(&flagset.SliceStringFlag{ - Name: "txpool.locals", - Usage: "Comma separated accounts to treat as locals (no flush, priority inclusion)", - Value: &c.cliConfig.TxPool.Locals, - Group: "Transaction Pool", + Name: "txpool.locals", + Usage: "Comma separated accounts to treat as locals (no flush, priority inclusion)", + Value: &c.cliConfig.TxPool.Locals, + Default: c.cliConfig.TxPool.Locals, + Group: "Transaction Pool", }) f.BoolFlag(&flagset.BoolFlag{ Name: "txpool.nolocals", @@ -329,16 +330,46 @@ func (c *Command) Flags() *flagset.Flagset { Group: "JsonRPC", }) f.SliceStringFlag(&flagset.SliceStringFlag{ - Name: "jsonrpc.corsdomain", - Usage: "Comma separated list of domains from which to accept cross origin requests (browser enforced)", - Value: &c.cliConfig.JsonRPC.Cors, - Group: "JsonRPC", + Name: "http.corsdomain", + Usage: "Comma separated list of domains from which to accept cross origin requests (browser enforced)", + Value: &c.cliConfig.JsonRPC.Http.Cors, + Default: c.cliConfig.JsonRPC.Http.Cors, + Group: "JsonRPC", }) f.SliceStringFlag(&flagset.SliceStringFlag{ - Name: "jsonrpc.vhosts", - Usage: "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard.", - Value: &c.cliConfig.JsonRPC.VHost, - Group: "JsonRPC", + Name: "http.vhosts", + Usage: "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard.", + Value: &c.cliConfig.JsonRPC.Http.VHost, + Default: c.cliConfig.JsonRPC.Http.VHost, + Group: "JsonRPC", + }) + f.SliceStringFlag(&flagset.SliceStringFlag{ + Name: "ws.corsdomain", + Usage: "Comma separated list of domains from which to accept cross origin requests (browser enforced)", + Value: &c.cliConfig.JsonRPC.Ws.Cors, + Default: c.cliConfig.JsonRPC.Ws.Cors, + Group: "JsonRPC", + }) + f.SliceStringFlag(&flagset.SliceStringFlag{ + Name: "ws.vhosts", + Usage: "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard.", + Value: &c.cliConfig.JsonRPC.Ws.VHost, + Default: c.cliConfig.JsonRPC.Ws.VHost, + Group: "JsonRPC", + }) + f.SliceStringFlag(&flagset.SliceStringFlag{ + Name: "graphql.corsdomain", + Usage: "Comma separated list of domains from which to accept cross origin requests (browser enforced)", + Value: &c.cliConfig.JsonRPC.Graphql.Cors, + Default: c.cliConfig.JsonRPC.Graphql.Cors, + Group: "JsonRPC", + }) + f.SliceStringFlag(&flagset.SliceStringFlag{ + Name: "graphql.vhosts", + Usage: "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard.", + Value: &c.cliConfig.JsonRPC.Graphql.VHost, + Default: c.cliConfig.JsonRPC.Graphql.VHost, + Group: "JsonRPC", }) // http options @@ -371,10 +402,11 @@ func (c *Command) Flags() *flagset.Flagset { Group: "JsonRPC", }) f.SliceStringFlag(&flagset.SliceStringFlag{ - Name: "http.modules", - Usage: "API's offered over the HTTP-RPC interface", - Value: &c.cliConfig.JsonRPC.Http.Modules, - Group: "JsonRPC", + Name: "http.api", + Usage: "API's offered over the HTTP-RPC interface", + Value: &c.cliConfig.JsonRPC.Http.API, + Default: c.cliConfig.JsonRPC.Http.API, + Group: "JsonRPC", }) // ws options @@ -407,10 +439,11 @@ func (c *Command) Flags() *flagset.Flagset { Group: "JsonRPC", }) f.SliceStringFlag(&flagset.SliceStringFlag{ - Name: "ws.modules", - Usage: "API's offered over the WS-RPC interface", - Value: &c.cliConfig.JsonRPC.Ws.Modules, - Group: "JsonRPC", + Name: "ws.api", + Usage: "API's offered over the WS-RPC interface", + Value: &c.cliConfig.JsonRPC.Ws.API, + Default: c.cliConfig.JsonRPC.Ws.API, + Group: "JsonRPC", }) // graphql options @@ -438,10 +471,11 @@ func (c *Command) Flags() *flagset.Flagset { Group: "P2P", }) f.SliceStringFlag(&flagset.SliceStringFlag{ - Name: "bootnodes", - Usage: "Comma separated enode URLs for P2P discovery bootstrap", - Value: &c.cliConfig.P2P.Discovery.Bootnodes, - Group: "P2P", + Name: "bootnodes", + Usage: "Comma separated enode URLs for P2P discovery bootstrap", + Value: &c.cliConfig.P2P.Discovery.Bootnodes, + Default: c.cliConfig.P2P.Discovery.Bootnodes, + Group: "P2P", }) f.Uint64Flag(&flagset.Uint64Flag{ Name: "maxpeers", @@ -581,10 +615,11 @@ func (c *Command) Flags() *flagset.Flagset { // account f.SliceStringFlag(&flagset.SliceStringFlag{ - Name: "unlock", - Usage: "Comma separated list of accounts to unlock", - Value: &c.cliConfig.Accounts.Unlock, - Group: "Account Management", + Name: "unlock", + Usage: "Comma separated list of accounts to unlock", + Value: &c.cliConfig.Accounts.Unlock, + Default: c.cliConfig.Accounts.Unlock, + Group: "Account Management", }) f.StringFlag(&flagset.StringFlag{ Name: "password", diff --git a/internal/cli/server/helper.go b/internal/cli/server/helper.go index 3a232d3185..428acceea3 100644 --- a/internal/cli/server/helper.go +++ b/internal/cli/server/helper.go @@ -2,7 +2,6 @@ package server import ( "fmt" - "io/ioutil" "math/rand" "net" "os" @@ -55,7 +54,7 @@ func CreateMockServer(config *Config) (*Server, error) { config.GRPC.Addr = fmt.Sprintf(":%d", port) // datadir - datadir, _ := ioutil.TempDir("/tmp", "bor-cli-test") + datadir, _ := os.MkdirTemp("/tmp", "bor-cli-test") config.DataDir = datadir // find available port for http server diff --git a/internal/cli/server/server.go b/internal/cli/server/server.go index 7d5ec2c8f3..77d310061c 100644 --- a/internal/cli/server/server.go +++ b/internal/cli/server/server.go @@ -23,8 +23,8 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/keystore" - "github.com/ethereum/go-ethereum/consensus/beacon" - "github.com/ethereum/go-ethereum/consensus/bor" + "github.com/ethereum/go-ethereum/consensus/beacon" //nolint:typecheck + "github.com/ethereum/go-ethereum/consensus/bor" //nolint:typecheck "github.com/ethereum/go-ethereum/consensus/clique" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/tracers" @@ -182,7 +182,7 @@ func NewServer(config *Config) (*Server, error) { // graphql is started from another place if config.JsonRPC.Graphql.Enabled { - if err := graphql.New(stack, srv.backend.APIBackend, config.JsonRPC.Cors, config.JsonRPC.VHost); err != nil { + if err := graphql.New(stack, srv.backend.APIBackend, config.JsonRPC.Graphql.Cors, config.JsonRPC.Graphql.VHost); err != nil { return nil, fmt.Errorf("failed to register the GraphQL service: %v", err) } } @@ -201,7 +201,7 @@ func NewServer(config *Config) (*Server, error) { } } - if err := srv.setupMetrics(config.Telemetry, config.Name); err != nil { + if err := srv.setupMetrics(config.Telemetry, config.Identity); err != nil { return nil, err } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 2e22095460..0d3aeb35c6 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -25,6 +25,8 @@ import ( "time" "github.com/davecgh/go-spew/spew" + "github.com/tyler-smith/go-bip39" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/keystore" @@ -47,7 +49,6 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" - "github.com/tyler-smith/go-bip39" ) // PublicEthereumAPI provides an API to access Ethereum related information. @@ -829,10 +830,10 @@ func (s *PublicBlockChainAPI) getAuthor(head *types.Header) *common.Address { } // GetBlockByNumber returns the requested canonical block. -// * When blockNr is -1 the chain head is returned. -// * When blockNr is -2 the pending chain head is returned. -// * When fullTx is true all transactions in the block are returned, otherwise -// only the transaction hash is returned. +// - When blockNr is -1 the chain head is returned. +// - When blockNr is -2 the pending chain head is returned. +// - When fullTx is true all transactions in the block are returned, otherwise +// only the transaction hash is returned. func (s *PublicBlockChainAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) { block, err := s.b.BlockByNumber(ctx, number) if block != nil && err == nil { diff --git a/les/client.go b/les/client.go index 87d4f00dad..58944658c9 100644 --- a/les/client.go +++ b/les/client.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/downloader/whitelist" "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/eth/gasprice" @@ -145,7 +146,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { } // Note: NewLightChain adds the trusted checkpoint so it needs an ODR with // indexers already set but not started yet - if leth.blockchain, err = light.NewLightChain(leth.odr, leth.chainConfig, leth.engine, checkpoint); err != nil { + if leth.blockchain, err = light.NewLightChain(leth.odr, leth.chainConfig, leth.engine, checkpoint, whitelist.NewService(10)); err != nil { return nil, err } leth.chainReader = leth.blockchain diff --git a/les/test_helper.go b/les/test_helper.go index 480d249dca..a099458353 100644 --- a/les/test_helper.go +++ b/les/test_helper.go @@ -203,7 +203,7 @@ func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, index oracle *checkpointoracle.CheckpointOracle ) genesis := gspec.MustCommit(db) - chain, _ := light.NewLightChain(odr, gspec.Config, engine, nil) + chain, _ := light.NewLightChain(odr, gspec.Config, engine, nil, nil) if indexers != nil { checkpointConfig := ¶ms.CheckpointOracleConfig{ Address: crypto.CreateAddress(bankAddr, 0), diff --git a/light/lightchain.go b/light/lightchain.go index ee4146a2d8..98ffa216c6 100644 --- a/light/lightchain.go +++ b/light/lightchain.go @@ -26,6 +26,9 @@ import ( "sync/atomic" "time" + lru "github.com/hashicorp/golang-lru" + + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core" @@ -37,7 +40,6 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" - lru "github.com/hashicorp/golang-lru" ) var ( @@ -81,7 +83,7 @@ type LightChain struct { // NewLightChain returns a fully initialised light chain using information // available in the database. It initialises the default Ethereum header // validator. -func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus.Engine, checkpoint *params.TrustedCheckpoint) (*LightChain, error) { +func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus.Engine, checkpoint *params.TrustedCheckpoint, checker ethereum.ChainValidator) (*LightChain, error) { bodyCache, _ := lru.New(bodyCacheLimit) bodyRLPCache, _ := lru.New(bodyCacheLimit) blockCache, _ := lru.New(blockCacheLimit) @@ -96,7 +98,7 @@ func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus. blockCache: blockCache, engine: engine, } - bc.forker = core.NewForkChoice(bc, nil) + bc.forker = core.NewForkChoice(bc, nil, checker) var err error bc.hc, err = core.NewHeaderChain(odr.Database(), config, bc.engine, bc.getProcInterrupt) if err != nil { diff --git a/light/lightchain_test.go b/light/lightchain_test.go index 8600e56345..4747fc58c6 100644 --- a/light/lightchain_test.go +++ b/light/lightchain_test.go @@ -56,7 +56,7 @@ func newCanonical(n int) (ethdb.Database, *LightChain, error) { db := rawdb.NewMemoryDatabase() gspec := core.Genesis{Config: params.TestChainConfig} genesis := gspec.MustCommit(db) - blockchain, _ := NewLightChain(&dummyOdr{db: db, indexerConfig: TestClientIndexerConfig}, gspec.Config, ethash.NewFaker(), nil) + blockchain, _ := NewLightChain(&dummyOdr{db: db, indexerConfig: TestClientIndexerConfig}, gspec.Config, ethash.NewFaker(), nil, nil) // Create and inject the requested chain if n == 0 { @@ -76,7 +76,7 @@ func newTestLightChain() *LightChain { Config: params.TestChainConfig, } gspec.MustCommit(db) - lc, err := NewLightChain(&dummyOdr{db: db}, gspec.Config, ethash.NewFullFaker(), nil) + lc, err := NewLightChain(&dummyOdr{db: db}, gspec.Config, ethash.NewFullFaker(), nil, nil) if err != nil { panic(err) } @@ -347,7 +347,7 @@ func TestReorgBadHeaderHashes(t *testing.T) { defer func() { delete(core.BadHashes, headers[3].Hash()) }() // Create a new LightChain and check that it rolled back the state. - ncm, err := NewLightChain(&dummyOdr{db: bc.chainDb}, params.TestChainConfig, ethash.NewFaker(), nil) + ncm, err := NewLightChain(&dummyOdr{db: bc.chainDb}, params.TestChainConfig, ethash.NewFaker(), nil, nil) if err != nil { t.Fatalf("failed to create new chain manager: %v", err) } diff --git a/light/odr_test.go b/light/odr_test.go index fdf657a82e..9f4b42e675 100644 --- a/light/odr_test.go +++ b/light/odr_test.go @@ -261,14 +261,14 @@ func testChainOdr(t *testing.T, protocol int, fn odrTestFn) { ) gspec.MustCommit(ldb) // Assemble the test environment - blockchain, _ := core.NewBlockChain(sdb, nil, params.TestChainConfig, ethash.NewFullFaker(), vm.Config{}, nil, nil) + blockchain, _ := core.NewBlockChain(sdb, nil, params.TestChainConfig, ethash.NewFullFaker(), vm.Config{}, nil, nil, nil) gchain, _ := core.GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), sdb, 4, testChainGen) if _, err := blockchain.InsertChain(gchain); err != nil { t.Fatal(err) } odr := &testOdr{sdb: sdb, ldb: ldb, indexerConfig: TestClientIndexerConfig} - lightchain, err := NewLightChain(odr, params.TestChainConfig, ethash.NewFullFaker(), nil) + lightchain, err := NewLightChain(odr, params.TestChainConfig, ethash.NewFullFaker(), nil, nil) if err != nil { t.Fatal(err) } diff --git a/light/trie_test.go b/light/trie_test.go index e8294cc2a2..24cf6ec48e 100644 --- a/light/trie_test.go +++ b/light/trie_test.go @@ -44,7 +44,8 @@ func TestNodeIterator(t *testing.T) { genesis = gspec.MustCommit(fulldb) ) gspec.MustCommit(lightdb) - blockchain, _ := core.NewBlockChain(fulldb, nil, params.TestChainConfig, ethash.NewFullFaker(), vm.Config{}, nil, nil) + + blockchain, _ := core.NewBlockChain(fulldb, nil, params.TestChainConfig, ethash.NewFullFaker(), vm.Config{}, nil, nil, nil) gchain, _ := core.GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), fulldb, 4, testChainGen) if _, err := blockchain.InsertChain(gchain); err != nil { panic(err) diff --git a/light/txpool_test.go b/light/txpool_test.go index cc2651d29a..a078a18f1a 100644 --- a/light/txpool_test.go +++ b/light/txpool_test.go @@ -91,7 +91,7 @@ func TestTxPool(t *testing.T) { ) gspec.MustCommit(ldb) // Assemble the test environment - blockchain, _ := core.NewBlockChain(sdb, nil, params.TestChainConfig, ethash.NewFullFaker(), vm.Config{}, nil, nil) + blockchain, _ := core.NewBlockChain(sdb, nil, params.TestChainConfig, ethash.NewFullFaker(), vm.Config{}, nil, nil, nil) gchain, _ := core.GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), sdb, poolTestBlocks, txPoolTestChainGen) if _, err := blockchain.InsertChain(gchain); err != nil { panic(err) @@ -103,7 +103,7 @@ func TestTxPool(t *testing.T) { discard: make(chan int, 1), mined: make(chan int, 1), } - lightchain, _ := NewLightChain(odr, params.TestChainConfig, ethash.NewFullFaker(), nil) + lightchain, _ := NewLightChain(odr, params.TestChainConfig, ethash.NewFullFaker(), nil, nil) txPermanent = 50 pool := NewTxPool(params.TestChainConfig, lightchain, relay) ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) diff --git a/miner/fake_miner.go b/miner/fake_miner.go index 43b397c428..c5c1fbd3b1 100644 --- a/miner/fake_miner.go +++ b/miner/fake_miner.go @@ -84,7 +84,7 @@ func createBorMiner(t *testing.T, ethAPIMock api.Caller, spanner bor.Spanner, he engine := NewFakeBor(t, chainDB, chainConfig, ethAPIMock, spanner, heimdallClientMock, contractMock) // Create Ethereum backend - bc, err := core.NewBlockChain(chainDB, nil, chainConfig, engine, vm.Config{}, nil, nil) + bc, err := core.NewBlockChain(chainDB, nil, chainConfig, engine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("can't create new chain %v", err) } diff --git a/miner/test_backend.go b/miner/test_backend.go index d1d9d5b237..29da747ae0 100644 --- a/miner/test_backend.go +++ b/miner/test_backend.go @@ -91,7 +91,7 @@ func newTestWorkerBackend(t TensingObject, chainConfig *params.ChainConfig, engi genesis := gspec.MustCommit(db) - chain, _ := core.NewBlockChain(db, &core.CacheConfig{TrieDirtyDisabled: true}, gspec.Config, engine, vm.Config{}, nil, nil) + chain, _ := core.NewBlockChain(db, &core.CacheConfig{TrieDirtyDisabled: true}, gspec.Config, engine, vm.Config{}, nil, nil, nil) txpool := core.NewTxPool(testTxPoolConfig, chainConfig, chain) // Generate a small n-block chain and an uncle block for it diff --git a/miner/worker_test.go b/miner/worker_test.go index 9bb2a538ce..011895c854 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -114,7 +114,7 @@ func testGenerateBlockAndImport(t *testing.T, isClique bool, isBor bool) { db2 := rawdb.NewMemoryDatabase() b.Genesis.MustCommit(db2) - chain, _ := core.NewBlockChain(db2, nil, b.chain.Config(), engine, vm.Config{}, nil, nil) + chain, _ := core.NewBlockChain(db2, nil, b.chain.Config(), engine, vm.Config{}, nil, nil, nil) defer chain.Stop() // Ignore empty commit here for less noise. @@ -650,7 +650,7 @@ func BenchmarkBorMining(b *testing.B) { db2 := rawdb.NewMemoryDatabase() back.Genesis.MustCommit(db2) - chain, _ := core.NewBlockChain(db2, nil, back.chain.Config(), engine, vm.Config{}, nil, nil) + chain, _ := core.NewBlockChain(db2, nil, back.chain.Config(), engine, vm.Config{}, nil, nil, nil) defer chain.Stop() // Ignore empty commit here for less noise. diff --git a/tests/block_test_util.go b/tests/block_test_util.go index d28f3a1237..487fd2d4d8 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -128,7 +128,8 @@ func (t *BlockTest) Run(snapshotter bool) error { cache.SnapshotLimit = 1 cache.SnapshotWait = true } - chain, err := core.NewBlockChain(db, cache, config, engine, vm.Config{}, nil, nil) + + chain, err := core.NewBlockChain(db, cache, config, engine, vm.Config{}, nil, nil, nil) if err != nil { return err } diff --git a/tests/bor/bor_api_test.go b/tests/bor/bor_api_test.go new file mode 100644 index 0000000000..fc81480902 --- /dev/null +++ b/tests/bor/bor_api_test.go @@ -0,0 +1,168 @@ +//go:build integration + +package bor + +import ( + "context" + "math/big" + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/bor" + "github.com/ethereum/go-ethereum/consensus/bor/clerk" + "github.com/ethereum/go-ethereum/consensus/bor/heimdall/checkpoint" + "github.com/ethereum/go-ethereum/consensus/bor/valset" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/tests/bor/mocks" +) + +func TestGetTransactionReceiptsByBlock(t *testing.T) { + init := buildEthereumInstance(t, rawdb.NewMemoryDatabase()) + chain := init.ethereum.BlockChain() + engine := init.ethereum.Engine() + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + _bor := engine.(*bor.Bor) + defer _bor.Close() + + // Mock /bor/span/1 + res, _ := loadSpanFromFile(t) + + h := mocks.NewMockIHeimdallClient(ctrl) + + h.EXPECT().Span(gomock.Any(), uint64(1)).Return(&res.Result, nil).MinTimes(1) + h.EXPECT().Close().MinTimes(1) + h.EXPECT().FetchCheckpoint(gomock.Any(), int64(-1)).Return(&checkpoint.Checkpoint{ + Proposer: res.Result.SelectedProducers[0].Address, + StartBlock: big.NewInt(0), + EndBlock: big.NewInt(int64(spanSize)), + }, nil).AnyTimes() + + // Mock State Sync events + // at # sprintSize, events are fetched for [fromID, (block-sprint).Time) + fromID := uint64(1) + to := int64(chain.GetHeaderByNumber(0).Time) + sample := getSampleEventRecord(t) + + // First query will be from [id=1, (block-sprint).Time] + eventRecords := []*clerk.EventRecordWithTime{ + buildStateEvent(sample, 1, 1), + buildStateEvent(sample, 2, 2), + buildStateEvent(sample, 3, 3), + } + + h.EXPECT().StateSyncEvents(gomock.Any(), fromID, to).Return(eventRecords, nil).MinTimes(1) + _bor.SetHeimdallClient(h) + + // Insert blocks for 0th sprint + db := init.ethereum.ChainDb() + block := init.genesis.ToBlock(db) + + signer := types.LatestSigner(init.genesis.Config) + toAddress := common.HexToAddress("0x000000000000000000000000000000000000aaaa") + + currentValidators := []*valset.Validator{valset.NewValidator(addr, 10)} + txHashes := map[int]common.Hash{} // blockNumber -> txHash + + var ( + err error + nonce uint64 + tx *types.Transaction + txs []*types.Transaction + ) + + for i := uint64(1); i <= sprintSize; i++ { + if IsSpanEnd(i) { + currentValidators = []*valset.Validator{valset.NewValidator(addr, 10)} + } + + if i%3 == 0 { + txdata := &types.LegacyTx{ + Nonce: nonce, + To: &toAddress, + Gas: 30000, + GasPrice: newGwei(5), + } + + nonce++ + + tx = types.NewTx(txdata) + tx, err = types.SignTx(tx, signer, key) + require.Nil(t, err, "an incorrect transaction or signer") + + txs = []*types.Transaction{tx} + } else { + txs = nil + } + + block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor, txs, currentValidators) + insertNewBlock(t, chain, block) + + if len(txs) != 0 { + txHashes[int(block.Number().Uint64())] = tx.Hash() + } + } + + // state 6 was not written + // + fromID = uint64(4) + to = int64(chain.GetHeaderByNumber(sprintSize).Time) + + eventRecords = []*clerk.EventRecordWithTime{ + buildStateEvent(sample, 4, 4), + buildStateEvent(sample, 5, 5), + } + h.EXPECT().StateSyncEvents(gomock.Any(), fromID, to).Return(eventRecords, nil).MinTimes(1) + + for i := sprintSize + 1; i <= spanSize; i++ { + block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor, nil, currentValidators) + insertNewBlock(t, chain, block) + } + + ethAPI := ethapi.NewPublicBlockChainAPI(init.ethereum.APIBackend) + txPoolAPI := ethapi.NewPublicTransactionPoolAPI(init.ethereum.APIBackend, nil) + + for n := 0; n < int(spanSize)+1; n++ { + rpcNumber := rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(n)) + + txs, err := ethAPI.GetTransactionReceiptsByBlock(context.Background(), rpcNumber) + require.Nil(t, err) + + tx := txPoolAPI.GetTransactionByBlockNumberAndIndex(context.Background(), rpc.BlockNumber(n), 0) + + blockMap, err := ethAPI.GetBlockByNumber(context.Background(), rpc.BlockNumber(n), true) + require.Nil(t, err) + + expectedTxHash, ok := txHashes[n] + // FIXME: add `IsSprintStart(uint64(n)) || IsSpanStart(uint64(n))` after adding a full state receiver contract + if ok { + require.Len(t, txs, 1) + + require.NotNil(t, tx, "not nil receipt expected") + + require.Equal(t, expectedTxHash, tx.Hash, "got different from expected receipt") + + blockTxs, ok := blockMap["transactions"].([]interface{}) + require.Len(t, blockTxs, 1) + + blockTx, ok := blockTxs[0].(*ethapi.RPCTransaction) + require.True(t, ok) + require.Equal(t, expectedTxHash, blockTx.Hash) + } else { + require.Len(t, txs, 0) + + require.Nil(t, tx, "nil receipt expected") + + blockTxs, _ := blockMap["transactions"].([]interface{}) + require.Len(t, blockTxs, 0) + } + } +} diff --git a/tests/bor/bor_test.go b/tests/bor/bor_test.go index d179c6ea9b..646a38604d 100644 --- a/tests/bor/bor_test.go +++ b/tests/bor/bor_test.go @@ -11,14 +11,15 @@ import ( "time" "github.com/golang/mock/gomock" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "golang.org/x/crypto/sha3" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/bor" "github.com/ethereum/go-ethereum/consensus/bor/clerk" "github.com/ethereum/go-ethereum/consensus/bor/heimdall/checkpoint" - "github.com/ethereum/go-ethereum/consensus/bor/heimdall/span" + "github.com/ethereum/go-ethereum/consensus/bor/valset" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" @@ -38,13 +39,13 @@ func TestInsertingSpanSizeBlocks(t *testing.T) { defer _bor.Close() - h, heimdallSpan, ctrl := getMockedHeimdallClient(t) - defer ctrl.Finish() - _, currentSpan := loadSpanFromFile(t) + h, ctrl := getMockedHeimdallClient(t, currentSpan) + defer ctrl.Finish() + h.EXPECT().Close().AnyTimes() - h.EXPECT().FetchLatestCheckpoint(gomock.Any()).Return(&checkpoint.Checkpoint{ + h.EXPECT().FetchCheckpoint(gomock.Any(), int64(-1)).Return(&checkpoint.Checkpoint{ Proposer: currentSpan.SelectedProducers[0].Address, StartBlock: big.NewInt(0), EndBlock: big.NewInt(int64(spanSize)), @@ -56,9 +57,11 @@ func TestInsertingSpanSizeBlocks(t *testing.T) { block := init.genesis.ToBlock(db) // to := int64(block.Header().Time) + currentValidators := []*valset.Validator{valset.NewValidator(addr, 10)} + // Insert sprintSize # of blocks so that span is fetched at the start of a new sprint for i := uint64(1); i <= spanSize; i++ { - block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor) + block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor, nil, currentValidators) insertNewBlock(t, chain, block) } @@ -67,10 +70,10 @@ func TestInsertingSpanSizeBlocks(t *testing.T) { t.Fatalf("%s", err) } - assert.Equal(t, 3, len(validators)) + require.Equal(t, 3, len(validators)) for i, validator := range validators { - assert.Equal(t, validator.Address.Bytes(), heimdallSpan.SelectedProducers[i].Address.Bytes()) - assert.Equal(t, validator.VotingPower, heimdallSpan.SelectedProducers[i].VotingPower) + require.Equal(t, validator.Address.Bytes(), currentSpan.SelectedProducers[i].Address.Bytes()) + require.Equal(t, validator.VotingPower, currentSpan.SelectedProducers[i].VotingPower) } } @@ -85,16 +88,23 @@ func TestFetchStateSyncEvents(t *testing.T) { // A. Insert blocks for 0th sprint db := init.ethereum.ChainDb() block := init.genesis.ToBlock(db) + + // B.1 Mock /bor/span/1 + res, _ := loadSpanFromFile(t) + + currentValidators := []*valset.Validator{valset.NewValidator(addr, 10)} + // Insert sprintSize # of blocks so that span is fetched at the start of a new sprint for i := uint64(1); i < sprintSize; i++ { - block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor) + if IsSpanEnd(i) { + currentValidators = res.Result.ValidatorSet.Validators + } + + block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor, nil, currentValidators) insertNewBlock(t, chain, block) } // B. Before inserting 1st block of the next sprint, mock heimdall deps - // B.1 Mock /bor/span/1 - res, _ := loadSpanFromFile(t) - ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -115,7 +125,7 @@ func TestFetchStateSyncEvents(t *testing.T) { h.EXPECT().StateSyncEvents(gomock.Any(), fromID, to).Return(eventRecords, nil).AnyTimes() _bor.SetHeimdallClient(h) - block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor) + block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor, nil, res.Result.ValidatorSet.Validators) insertNewBlock(t, chain, block) } @@ -130,6 +140,9 @@ func TestFetchStateSyncEvents_2(t *testing.T) { // Mock /bor/span/1 res, _ := loadSpanFromFile(t) + // add the block producer + res.Result.ValidatorSet.Validators = append(res.Result.ValidatorSet.Validators, valset.NewValidator(addr, 4500)) + ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -160,15 +173,24 @@ func TestFetchStateSyncEvents_2(t *testing.T) { // Insert blocks for 0th sprint db := init.ethereum.ChainDb() block := init.genesis.ToBlock(db) + + var currentValidators []*valset.Validator + for i := uint64(1); i <= sprintSize; i++ { - block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor) + if IsSpanEnd(i) { + currentValidators = res.Result.ValidatorSet.Validators + } else { + currentValidators = []*valset.Validator{valset.NewValidator(addr, 10)} + } + + block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor, nil, currentValidators) insertNewBlock(t, chain, block) } lastStateID, _ := _bor.GenesisContractsClient.LastStateId(sprintSize) // state 6 was not written - assert.Equal(t, uint64(4), lastStateID.Uint64()) + require.Equal(t, uint64(4), lastStateID.Uint64()) // fromID = uint64(5) @@ -181,12 +203,18 @@ func TestFetchStateSyncEvents_2(t *testing.T) { h.EXPECT().StateSyncEvents(gomock.Any(), fromID, to).Return(eventRecords, nil).AnyTimes() for i := sprintSize + 1; i <= spanSize; i++ { - block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor) + if IsSpanEnd(i) { + currentValidators = res.Result.ValidatorSet.Validators + } else { + currentValidators = []*valset.Validator{valset.NewValidator(addr, 10)} + } + + block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor, nil, res.Result.ValidatorSet.Validators) insertNewBlock(t, chain, block) } lastStateID, _ = _bor.GenesisContractsClient.LastStateId(spanSize) - assert.Equal(t, uint64(6), lastStateID.Uint64()) + require.Equal(t, uint64(6), lastStateID.Uint64()) } func TestOutOfTurnSigning(t *testing.T) { @@ -197,17 +225,29 @@ func TestOutOfTurnSigning(t *testing.T) { defer _bor.Close() - h, _, ctrl := getMockedHeimdallClient(t) + _, heimdallSpan := loadSpanFromFile(t) + proposer := valset.NewValidator(addr, 10) + heimdallSpan.ValidatorSet.Validators = append(heimdallSpan.ValidatorSet.Validators, proposer) + + // add the block producer + h, ctrl := getMockedHeimdallClient(t, heimdallSpan) defer ctrl.Finish() h.EXPECT().Close().AnyTimes() + _bor.SetHeimdallClient(h) db := init.ethereum.ChainDb() block := init.genesis.ToBlock(db) + setDifficulty := func(header *types.Header) { + if IsSprintStart(header.Number.Uint64()) { + header.Difficulty = big.NewInt(int64(len(heimdallSpan.ValidatorSet.Validators))) + } + } + for i := uint64(1); i < spanSize; i++ { - block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor) + block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor, nil, heimdallSpan.ValidatorSet.Validators, setDifficulty) insertNewBlock(t, chain, block) } @@ -215,36 +255,50 @@ func TestOutOfTurnSigning(t *testing.T) { // This account is one the out-of-turn validators for 1st (0-indexed) span signer := "c8deb0bea5c41afe8e37b4d1bd84e31adff11b09c8c96ff4b605003cce067cd9" signerKey, _ := hex.DecodeString(signer) - key, _ = crypto.HexToECDSA(signer) - addr = crypto.PubkeyToAddress(key.PublicKey) + newKey, _ := crypto.HexToECDSA(signer) + newAddr := crypto.PubkeyToAddress(newKey.PublicKey) expectedSuccessionNumber := 2 - block = buildNextBlock(t, _bor, chain, block, signerKey, init.genesis.Config.Bor) - _, err := chain.InsertChain([]*types.Block{block}) - assert.Equal(t, - *err.(*bor.BlockTooSoonError), - bor.BlockTooSoonError{Number: spanSize, Succession: expectedSuccessionNumber}) + parentTime := block.Time() - expectedDifficulty := uint64(3 - expectedSuccessionNumber) // len(validators) - succession + setParentTime := func(header *types.Header) { + header.Time = parentTime + 1 + } + + const turn = 1 + + setDifficulty = func(header *types.Header) { + header.Difficulty = big.NewInt(int64(len(heimdallSpan.ValidatorSet.Validators)) - turn) + } + + block = buildNextBlock(t, _bor, chain, block, signerKey, init.genesis.Config.Bor, nil, heimdallSpan.ValidatorSet.Validators, setParentTime, setDifficulty) + _, err := chain.InsertChain([]*types.Block{block}) + require.Equal(t, + bor.BlockTooSoonError{Number: spanSize, Succession: expectedSuccessionNumber}, + *err.(*bor.BlockTooSoonError)) + + expectedDifficulty := uint64(len(heimdallSpan.ValidatorSet.Validators) - expectedSuccessionNumber - turn) // len(validators) - succession header := block.Header() - header.Time += bor.CalcProducerDelay(header.Number.Uint64(), expectedSuccessionNumber, init.genesis.Config.Bor) - - bor.CalcProducerDelay(header.Number.Uint64(), 0, init.genesis.Config.Bor) + diff := bor.CalcProducerDelay(header.Number.Uint64(), expectedSuccessionNumber, init.genesis.Config.Bor) + header.Time += diff sign(t, header, signerKey, init.genesis.Config.Bor) + block = types.NewBlockWithHeader(header) _, err = chain.InsertChain([]*types.Block{block}) - assert.Equal(t, - *err.(*bor.WrongDifficultyError), - bor.WrongDifficultyError{Number: spanSize, Expected: expectedDifficulty, Actual: 3, Signer: addr.Bytes()}) + require.NotNil(t, err) + require.Equal(t, + bor.WrongDifficultyError{Number: spanSize, Expected: expectedDifficulty, Actual: 3, Signer: newAddr.Bytes()}, + *err.(*bor.WrongDifficultyError)) header.Difficulty = new(big.Int).SetUint64(expectedDifficulty) sign(t, header, signerKey, init.genesis.Config.Bor) block = types.NewBlockWithHeader(header) _, err = chain.InsertChain([]*types.Block{block}) - assert.Nil(t, err) + require.Nil(t, err) } func TestSignerNotFound(t *testing.T) { @@ -255,7 +309,9 @@ func TestSignerNotFound(t *testing.T) { defer _bor.Close() - h, _, ctrl := getMockedHeimdallClient(t) + _, heimdallSpan := loadSpanFromFile(t) + + h, ctrl := getMockedHeimdallClient(t, heimdallSpan) defer ctrl.Finish() h.EXPECT().Close().AnyTimes() @@ -266,62 +322,21 @@ func TestSignerNotFound(t *testing.T) { block := init.genesis.ToBlock(db) // random signer account that is not a part of the validator set - signer := "3714d99058cd64541433d59c6b391555b2fd9b54629c2b717a6c9c00d1127b6b" + const signer = "3714d99058cd64541433d59c6b391555b2fd9b54629c2b717a6c9c00d1127b6b" signerKey, _ := hex.DecodeString(signer) - key, _ = crypto.HexToECDSA(signer) - addr = crypto.PubkeyToAddress(key.PublicKey) + newKey, _ := crypto.HexToECDSA(signer) + newAddr := crypto.PubkeyToAddress(newKey.PublicKey) + + _bor.Authorize(newAddr, func(account accounts.Account, s string, data []byte) ([]byte, error) { + return crypto.Sign(crypto.Keccak256(data), newKey) + }) + + block = buildNextBlock(t, _bor, chain, block, signerKey, init.genesis.Config.Bor, nil, heimdallSpan.ValidatorSet.Validators) - block = buildNextBlock(t, _bor, chain, block, signerKey, init.genesis.Config.Bor) _, err := chain.InsertChain([]*types.Block{block}) - assert.Equal(t, + require.Equal(t, *err.(*bor.UnauthorizedSignerError), - bor.UnauthorizedSignerError{Number: 0, Signer: addr.Bytes()}) -} - -func getMockedHeimdallClient(t *testing.T) (*mocks.MockIHeimdallClient, *span.HeimdallSpan, *gomock.Controller) { - ctrl := gomock.NewController(t) - h := mocks.NewMockIHeimdallClient(ctrl) - - _, heimdallSpan := loadSpanFromFile(t) - - h.EXPECT().Span(gomock.Any(), uint64(1)).Return(heimdallSpan, nil).AnyTimes() - - h.EXPECT().StateSyncEvents(gomock.Any(), gomock.Any(), gomock.Any()). - Return([]*clerk.EventRecordWithTime{getSampleEventRecord(t)}, nil).AnyTimes() - - return h, heimdallSpan, ctrl -} - -func generateFakeStateSyncEvents(sample *clerk.EventRecordWithTime, count int) []*clerk.EventRecordWithTime { - events := make([]*clerk.EventRecordWithTime, count) - event := *sample - event.ID = 1 - events[0] = &clerk.EventRecordWithTime{} - *events[0] = event - for i := 1; i < count; i++ { - event.ID = uint64(i) - event.Time = event.Time.Add(1 * time.Second) - events[i] = &clerk.EventRecordWithTime{} - *events[i] = event - } - return events -} - -func buildStateEvent(sample *clerk.EventRecordWithTime, id uint64, timeStamp int64) *clerk.EventRecordWithTime { - event := *sample - event.ID = id - event.Time = time.Unix(timeStamp, 0) - return &event -} - -func getSampleEventRecord(t *testing.T) *clerk.EventRecordWithTime { - eventRecords := stateSyncEventsPayload(t) - eventRecords.Result[0].Time = time.Unix(1, 0) - return eventRecords.Result[0] -} - -func getEventRecords(t *testing.T) []*clerk.EventRecordWithTime { - return stateSyncEventsPayload(t).Result + bor.UnauthorizedSignerError{Number: 0, Signer: newAddr.Bytes()}) } // TestEIP1559Transition tests the following: @@ -351,7 +366,7 @@ func TestEIP1559Transition(t *testing.T) { addr3 = crypto.PubkeyToAddress(key3.PublicKey) funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether)) gspec = &core.Genesis{ - Config: params.BorTestChainConfig, + Config: params.BorUnittestChainConfig, Alloc: core.GenesisAlloc{ addr1: {Balance: funds}, addr2: {Balance: funds}, @@ -403,7 +418,7 @@ func TestEIP1559Transition(t *testing.T) { diskdb := rawdb.NewMemoryDatabase() gspec.MustCommit(diskdb) - chain, err := core.NewBlockChain(diskdb, nil, gspec.Config, engine, vm.Config{}, nil, nil) + chain, err := core.NewBlockChain(diskdb, nil, gspec.Config, engine, vm.Config{}, nil, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -433,7 +448,7 @@ func TestEIP1559Transition(t *testing.T) { } // check burnt contract balance - actual = state.GetBalance(common.HexToAddress(params.BorTestChainConfig.Bor.CalculateBurntContract(block.NumberU64()))) + actual = state.GetBalance(common.HexToAddress(params.BorUnittestChainConfig.Bor.CalculateBurntContract(block.NumberU64()))) expected = new(big.Int).Mul(new(big.Int).SetUint64(block.GasUsed()), block.BaseFee()) burntContractBalance := expected if actual.Cmp(expected) != 0 { @@ -481,7 +496,7 @@ func TestEIP1559Transition(t *testing.T) { } // check burnt contract balance - actual = state.GetBalance(common.HexToAddress(params.BorTestChainConfig.Bor.CalculateBurntContract(block.NumberU64()))) + actual = state.GetBalance(common.HexToAddress(params.BorUnittestChainConfig.Bor.CalculateBurntContract(block.NumberU64()))) expected = new(big.Int).Add(burntContractBalance, new(big.Int).Mul(new(big.Int).SetUint64(block.GasUsed()), block.BaseFee())) burntContractBalance = expected if actual.Cmp(expected) != 0 { @@ -539,7 +554,7 @@ func TestEIP1559Transition(t *testing.T) { state, _ = chain.State() // check burnt contract balance - actual = state.GetBalance(common.HexToAddress(params.BorTestChainConfig.Bor.CalculateBurntContract(block.NumberU64()))) + actual = state.GetBalance(common.HexToAddress(params.BorUnittestChainConfig.Bor.CalculateBurntContract(block.NumberU64()))) burntAmount := new(big.Int).Mul( block.BaseFee(), big.NewInt(int64(block.GasUsed())), @@ -550,25 +565,188 @@ func TestEIP1559Transition(t *testing.T) { } } -func newGwei(n int64) *big.Int { - return new(big.Int).Mul(big.NewInt(n), big.NewInt(params.GWei)) +// EIP1559 is not supported without EIP155. An error is expected +func TestEIP1559TransitionWithEIP155(t *testing.T) { + var ( + aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") + + // Generate a canonical chain to act as the main dataset + db = rawdb.NewMemoryDatabase() + engine = ethash.NewFaker() + + // A sender who makes transactions, has some funds + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + key3, _ = crypto.HexToECDSA("225171aed3793cba1c029832886d69785b7e77a54a44211226b447aa2d16b058") + + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + addr2 = crypto.PubkeyToAddress(key2.PublicKey) + addr3 = crypto.PubkeyToAddress(key3.PublicKey) + funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether)) + gspec = &core.Genesis{ + Config: params.BorUnittestChainConfig, + Alloc: core.GenesisAlloc{ + addr1: {Balance: funds}, + addr2: {Balance: funds}, + addr3: {Balance: funds}, + // The address 0xAAAA sloads 0x00 and 0x01 + aa: { + Code: []byte{ + byte(vm.PC), + byte(vm.PC), + byte(vm.SLOAD), + byte(vm.SLOAD), + }, + Nonce: 0, + Balance: big.NewInt(0), + }, + }, + } + ) + + genesis := gspec.MustCommit(db) + + // Use signer without chain ID + signer := types.HomesteadSigner{} + + _, _ = core.GenerateChain(gspec.Config, genesis, engine, db, 1, func(i int, b *core.BlockGen) { + b.SetCoinbase(common.Address{1}) + // One transaction to 0xAAAA + accesses := types.AccessList{types.AccessTuple{ + Address: aa, + StorageKeys: []common.Hash{{0}}, + }} + + txdata := &types.DynamicFeeTx{ + ChainID: gspec.Config.ChainID, + Nonce: 0, + To: &aa, + Gas: 30000, + GasFeeCap: newGwei(5), + GasTipCap: big.NewInt(2), + AccessList: accesses, + Data: []byte{}, + } + + var err error + + tx := types.NewTx(txdata) + tx, err = types.SignTx(tx, signer, key1) + + require.ErrorIs(t, err, types.ErrTxTypeNotSupported) + }) +} + +// it is up to a user to use protected transactions. so if a transaction is unprotected no errors related to chainID are expected. +// transactions are checked in 2 places: transaction pool and blockchain processor. +func TestTransitionWithoutEIP155(t *testing.T) { + var ( + aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") + + // Generate a canonical chain to act as the main dataset + db = rawdb.NewMemoryDatabase() + engine = ethash.NewFaker() + + // A sender who makes transactions, has some funds + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + key3, _ = crypto.HexToECDSA("225171aed3793cba1c029832886d69785b7e77a54a44211226b447aa2d16b058") + + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + addr2 = crypto.PubkeyToAddress(key2.PublicKey) + addr3 = crypto.PubkeyToAddress(key3.PublicKey) + funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether)) + gspec = &core.Genesis{ + Config: params.BorUnittestChainConfig, + Alloc: core.GenesisAlloc{ + addr1: {Balance: funds}, + addr2: {Balance: funds}, + addr3: {Balance: funds}, + // The address 0xAAAA sloads 0x00 and 0x01 + aa: { + Code: []byte{ + byte(vm.PC), + byte(vm.PC), + byte(vm.SLOAD), + byte(vm.SLOAD), + }, + Nonce: 0, + Balance: big.NewInt(0), + }, + }, + } + ) + + genesis := gspec.MustCommit(db) + + // Use signer without chain ID + signer := types.HomesteadSigner{} + //signer := types.FrontierSigner{} + + blocks, _ := core.GenerateChain(gspec.Config, genesis, engine, db, 1, func(i int, b *core.BlockGen) { + b.SetCoinbase(common.Address{1}) + + txdata := &types.LegacyTx{ + Nonce: 0, + To: &aa, + Gas: 30000, + GasPrice: newGwei(5), + } + + var err error + + tx := types.NewTx(txdata) + tx, err = types.SignTx(tx, signer, key1) + + require.Nil(t, err) + require.False(t, tx.Protected()) + + from, err := types.Sender(types.EIP155Signer{}, tx) + require.Equal(t, addr1, from) + require.Nil(t, err) + + b.AddTx(tx) + }) + + diskdb := rawdb.NewMemoryDatabase() + gspec.MustCommit(diskdb) + + chain, err := core.NewBlockChain(diskdb, nil, gspec.Config, engine, vm.Config{}, nil, nil, nil) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + if n, err := chain.InsertChain(blocks); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + + block := chain.GetBlockByNumber(1) + + require.Len(t, block.Transactions(), 1) } func TestJaipurFork(t *testing.T) { init := buildEthereumInstance(t, rawdb.NewMemoryDatabase()) chain := init.ethereum.BlockChain() engine := init.ethereum.Engine() + _bor := engine.(*bor.Bor) + defer _bor.Close() + db := init.ethereum.ChainDb() block := init.genesis.ToBlock(db) + + res, _ := loadSpanFromFile(t) + for i := uint64(1); i < sprintSize; i++ { - block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor) + block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor, nil, res.Result.ValidatorSet.Validators) insertNewBlock(t, chain, block) + if block.Number().Uint64() == init.genesis.Config.Bor.JaipurBlock-1 { - assert.Equal(t, testSealHash(block.Header(), init.genesis.Config.Bor), bor.SealHash(block.Header(), init.genesis.Config.Bor)) + require.Equal(t, testSealHash(block.Header(), init.genesis.Config.Bor), bor.SealHash(block.Header(), init.genesis.Config.Bor)) } + if block.Number().Uint64() == init.genesis.Config.Bor.JaipurBlock { - assert.Equal(t, testSealHash(block.Header(), init.genesis.Config.Bor), bor.SealHash(block.Header(), init.genesis.Config.Bor)) + require.Equal(t, testSealHash(block.Header(), init.genesis.Config.Bor), bor.SealHash(block.Header(), init.genesis.Config.Bor)) } } } diff --git a/tests/bor/helper.go b/tests/bor/helper.go index a8a3ae4ea6..f5d80bbebf 100644 --- a/tests/bor/helper.go +++ b/tests/bor/helper.go @@ -1,46 +1,63 @@ +//go:build integration + package bor import ( "encoding/hex" "encoding/json" + "fmt" "io/ioutil" "math/big" "sort" "testing" + "time" + "github.com/golang/mock/gomock" + + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/bor" - "github.com/ethereum/go-ethereum/consensus/bor/heimdall" + "github.com/ethereum/go-ethereum/consensus/bor/clerk" + "github.com/ethereum/go-ethereum/consensus/bor/heimdall" //nolint:typecheck "github.com/ethereum/go-ethereum/consensus/bor/heimdall/span" "github.com/ethereum/go-ethereum/consensus/bor/valset" "github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/secp256k1" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/tests/bor/mocks" ) var ( + + // Only this account is a validator for the 0th span + key, _ = crypto.HexToECDSA(privKey) + addr = crypto.PubkeyToAddress(key.PublicKey) // 0x71562b71999873DB5b286dF957af199Ec94617F7 + + // This account is one the validators for 1st span (0-indexed) + key2, _ = crypto.HexToECDSA(privKey2) + addr2 = crypto.PubkeyToAddress(key2.PublicKey) // 0x9fB29AAc15b9A4B7F17c3385939b007540f4d791 +) + +const ( + privKey = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291" + privKey2 = "9b28f36fbd67381120752d6172ecdcf10e06ab2d9a1367aac00cdcd6ac7855d3" + // The genesis for tests was generated with following parameters extraSeal = 65 // Fixed number of extra-data suffix bytes reserved for signer seal - // Only this account is a validator for the 0th span - privKey = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291" - key, _ = crypto.HexToECDSA(privKey) - addr = crypto.PubkeyToAddress(key.PublicKey) // 0x71562b71999873DB5b286dF957af199Ec94617F7 + sprintSize uint64 = 4 + spanSize uint64 = 8 - // This account is one the validators for 1st span (0-indexed) - privKey2 = "9b28f36fbd67381120752d6172ecdcf10e06ab2d9a1367aac00cdcd6ac7855d3" - key2, _ = crypto.HexToECDSA(privKey2) - addr2 = crypto.PubkeyToAddress(key2.PublicKey) // 0x9fB29AAc15b9A4B7F17c3385939b007540f4d791 - - validatorHeaderBytesLength = common.AddressLength + 20 // address + power - sprintSize uint64 = 4 - spanSize uint64 = 8 + validatorHeaderBytesLength = common.AddressLength + 20 // address + power ) type initializeData struct { @@ -49,8 +66,6 @@ type initializeData struct { } func buildEthereumInstance(t *testing.T, db ethdb.Database) *initializeData { - t.Helper() - genesisData, err := ioutil.ReadFile("./testdata/genesis.json") if err != nil { t.Fatalf("%s", err) @@ -64,6 +79,7 @@ func buildEthereumInstance(t *testing.T, db ethdb.Database) *initializeData { ethConf := ð.Config{ Genesis: gen, + BorLogs: true, } ethConf.Genesis.MustCommit(db) @@ -75,6 +91,10 @@ func buildEthereumInstance(t *testing.T, db ethdb.Database) *initializeData { ethConf.Genesis.MustCommit(ethereum.ChainDb()) + ethereum.Engine().(*bor.Bor).Authorize(addr, func(account accounts.Account, s string, data []byte) ([]byte, error) { + return crypto.Sign(crypto.Keccak256(data), key) + }) + return &initializeData{ genesis: gen, ethereum: ethereum, @@ -89,33 +109,31 @@ func insertNewBlock(t *testing.T, chain *core.BlockChain, block *types.Block) { } } -func buildNextBlock(t *testing.T, _bor *bor.Bor, chain *core.BlockChain, block *types.Block, signer []byte, borConfig *params.BorConfig) *types.Block { +type Option func(header *types.Header) + +func buildNextBlock(t *testing.T, _bor consensus.Engine, chain *core.BlockChain, parentBlock *types.Block, signer []byte, borConfig *params.BorConfig, txs []*types.Transaction, currentValidators []*valset.Validator, opts ...Option) *types.Block { t.Helper() - header := block.Header() - header.Number.Add(header.Number, big.NewInt(1)) + header := &types.Header{ + Number: big.NewInt(int64(parentBlock.Number().Uint64() + 1)), + Difficulty: big.NewInt(int64(parentBlock.Difficulty().Uint64())), + GasLimit: parentBlock.GasLimit(), + ParentHash: parentBlock.Hash(), + } number := header.Number.Uint64() if signer == nil { signer = getSignerKey(header.Number.Uint64()) } - header.ParentHash = block.Hash() - header.Time += bor.CalcProducerDelay(header.Number.Uint64(), 0, borConfig) + header.Time = parentBlock.Time() + bor.CalcProducerDelay(header.Number.Uint64(), 0, borConfig) header.Extra = make([]byte, 32+65) // vanity + extraSeal - currentValidators := []*valset.Validator{valset.NewValidator(addr, 10)} + isSpanStart := IsSpanStart(number) + isSprintEnd := IsSprintEnd(number) - isSpanEnd := (number+1)%spanSize == 0 - isSpanStart := number%spanSize == 0 - isSprintEnd := (header.Number.Uint64()+1)%sprintSize == 0 - - if isSpanEnd { - _, heimdallSpan := loadSpanFromFile(t) - // this is to stash the validator bytes in the header - currentValidators = heimdallSpan.ValidatorSet.Validators - } else if isSpanStart { - header.Difficulty = new(big.Int).SetInt64(3) + if isSpanStart { + header.Difficulty = new(big.Int).SetInt64(int64(len(currentValidators))) } if isSprintEnd { @@ -132,27 +150,87 @@ func buildNextBlock(t *testing.T, _bor *bor.Bor, chain *core.BlockChain, block * } if chain.Config().IsLondon(header.Number) { - header.BaseFee = misc.CalcBaseFee(chain.Config(), block.Header()) + header.BaseFee = misc.CalcBaseFee(chain.Config(), parentBlock.Header()) - if !chain.Config().IsLondon(block.Number()) { - parentGasLimit := block.GasLimit() * params.ElasticityMultiplier + if !chain.Config().IsLondon(parentBlock.Number()) { + parentGasLimit := parentBlock.GasLimit() * params.ElasticityMultiplier header.GasLimit = core.CalcGasLimit(parentGasLimit, parentGasLimit) } } + for _, opt := range opts { + opt(header) + } + state, err := chain.State() if err != nil { t.Fatalf("%s", err) } - _, err = _bor.FinalizeAndAssemble(chain, header, state, nil, nil, nil) - if err != nil { - t.Fatalf("%s", err) + b := &blockGen{header: header} + for _, tx := range txs { + b.addTxWithChain(chain, state, tx, addr) } - sign(t, header, signer, borConfig) + // Finalize and seal the block + block, _ := _bor.FinalizeAndAssemble(chain, b.header, state, b.txs, nil, b.receipts) - return types.NewBlockWithHeader(header) + // Write state changes to db + root, err := state.Commit(chain.Config().IsEIP158(b.header.Number)) + if err != nil { + panic(fmt.Sprintf("state write error: %v", err)) + } + + if err := state.Database().TrieDB().Commit(root, false, nil); err != nil { + panic(fmt.Sprintf("trie write error: %v", err)) + } + + res := make(chan *types.Block, 1) + + err = _bor.Seal(chain, block, res, nil) + if err != nil { + // an error case - sign manually + sign(t, header, signer, borConfig) + return types.NewBlockWithHeader(header) + } + + return <-res +} + +type blockGen struct { + txs []*types.Transaction + receipts []*types.Receipt + gasPool *core.GasPool + header *types.Header +} + +func (b *blockGen) addTxWithChain(bc *core.BlockChain, statedb *state.StateDB, tx *types.Transaction, coinbase common.Address) { + if b.gasPool == nil { + b.setCoinbase(coinbase) + } + + statedb.Prepare(tx.Hash(), len(b.txs)) + + receipt, err := core.ApplyTransaction(bc.Config(), bc, &b.header.Coinbase, b.gasPool, statedb, b.header, tx, &b.header.GasUsed, vm.Config{}) + if err != nil { + panic(err) + } + + b.txs = append(b.txs, tx) + b.receipts = append(b.receipts, receipt) +} + +func (b *blockGen) setCoinbase(addr common.Address) { + if b.gasPool != nil { + if len(b.txs) > 0 { + panic("coinbase must be set before adding transactions") + } + + panic("coinbase can only be set once") + } + + b.header.Coinbase = addr + b.gasPool = new(core.GasPool).AddGas(b.header.GasLimit) } func sign(t *testing.T, header *types.Header, signer []byte, c *params.BorConfig) { @@ -204,13 +282,80 @@ func loadSpanFromFile(t *testing.T) (*heimdall.SpanResponse, *span.HeimdallSpan) func getSignerKey(number uint64) []byte { signerKey := privKey - isSpanStart := number%spanSize == 0 - if isSpanStart { + if IsSpanStart(number) { // validator set in the new span has changed signerKey = privKey2 } - _key, _ := hex.DecodeString(signerKey) + newKey, _ := hex.DecodeString(signerKey) - return _key + return newKey +} + +func getMockedHeimdallClient(t *testing.T, heimdallSpan *span.HeimdallSpan) (*mocks.MockIHeimdallClient, *gomock.Controller) { + t.Helper() + + ctrl := gomock.NewController(t) + h := mocks.NewMockIHeimdallClient(ctrl) + + h.EXPECT().Span(gomock.Any(), uint64(1)).Return(heimdallSpan, nil).AnyTimes() + + h.EXPECT().StateSyncEvents(gomock.Any(), gomock.Any(), gomock.Any()). + Return([]*clerk.EventRecordWithTime{getSampleEventRecord(t)}, nil).AnyTimes() + + return h, ctrl +} + +func generateFakeStateSyncEvents(sample *clerk.EventRecordWithTime, count int) []*clerk.EventRecordWithTime { + events := make([]*clerk.EventRecordWithTime, count) + event := *sample + event.ID = 1 + events[0] = &clerk.EventRecordWithTime{} + *events[0] = event + + for i := 1; i < count; i++ { + event.ID = uint64(i) + event.Time = event.Time.Add(1 * time.Second) + events[i] = &clerk.EventRecordWithTime{} + *events[i] = event + } + + return events +} + +func buildStateEvent(sample *clerk.EventRecordWithTime, id uint64, timeStamp int64) *clerk.EventRecordWithTime { + event := *sample + event.ID = id + event.Time = time.Unix(timeStamp, 0) + + return &event +} + +func getSampleEventRecord(t *testing.T) *clerk.EventRecordWithTime { + t.Helper() + + eventRecords := stateSyncEventsPayload(t) + eventRecords.Result[0].Time = time.Unix(1, 0) + + return eventRecords.Result[0] +} + +func newGwei(n int64) *big.Int { + return new(big.Int).Mul(big.NewInt(n), big.NewInt(params.GWei)) +} + +func IsSpanEnd(number uint64) bool { + return (number+1)%spanSize == 0 +} + +func IsSpanStart(number uint64) bool { + return number%spanSize == 0 +} + +func IsSprintStart(number uint64) bool { + return number%sprintSize == 0 +} + +func IsSprintEnd(number uint64) bool { + return (number+1)%sprintSize == 0 } diff --git a/tests/bor/mocks/IHeimdallClient.go b/tests/bor/mocks/IHeimdallClient.go index 15e6a70990..15a09712f1 100644 --- a/tests/bor/mocks/IHeimdallClient.go +++ b/tests/bor/mocks/IHeimdallClient.go @@ -49,19 +49,34 @@ func (mr *MockIHeimdallClientMockRecorder) Close() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockIHeimdallClient)(nil).Close)) } -// FetchLatestCheckpoint mocks base method. -func (m *MockIHeimdallClient) FetchLatestCheckpoint(arg0 context.Context) (*checkpoint.Checkpoint, error) { +// FetchCheckpoint mocks base method. +func (m *MockIHeimdallClient) FetchCheckpoint(arg0 context.Context, arg1 int64) (*checkpoint.Checkpoint, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "FetchLatestCheckpoint", arg0) + ret := m.ctrl.Call(m, "FetchCheckpoint", arg0, arg1) ret0, _ := ret[0].(*checkpoint.Checkpoint) ret1, _ := ret[1].(error) return ret0, ret1 } -// FetchLatestCheckpoint indicates an expected call of FetchLatestCheckpoint. -func (mr *MockIHeimdallClientMockRecorder) FetchLatestCheckpoint(arg0 interface{}) *gomock.Call { +// FetchCheckpoint indicates an expected call of FetchCheckpoint. +func (mr *MockIHeimdallClientMockRecorder) FetchCheckpoint(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchLatestCheckpoint", reflect.TypeOf((*MockIHeimdallClient)(nil).FetchLatestCheckpoint), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchCheckpoint", reflect.TypeOf((*MockIHeimdallClient)(nil).FetchCheckpoint), arg0, arg1) +} + +// FetchCheckpointCount mocks base method. +func (m *MockIHeimdallClient) FetchCheckpointCount(arg0 context.Context) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FetchCheckpointCount", arg0) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FetchCheckpointCount indicates an expected call of FetchCheckpointCount. +func (mr *MockIHeimdallClientMockRecorder) FetchCheckpointCount(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchCheckpointCount", reflect.TypeOf((*MockIHeimdallClient)(nil).FetchCheckpointCount), arg0) } // Span mocks base method. diff --git a/tests/bor/testdata/genesis.json b/tests/bor/testdata/genesis.json index b5767b3ed3..f096ad1cec 100644 --- a/tests/bor/testdata/genesis.json +++ b/tests/bor/testdata/genesis.json @@ -52,6 +52,9 @@ }, "71562b71999873DB5b286dF957af199Ec94617F7": { "balance": "0x3635c9adc5dea00000" + }, + "9fB29AAc15b9A4B7F17c3385939b007540f4d791": { + "balance": "0x3635c9adc5dea00000" } }, "number": "0x0", diff --git a/tests/fuzzers/les/les-fuzzer.go b/tests/fuzzers/les/les-fuzzer.go index 3e10171873..c379d1f08d 100644 --- a/tests/fuzzers/les/les-fuzzer.go +++ b/tests/fuzzers/les/les-fuzzer.go @@ -80,7 +80,7 @@ func makechain() (bc *core.BlockChain, addrHashes, txHashes []common.Hash) { addrHashes = append(addrHashes, crypto.Keccak256Hash(addr[:])) txHashes = append(txHashes, tx.Hash()) }) - bc, _ = core.NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + bc, _ = core.NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil) if _, err := bc.InsertChain(blocks); err != nil { panic(err) } diff --git a/tests/fuzzers/snap/fuzz_handler.go b/tests/fuzzers/snap/fuzz_handler.go index 1ae61df29d..d58221b500 100644 --- a/tests/fuzzers/snap/fuzz_handler.go +++ b/tests/fuzzers/snap/fuzz_handler.go @@ -79,7 +79,7 @@ func getChain() *core.BlockChain { SnapshotWait: true, } trieRoot = blocks[len(blocks)-1].Root() - bc, _ := core.NewBlockChain(db, cacheConf, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + bc, _ := core.NewBlockChain(db, cacheConf, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil) if _, err := bc.InsertChain(blocks); err != nil { panic(err) }