//go:build integration package bor import ( "crypto/ecdsa" "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/accounts/keystore" "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" //nolint:typecheck "github.com/ethereum/go-ethereum/consensus/bor/heimdall/checkpoint" "github.com/ethereum/go-ethereum/consensus/bor/heimdall/milestone" "github.com/ethereum/go-ethereum/consensus/bor/heimdall/span" "github.com/ethereum/go-ethereum/consensus/bor/valset" "github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/txpool/legacypool" "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/eth/downloader" "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/tests/bor/mocks" "github.com/ethereum/go-ethereum/triedb" ) 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 keys = []*ecdsa.PrivateKey{key, key2} ) 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 sprintSize uint64 = 4 spanSize uint64 = 8 validatorHeaderBytesLength = common.AddressLength + 20 // address + power ) type initializeData struct { genesis *core.Genesis ethereum *eth.Ethereum } func setupMiner(t *testing.T, n int, genesis *core.Genesis) ([]*node.Node, []*eth.Ethereum, []*enode.Node) { t.Helper() // Create an Ethash network based off of the Ropsten config var ( stacks []*node.Node nodes []*eth.Ethereum enodes []*enode.Node ) for i := 0; i < n; i++ { // Start the node and wait until it's up stack, ethBackend, err := InitMiner(genesis, keys[i], true) if err != nil { t.Fatal("Error occurred while initialising miner", "error", err) } for stack.Server().NodeInfo().Ports.Listener == 0 { time.Sleep(250 * time.Millisecond) } // Connect the node to all the previous ones for _, n := range enodes { stack.Server().AddPeer(n) } // Start tracking the node and its enode stacks = append(stacks, stack) nodes = append(nodes, ethBackend) enodes = append(enodes, stack.Server().Self()) } return stacks, nodes, enodes } func buildEthereumInstance(t *testing.T, db ethdb.Database) *initializeData { genesisData, err := ioutil.ReadFile("./testdata/genesis.json") if err != nil { t.Fatalf("%s", err) } gen := &core.Genesis{} if err := json.Unmarshal(genesisData, gen); err != nil { t.Fatalf("%s", err) } ethConf := ð.Config{ Genesis: gen, BorLogs: true, StateScheme: "hash", } ethConf.Genesis.MustCommit(db, triedb.NewDatabase(db, triedb.HashDefaults)) ethereum := utils.CreateBorEthereum(ethConf) if err != nil { t.Fatalf("failed to register Ethereum protocol: %v", err) } ethConf.Genesis.MustCommit(ethereum.ChainDb(), triedb.NewDatabase(ethereum.ChainDb(), triedb.HashDefaults)) 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, } } func insertNewBlock(t *testing.T, chain *core.BlockChain, block *types.Block) { t.Helper() if _, err := chain.InsertChain([]*types.Block{block}); err != nil { t.Fatalf("%s", err) } } 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 := &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.Time = parentBlock.Time() + bor.CalcProducerDelay(header.Number.Uint64(), 0, borConfig) header.Extra = make([]byte, 32+65) // vanity + extraSeal isSpanStart := IsSpanStart(number) isSprintEnd := IsSprintEnd(number) if isSpanStart { header.Difficulty = new(big.Int).SetInt64(int64(len(currentValidators))) } if isSprintEnd { sort.Sort(valset.ValidatorsByAddress(currentValidators)) validatorBytes := make([]byte, len(currentValidators)*validatorHeaderBytesLength) header.Extra = make([]byte, 32+len(validatorBytes)+65) // vanity + validatorBytes + extraSeal for i, val := range currentValidators { copy(validatorBytes[i*validatorHeaderBytesLength:], val.HeaderBytes()) } copy(header.Extra[32:], validatorBytes) } if chain.Config().IsLondon(header.Number) { header.BaseFee = eip1559.CalcBaseFee(chain.Config(), parentBlock.Header()) 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) } b := &blockGen{header: header} for _, tx := range txs { b.addTxWithChain(chain, state, tx, addr) } // Finalize and seal the block block, err := _bor.FinalizeAndAssemble(chain, b.header, state, &types.Body{ Transactions: b.txs, }, b.receipts) if err != nil { panic(fmt.Sprintf("error finalizing block: %v", err)) } // Write state changes to db root, err := state.Commit(block.NumberU64(), chain.Config().IsEIP158(b.header.Number), false) if err != nil { panic(fmt.Sprintf("state write error: %v", err)) } if err := state.Database().TrieDB().Commit(root, false); 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.SetTxContext(tx.Hash(), len(b.txs)) context := core.NewEVMBlockContext(b.header, bc, nil) evm := vm.NewEVM(context, statedb, bc.Config(), vm.Config{}) receipt, err := core.ApplyTransaction(evm, b.gasPool, statedb, b.header, tx, &b.header.GasUsed, nil) 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) { t.Helper() sig, err := secp256k1.Sign(crypto.Keccak256(bor.BorRLP(header, c)), signer) if err != nil { t.Fatalf("%s", err) } copy(header.Extra[len(header.Extra)-extraSeal:], sig) } //nolint:unused,deadcode func stateSyncEventsPayload(t *testing.T) *heimdall.StateSyncEventsResponse { t.Helper() stateData, err := ioutil.ReadFile("./testdata/states.json") if err != nil { t.Fatalf("%s", err) } res := &heimdall.StateSyncEventsResponse{} if err := json.Unmarshal(stateData, res); err != nil { t.Fatalf("%s", err) } return res } //nolint:unused,deadcode func loadSpanFromFile(t *testing.T) (*heimdall.SpanResponse, *span.HeimdallSpan) { t.Helper() spanData, err := ioutil.ReadFile("./testdata/span.json") if err != nil { t.Fatalf("%s", err) } res := &heimdall.SpanResponse{} if err := json.Unmarshal(spanData, res); err != nil { t.Fatalf("%s", err) } return res, &res.Result } func getSignerKey(number uint64) []byte { signerKey := privKey if IsSpanStart(number) { // validator set in the new span has changed signerKey = privKey2 } newKey, _ := hex.DecodeString(signerKey) 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 createMockSpan(address common.Address, chainId string) span.HeimdallSpan { // Mock span 0 for heimdall calls validator := valset.Validator{ ID: 0, Address: address, VotingPower: 10, ProposerPriority: 0, } validatorSet := valset.ValidatorSet{ Validators: []*valset.Validator{&validator}, Proposer: &validator, } span0 := span.HeimdallSpan{ Span: span.Span{ ID: 0, StartBlock: 0, EndBlock: 255, }, ValidatorSet: validatorSet, SelectedProducers: []valset.Validator{validator}, ChainID: chainId, } return span0 } func createMockHeimdall(ctrl *gomock.Controller, span0, span1 *span.HeimdallSpan) *mocks.MockIHeimdallClient { h := mocks.NewMockIHeimdallClient(ctrl) h.EXPECT().Close().AnyTimes() h.EXPECT().Span(gomock.Any(), uint64(0)).Return(span0, nil).AnyTimes() h.EXPECT().Span(gomock.Any(), uint64(1)).Return(span1, nil).AnyTimes() h.EXPECT().FetchCheckpoint(gomock.Any(), int64(-1)).Return(&checkpoint.Checkpoint{}, nil).AnyTimes() h.EXPECT().FetchMilestone(gomock.Any()).Return(&milestone.Milestone{}, nil).AnyTimes() h.EXPECT().FetchLastNoAckMilestone(gomock.Any()).Return("", nil).AnyTimes() h.EXPECT().FetchNoAckMilestone(gomock.Any(), string("test")).Return(nil).AnyTimes() return h } func getMockedSpanner(t *testing.T, validators []*valset.Validator) *bor.MockSpanner { t.Helper() spanner := bor.NewMockSpanner(gomock.NewController(t)) spanner.EXPECT().GetCurrentValidatorsByHash(gomock.Any(), gomock.Any(), gomock.Any()).Return(validators, nil).AnyTimes() spanner.EXPECT().GetCurrentValidatorsByBlockNrOrHash(gomock.Any(), gomock.Any(), gomock.Any()).Return(validators, nil).AnyTimes() spanner.EXPECT().GetCurrentSpan(gomock.Any(), gomock.Any()).Return(&span.Span{0, 0, 0}, nil).AnyTimes() spanner.EXPECT().CommitSpan(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() return spanner } 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 + 1) 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 } func InitGenesis(t *testing.T, faucets []*ecdsa.PrivateKey, fileLocation string, sprintSize uint64) *core.Genesis { t.Helper() // sprint size = 8 in genesis genesisData, err := ioutil.ReadFile(fileLocation) if err != nil { t.Fatalf("%s", err) } genesis := &core.Genesis{} if err := json.Unmarshal(genesisData, genesis); err != nil { t.Fatalf("%s", err) } genesis.Config.ChainID = big.NewInt(15001) genesis.Config.EIP150Block = big.NewInt(0) genesis.Config.Bor.Sprint["0"] = sprintSize return genesis } func InitMiner(genesis *core.Genesis, privKey *ecdsa.PrivateKey, withoutHeimdall bool) (*node.Node, *eth.Ethereum, error) { // Define the basic configurations for the Ethereum node datadir, _ := ioutil.TempDir("", "") config := &node.Config{ Name: "geth", Version: params.Version, DataDir: datadir, P2P: p2p.Config{ ListenAddr: "0.0.0.0:0", NoDiscovery: true, MaxPeers: 25, }, UseLightweightKDF: true, } // Create the node and configure a full Ethereum node on it stack, err := node.New(config) if err != nil { return nil, nil, err } ethBackend, err := eth.New(stack, ðconfig.Config{ Genesis: genesis, NetworkId: genesis.Config.ChainID.Uint64(), SyncMode: downloader.FullSync, DatabaseCache: 256, DatabaseHandles: 256, TxPool: legacypool.DefaultConfig, GPO: ethconfig.Defaults.GPO, Miner: miner.Config{ Etherbase: crypto.PubkeyToAddress(privKey.PublicKey), GasCeil: genesis.GasLimit * 11 / 10, GasPrice: big.NewInt(1), Recommit: time.Second, }, WithoutHeimdall: withoutHeimdall, }) if err != nil { return nil, nil, err } // register backend to account manager with keystore for signing keydir := stack.KeyStoreDir() n, p := keystore.StandardScryptN, keystore.StandardScryptP kStore := keystore.NewKeyStore(keydir, n, p) _, err = kStore.ImportECDSA(privKey, "") if err != nil { return nil, nil, err } acc := kStore.Accounts()[0] err = kStore.Unlock(acc, "") if err != nil { return nil, nil, err } // proceed to authorize the local account manager in any case ethBackend.AccountManager().AddBackend(kStore) err = stack.Start() return stack, ethBackend, err }