From 7b2599783006eed7291d1ac693624f6f4cee8704 Mon Sep 17 00:00:00 2001 From: Arpit Temani Date: Thu, 14 Jul 2022 21:58:56 +0530 Subject: [PATCH] Reciept e2e test (#431) * initial * fixed tests * eip155 tests * progress * fix * fix TestFetchStateSyncEvents* * debug * fixed with updated list of validators * fix * a bit more stable * optimize allocations * linters * fix vals copy * a bit of refactoring * update TestGetTransactionReceiptsByBlock * remove logs from TestGetTransactionReceiptsByBlock in favour of checks * comments * Linters * linters * fixes after merge * fixes after merge * fixes after merge * fixes after merge * fail fast Co-authored-by: Evgeny Danienko <6655321@bk.ru> --- cmd/utils/bor_flags.go | 3 +- consensus/bor/bor.go | 34 +- consensus/bor/bor_test.go | 32 +- consensus/bor/contract/client.go | 3 +- consensus/bor/heimdall/client.go | 4 +- consensus/bor/heimdall/span/spanner.go | 2 +- consensus/bor/merkle.go | 2 +- consensus/bor/snapshot.go | 12 +- consensus/bor/snapshot_test.go | 22 +- consensus/bor/valset/validator.go | 29 +- consensus/bor/valset/validator_set.go | 66 ++-- consensus/bor/valset/validator_set_test.go | 49 +-- core/rawdb/bor_receipt.go | 6 +- core/state/statedb.go | 4 +- eth/downloader/whitelist/service_test.go | 24 +- eth/filters/bor_api.go | 3 +- internal/cli/bootnode.go | 3 +- internal/cli/debug_test.go | 12 +- internal/cli/markdown_test.go | 4 +- internal/cli/server/chains/chain.go | 3 +- internal/cli/server/helper.go | 3 +- internal/ethapi/api.go | 11 +- tests/bor/bor_api_test.go | 168 ++++++++++ tests/bor/bor_test.go | 370 +++++++++++++++------ tests/bor/helper.go | 229 ++++++++++--- tests/bor/testdata/genesis.json | 3 + 26 files changed, 820 insertions(+), 281 deletions(-) create mode 100644 tests/bor/bor_api_test.go 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/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..34a8df15b0 100644 --- a/consensus/bor/bor_test.go +++ b/consensus/bor/bor_test.go @@ -4,7 +4,7 @@ import ( "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" @@ -55,11 +55,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) + require.NoError(t, err) addBlock := func(root common.Hash, num int64) (common.Hash, *state.StateDB) { h := &types.Header{ @@ -70,37 +70,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 +124,19 @@ 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) } 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/client.go b/consensus/bor/heimdall/client.go index 64db4a6293..e66513c983 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" @@ -251,7 +251,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 index d48fd3d626..9397ca3e92 100644 --- a/consensus/bor/valset/validator_set_test.go +++ b/consensus/bor/valset/validator_set_test.go @@ -3,14 +3,16 @@ package valset import ( "testing" + "github.com/stretchr/testify/require" + "gotest.tools/assert" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - - "gotest.tools/assert" ) func NewValidatorFromKey(key string, votingPower int64) *Validator { privKey, _ := crypto.HexToECDSA(key) + return NewValidator(crypto.PubkeyToAddress(privKey.PublicKey), votingPower) } @@ -50,7 +52,7 @@ func TestIncrementProposerPriority(t *testing.T) { for i := 0; i < 10; i++ { valSet.IncrementProposerPriority(1) - assert.Equal(t, expectedPropsers[i].Address, valSet.GetProposer().Address) + require.Equal(t, expectedPropsers[i].Address, valSet.GetProposer().Address) } // Validator set length = 2 @@ -61,7 +63,7 @@ func TestIncrementProposerPriority(t *testing.T) { for i := 0; i < 10; i++ { valSet.IncrementProposerPriority(1) - assert.Equal(t, expectedPropsers[i].Address, valSet.GetProposer().Address) + require.Equal(t, expectedPropsers[i].Address, valSet.GetProposer().Address) } // Validator set length = 3 @@ -72,7 +74,7 @@ func TestIncrementProposerPriority(t *testing.T) { for i := 0; i < 10; i++ { valSet.IncrementProposerPriority(1) - assert.Equal(t, expectedPropsers[i].Address, valSet.GetProposer().Address) + require.Equal(t, expectedPropsers[i].Address, valSet.GetProposer().Address) } // Validator set length = 4 @@ -83,7 +85,7 @@ func TestIncrementProposerPriority(t *testing.T) { for i := 0; i < 10; i++ { valSet.IncrementProposerPriority(1) - assert.Equal(t, expectedPropsers[i].Address, valSet.GetProposer().Address) + require.Equal(t, expectedPropsers[i].Address, valSet.GetProposer().Address) } } @@ -99,7 +101,7 @@ func TestRescalePriorities(t *testing.T) { expectedPriorities := []int64{0} for i, val := range valSet.Validators { - assert.Equal(t, expectedPriorities[i], val.ProposerPriority) + require.Equal(t, expectedPriorities[i], val.ProposerPriority) } // Validator set length = 2 @@ -110,7 +112,7 @@ func TestRescalePriorities(t *testing.T) { expectedPriorities = []int64{50, -50} for i, val := range valSet.Validators { - assert.Equal(t, expectedPriorities[i], val.ProposerPriority) + require.Equal(t, expectedPriorities[i], val.ProposerPriority) } // Validator set length = 3 @@ -121,7 +123,7 @@ func TestRescalePriorities(t *testing.T) { expectedPriorities = []int64{-17, 5, 11} for i, val := range valSet.Validators { - assert.Equal(t, expectedPriorities[i], val.ProposerPriority) + require.Equal(t, expectedPriorities[i], val.ProposerPriority) } // Validator set length = 4 @@ -132,7 +134,7 @@ func TestRescalePriorities(t *testing.T) { expectedPriorities = []int64{-6, 3, 1, 2} for i, val := range valSet.Validators { - assert.Equal(t, expectedPriorities[i], val.ProposerPriority) + require.Equal(t, expectedPriorities[i], val.ProposerPriority) } } @@ -148,18 +150,18 @@ func TestGetValidatorByAddressAndIndex(t *testing.T) { assert.DeepEqual(t, val, valByIndex) assert.DeepEqual(t, val, valByAddress) - assert.DeepEqual(t, val.Address, common.BytesToAddress(addr)) + assert.DeepEqual(t, val.Address, addr) } tempAddress := common.HexToAddress("0x12345") // Negative Testcase idx, _ := valSet.GetByAddress(tempAddress) - assert.Equal(t, idx, -1) + require.Equal(t, idx, -1) // checking for validator index out of range addr, _ := valSet.GetByIndex(100) - assert.Equal(t, common.BytesToAddress(addr), common.Address{}) + require.Equal(t, addr, common.Address{}) } func TestUpdateWithChangeSet(t *testing.T) { @@ -169,28 +171,29 @@ func TestUpdateWithChangeSet(t *testing.T) { valSet := NewValidatorSet(vals[:4]) // halved the power of vals[2] and doubled the power of vals[3] - val2 := NewValidatorFromKey("c8deb0bea5c41afe8e37b4d1bd84e31adff11b09c8c96ff4b605003cce067cd7", 150) // signer2 - val3 := NewValidatorFromKey("c8deb0bea5c41afe8e37b4d1bd84e31adff11b09c8c96ff4b605003cce067cd6", 800) // signer3 + vals[2].VotingPower = 150 + vals[3].VotingPower = 800 // Adding new temp validator in the set - tempSigner := "c8deb0bea5c41afe8e37b4d1bd84e31adff11b09c8c96ff4b605003cce067cd5" + const tempSigner = "c8deb0bea5c41afe8e37b4d1bd84e31adff11b09c8c96ff4b605003cce067cd5" + tempVal := NewValidatorFromKey(tempSigner, 250) // check totalVotingPower before updating validator set - assert.Equal(t, int64(1000), valSet.TotalVotingPower()) + require.Equal(t, int64(1000), valSet.TotalVotingPower()) - err := valSet.UpdateWithChangeSet([]*Validator{val2, val3, tempVal}) - assert.NilError(t, err) + err := valSet.UpdateWithChangeSet([]*Validator{vals[2], vals[3], tempVal}) + require.NoError(t, err) // check totalVotingPower after updating validator set - assert.Equal(t, int64(1500), valSet.TotalVotingPower()) + require.Equal(t, int64(1500), valSet.TotalVotingPower()) _, updatedVal2 := valSet.GetByAddress(vals[2].Address) - assert.Equal(t, int64(150), updatedVal2.VotingPower) + require.Equal(t, int64(150), updatedVal2.VotingPower) _, updatedVal3 := valSet.GetByAddress(vals[3].Address) - assert.Equal(t, int64(800), updatedVal3.VotingPower) + require.Equal(t, int64(800), updatedVal3.VotingPower) _, updatedTempVal := valSet.GetByAddress(tempVal.Address) - assert.Equal(t, int64(250), updatedTempVal.VotingPower) + require.Equal(t, int64(250), updatedTempVal.VotingPower) } 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/eth/downloader/whitelist/service_test.go b/eth/downloader/whitelist/service_test.go index ca202cc3ad..01822c85dd 100644 --- a/eth/downloader/whitelist/service_test.go +++ b/eth/downloader/whitelist/service_test.go @@ -5,7 +5,7 @@ import ( "math/big" "testing" - "gotest.tools/assert" + "github.com/stretchr/testify/require" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -28,11 +28,11 @@ func TestWhitelistCheckpoint(t *testing.T) { 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 @@ -44,14 +44,14 @@ func TestIsValidChain(t *testing.T) { // 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") + 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) { @@ -69,7 +69,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 @@ -92,16 +92,16 @@ 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") + 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") + require.Equal(t, err, ErrCheckpointMismatch, "expected checkpoint mismatch error") + require.Equal(t, res, false, "expected chain to be invalid") } 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/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/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/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/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/tests/bor/bor_api_test.go b/tests/bor/bor_api_test.go new file mode 100644 index 0000000000..4b35645e1b --- /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().FetchLatestCheckpoint(gomock.Any()).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..811db035e0 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,11 +39,11 @@ 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{ Proposer: currentSpan.SelectedProducers[0].Address, @@ -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}, @@ -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) + 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..bdddb34b6d 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/clerk" "github.com/ethereum/go-ethereum/consensus/bor/heimdall" "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/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",