mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-25 14:16:44 +00:00
1148 lines
35 KiB
Go
1148 lines
35 KiB
Go
//go:build integration
|
|
// +build integration
|
|
|
|
package bor
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ecdsa"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io"
|
|
"math/big"
|
|
"os"
|
|
"sync"
|
|
"testing"
|
|
"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/common/fdlimit"
|
|
"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/consensus/ethash"
|
|
"github.com/ethereum/go-ethereum/core"
|
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
"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/eth"
|
|
"github.com/ethereum/go-ethereum/log"
|
|
"github.com/ethereum/go-ethereum/node"
|
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
|
"github.com/ethereum/go-ethereum/params"
|
|
"github.com/ethereum/go-ethereum/rlp"
|
|
"github.com/ethereum/go-ethereum/tests/bor/mocks"
|
|
)
|
|
|
|
var (
|
|
// addr1 = 0x71562b71999873DB5b286dF957af199Ec94617F7
|
|
pkey1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
|
// addr2 = 0x9fB29AAc15b9A4B7F17c3385939b007540f4d791
|
|
pkey2, _ = crypto.HexToECDSA("9b28f36fbd67381120752d6172ecdcf10e06ab2d9a1367aac00cdcd6ac7855d3")
|
|
)
|
|
|
|
func TestValidatorWentOffline(t *testing.T) {
|
|
|
|
log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
|
|
fdlimit.Raise(2048)
|
|
|
|
// Generate a batch of accounts to seal and fund with
|
|
faucets := make([]*ecdsa.PrivateKey, 128)
|
|
for i := 0; i < len(faucets); i++ {
|
|
faucets[i], _ = crypto.GenerateKey()
|
|
}
|
|
|
|
// Create an Ethash network based off of the Ropsten config
|
|
// Generate a batch of accounts to seal and fund with
|
|
genesis := InitGenesis(t, faucets, "./testdata/genesis_2val.json", 8)
|
|
|
|
var (
|
|
stacks []*node.Node
|
|
nodes []*eth.Ethereum
|
|
enodes []*enode.Node
|
|
)
|
|
for i := 0; i < 2; i++ {
|
|
// Start the node and wait until it's up
|
|
stack, ethBackend, err := InitMiner(genesis, keys[i], true)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer stack.Close()
|
|
|
|
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())
|
|
}
|
|
|
|
// Iterate over all the nodes and start mining
|
|
time.Sleep(3 * time.Second)
|
|
for _, node := range nodes {
|
|
if err := node.StartMining(1); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
for {
|
|
|
|
// for block 1 to 8, the primary validator is node0
|
|
// for block 9 to 16, the primary validator is node1
|
|
// for block 17 to 24, the primary validator is node0
|
|
// for block 25 to 32, the primary validator is node1
|
|
blockHeaderVal0 := nodes[0].BlockChain().CurrentHeader()
|
|
|
|
// we remove peer connection between node0 and node1
|
|
if blockHeaderVal0.Number.Uint64() == 9 {
|
|
stacks[0].Server().RemovePeer(enodes[1])
|
|
}
|
|
|
|
// here, node1 is the primary validator, node0 will sign out-of-turn
|
|
|
|
// we add peer connection between node1 and node0
|
|
if blockHeaderVal0.Number.Uint64() == 14 {
|
|
stacks[0].Server().AddPeer(enodes[1])
|
|
}
|
|
|
|
// reorg happens here, node1 has higher difficulty, it will replace blocks by node0
|
|
|
|
if blockHeaderVal0.Number.Uint64() == 30 {
|
|
|
|
break
|
|
}
|
|
|
|
}
|
|
|
|
// check block 10 miner ; expected author is node1 signer
|
|
blockHeaderVal0 := nodes[0].BlockChain().GetHeaderByNumber(10)
|
|
blockHeaderVal1 := nodes[1].BlockChain().GetHeaderByNumber(10)
|
|
authorVal0, err := nodes[0].Engine().Author(blockHeaderVal0)
|
|
if err != nil {
|
|
log.Error("Error in getting author", "err", err)
|
|
}
|
|
authorVal1, err := nodes[1].Engine().Author(blockHeaderVal1)
|
|
if err != nil {
|
|
log.Error("Error in getting author", "err", err)
|
|
}
|
|
|
|
// check both nodes have the same block 10
|
|
assert.Equal(t, authorVal0, authorVal1)
|
|
|
|
// check node0 has block mined by node1
|
|
assert.Equal(t, authorVal0, nodes[1].AccountManager().Accounts()[0])
|
|
|
|
// check node1 has block mined by node1
|
|
assert.Equal(t, authorVal1, nodes[1].AccountManager().Accounts()[0])
|
|
|
|
// check block 11 miner ; expected author is node1 signer
|
|
blockHeaderVal0 = nodes[0].BlockChain().GetHeaderByNumber(11)
|
|
blockHeaderVal1 = nodes[1].BlockChain().GetHeaderByNumber(11)
|
|
authorVal0, err = nodes[0].Engine().Author(blockHeaderVal0)
|
|
if err != nil {
|
|
log.Error("Error in getting author", "err", err)
|
|
}
|
|
authorVal1, err = nodes[1].Engine().Author(blockHeaderVal1)
|
|
if err != nil {
|
|
log.Error("Error in getting author", "err", err)
|
|
}
|
|
|
|
// check both nodes have the same block 11
|
|
assert.Equal(t, authorVal0, authorVal1)
|
|
|
|
// check node0 has block mined by node1
|
|
assert.Equal(t, authorVal0, nodes[1].AccountManager().Accounts()[0])
|
|
|
|
// check node1 has block mined by node1
|
|
assert.Equal(t, authorVal1, nodes[1].AccountManager().Accounts()[0])
|
|
|
|
// check block 12 miner ; expected author is node1 signer
|
|
blockHeaderVal0 = nodes[0].BlockChain().GetHeaderByNumber(12)
|
|
blockHeaderVal1 = nodes[1].BlockChain().GetHeaderByNumber(12)
|
|
authorVal0, err = nodes[0].Engine().Author(blockHeaderVal0)
|
|
if err != nil {
|
|
log.Error("Error in getting author", "err", err)
|
|
}
|
|
authorVal1, err = nodes[1].Engine().Author(blockHeaderVal1)
|
|
if err != nil {
|
|
log.Error("Error in getting author", "err", err)
|
|
}
|
|
|
|
// check both nodes have the same block 12
|
|
assert.Equal(t, authorVal0, authorVal1)
|
|
|
|
// check node0 has block mined by node1
|
|
assert.Equal(t, authorVal0, nodes[1].AccountManager().Accounts()[0])
|
|
|
|
// check node1 has block mined by node1
|
|
assert.Equal(t, authorVal1, nodes[1].AccountManager().Accounts()[0])
|
|
|
|
// check block 17 miner ; expected author is node0 signer
|
|
blockHeaderVal0 = nodes[0].BlockChain().GetHeaderByNumber(17)
|
|
blockHeaderVal1 = nodes[1].BlockChain().GetHeaderByNumber(17)
|
|
authorVal0, err = nodes[0].Engine().Author(blockHeaderVal0)
|
|
if err != nil {
|
|
log.Error("Error in getting author", "err", err)
|
|
}
|
|
authorVal1, err = nodes[1].Engine().Author(blockHeaderVal1)
|
|
if err != nil {
|
|
log.Error("Error in getting author", "err", err)
|
|
}
|
|
|
|
// check both nodes have the same block 17
|
|
assert.Equal(t, authorVal0, authorVal1)
|
|
|
|
// check node0 has block mined by node1
|
|
assert.Equal(t, authorVal0, nodes[0].AccountManager().Accounts()[0])
|
|
|
|
// check node1 has block mined by node1
|
|
assert.Equal(t, authorVal1, nodes[0].AccountManager().Accounts()[0])
|
|
}
|
|
|
|
func TestForkWithBlockTime(t *testing.T) {
|
|
|
|
cases := []struct {
|
|
name string
|
|
sprint map[string]uint64
|
|
blockTime map[string]uint64
|
|
change uint64
|
|
producerDelay map[string]uint64
|
|
forkExpected bool
|
|
}{
|
|
{
|
|
name: "No fork after 2 sprints with producer delay = max block time",
|
|
sprint: map[string]uint64{
|
|
"0": 128,
|
|
},
|
|
blockTime: map[string]uint64{
|
|
"0": 5,
|
|
"128": 2,
|
|
"256": 8,
|
|
},
|
|
change: 2,
|
|
producerDelay: map[string]uint64{
|
|
"0": 8,
|
|
},
|
|
forkExpected: false,
|
|
},
|
|
{
|
|
name: "No Fork after 1 sprint producer delay = max block time",
|
|
sprint: map[string]uint64{
|
|
"0": 64,
|
|
},
|
|
blockTime: map[string]uint64{
|
|
"0": 5,
|
|
"64": 2,
|
|
},
|
|
change: 1,
|
|
producerDelay: map[string]uint64{
|
|
"0": 5,
|
|
},
|
|
forkExpected: false,
|
|
},
|
|
{
|
|
name: "Fork after 4 sprints with producer delay < max block time",
|
|
sprint: map[string]uint64{
|
|
"0": 16,
|
|
},
|
|
blockTime: map[string]uint64{
|
|
"0": 2,
|
|
"64": 5,
|
|
},
|
|
change: 4,
|
|
producerDelay: map[string]uint64{
|
|
"0": 4,
|
|
},
|
|
forkExpected: true,
|
|
},
|
|
}
|
|
|
|
// Create an Ethash network based off of the Ropsten config
|
|
// Generate a batch of accounts to seal and fund with
|
|
faucets := make([]*ecdsa.PrivateKey, 128)
|
|
for i := 0; i < len(faucets); i++ {
|
|
faucets[i], _ = crypto.GenerateKey()
|
|
}
|
|
genesis := InitGenesis(t, faucets, "./testdata/genesis_2val.json", 8)
|
|
|
|
for _, test := range cases {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
|
|
genesis.Config.Bor.Sprint = test.sprint
|
|
genesis.Config.Bor.Period = test.blockTime
|
|
genesis.Config.Bor.BackupMultiplier = test.blockTime
|
|
genesis.Config.Bor.ProducerDelay = test.producerDelay
|
|
|
|
stacks, nodes, _ := setupMiner(t, 2, genesis)
|
|
|
|
defer func() {
|
|
for _, stack := range stacks {
|
|
stack.Close()
|
|
}
|
|
}()
|
|
|
|
// Iterate over all the nodes and start mining
|
|
for _, node := range nodes {
|
|
if err := node.StartMining(1); err != nil {
|
|
t.Fatal("Error occured while starting miner", "node", node, "error", err)
|
|
}
|
|
}
|
|
var wg sync.WaitGroup
|
|
blockHeaders := make([]*types.Header, 2)
|
|
ticker := time.NewTicker(time.Duration(test.blockTime["0"]) * time.Second)
|
|
defer ticker.Stop()
|
|
|
|
for i := 0; i < 2; i++ {
|
|
wg.Add(1)
|
|
|
|
go func(i int) {
|
|
defer wg.Done()
|
|
|
|
for range ticker.C {
|
|
blockHeaders[i] = nodes[i].BlockChain().GetHeaderByNumber(test.sprint["0"]*test.change + 10)
|
|
if blockHeaders[i] != nil {
|
|
break
|
|
}
|
|
}
|
|
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
// Before the end of sprint
|
|
blockHeaderVal0 := nodes[0].BlockChain().GetHeaderByNumber(test.sprint["0"] - 1)
|
|
blockHeaderVal1 := nodes[1].BlockChain().GetHeaderByNumber(test.sprint["0"] - 1)
|
|
assert.Equal(t, blockHeaderVal0.Hash(), blockHeaderVal1.Hash())
|
|
assert.Equal(t, blockHeaderVal0.Time, blockHeaderVal1.Time)
|
|
|
|
author0, err := nodes[0].Engine().Author(blockHeaderVal0)
|
|
if err != nil {
|
|
t.Error("Error occured while fetching author", "err", err)
|
|
}
|
|
author1, err := nodes[1].Engine().Author(blockHeaderVal1)
|
|
if err != nil {
|
|
t.Error("Error occured while fetching author", "err", err)
|
|
}
|
|
assert.Equal(t, author0, author1)
|
|
|
|
// After the end of sprint
|
|
author2, err := nodes[0].Engine().Author(blockHeaders[0])
|
|
if err != nil {
|
|
t.Error("Error occured while fetching author", "err", err)
|
|
}
|
|
author3, err := nodes[1].Engine().Author(blockHeaders[1])
|
|
if err != nil {
|
|
t.Error("Error occured while fetching author", "err", err)
|
|
}
|
|
|
|
if test.forkExpected {
|
|
assert.NotEqual(t, blockHeaders[0].Hash(), blockHeaders[1].Hash())
|
|
assert.NotEqual(t, blockHeaders[0].Time, blockHeaders[1].Time)
|
|
assert.NotEqual(t, author2, author3)
|
|
} else {
|
|
assert.Equal(t, blockHeaders[0].Hash(), blockHeaders[1].Hash())
|
|
assert.Equal(t, blockHeaders[0].Time, blockHeaders[1].Time)
|
|
assert.Equal(t, author2, author3)
|
|
}
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
func TestInsertingSpanSizeBlocks(t *testing.T) {
|
|
init := buildEthereumInstance(t, rawdb.NewMemoryDatabase())
|
|
chain := init.ethereum.BlockChain()
|
|
engine := init.ethereum.Engine()
|
|
_bor := engine.(*bor.Bor)
|
|
|
|
defer _bor.Close()
|
|
|
|
_, currentSpan := loadSpanFromFile(t)
|
|
|
|
h, ctrl := getMockedHeimdallClient(t, currentSpan)
|
|
defer ctrl.Finish()
|
|
|
|
h.EXPECT().Close().AnyTimes()
|
|
h.EXPECT().FetchCheckpoint(gomock.Any(), int64(-1)).Return(&checkpoint.Checkpoint{
|
|
Proposer: currentSpan.SelectedProducers[0].Address,
|
|
StartBlock: big.NewInt(0),
|
|
EndBlock: big.NewInt(int64(spanSize)),
|
|
}, nil).AnyTimes()
|
|
|
|
_bor.SetHeimdallClient(h)
|
|
|
|
block := init.genesis.ToBlock()
|
|
// to := int64(block.Header().Time)
|
|
|
|
currentValidators := []*valset.Validator{valset.NewValidator(addr, 10)}
|
|
|
|
spanner := getMockedSpanner(t, currentValidators)
|
|
_bor.SetSpanner(spanner)
|
|
|
|
// 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, nil, currentValidators)
|
|
insertNewBlock(t, chain, block)
|
|
}
|
|
|
|
spanner = getMockedSpanner(t, currentSpan.ValidatorSet.Validators)
|
|
_bor.SetSpanner(spanner)
|
|
|
|
validators, err := _bor.GetCurrentValidators(context.Background(), block.Hash(), spanSize) // check validator set at the first block of new span
|
|
if err != nil {
|
|
t.Fatalf("%s", err)
|
|
}
|
|
|
|
require.Equal(t, 3, len(validators))
|
|
for i, validator := range validators {
|
|
require.Equal(t, validator.Address.Bytes(), currentSpan.SelectedProducers[i].Address.Bytes())
|
|
require.Equal(t, validator.VotingPower, currentSpan.SelectedProducers[i].VotingPower)
|
|
}
|
|
}
|
|
|
|
func TestFetchStateSyncEvents(t *testing.T) {
|
|
init := buildEthereumInstance(t, rawdb.NewMemoryDatabase())
|
|
chain := init.ethereum.BlockChain()
|
|
engine := init.ethereum.Engine()
|
|
_bor := engine.(*bor.Bor)
|
|
|
|
defer _bor.Close()
|
|
|
|
// A. Insert blocks for 0th sprint
|
|
block := init.genesis.ToBlock()
|
|
|
|
// B.1 Mock /bor/span/1
|
|
res, _ := loadSpanFromFile(t)
|
|
|
|
currentValidators := []*valset.Validator{valset.NewValidator(addr, 10)}
|
|
|
|
spanner := getMockedSpanner(t, currentValidators)
|
|
_bor.SetSpanner(spanner)
|
|
|
|
// Insert sprintSize # of blocks so that span is fetched at the start of a new sprint
|
|
for i := uint64(1); i < sprintSize; i++ {
|
|
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
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
|
|
h := mocks.NewMockIHeimdallClient(ctrl)
|
|
h.EXPECT().Close().AnyTimes()
|
|
h.EXPECT().Span(gomock.Any(), uint64(1)).Return(&res.Result, nil).AnyTimes()
|
|
|
|
// B.2 Mock State Sync events
|
|
fromID := uint64(1)
|
|
// at # sprintSize, events are fetched for [fromID, (block-sprint).Time)
|
|
to := int64(chain.GetHeaderByNumber(0).Time)
|
|
eventCount := 50
|
|
|
|
sample := getSampleEventRecord(t)
|
|
sample.Time = time.Unix(to-int64(eventCount+1), 0) // last event.Time will be just < to
|
|
eventRecords := generateFakeStateSyncEvents(sample, eventCount)
|
|
|
|
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, nil, res.Result.ValidatorSet.Validators)
|
|
|
|
// Validate the state sync transactions set by consensus
|
|
validateStateSyncEvents(t, eventRecords, chain.GetStateSync())
|
|
|
|
insertNewBlock(t, chain, block)
|
|
}
|
|
|
|
func validateStateSyncEvents(t *testing.T, expected []*clerk.EventRecordWithTime, got []*types.StateSyncData) {
|
|
require.Equal(t, len(expected), len(got), "number of state sync events should be equal")
|
|
|
|
for i := 0; i < len(expected); i++ {
|
|
require.Equal(t, expected[i].ID, got[i].ID, fmt.Sprintf("state sync ids should be equal - index: %d, expected: %d, got: %d", i, expected[i].ID, got[i].ID))
|
|
}
|
|
}
|
|
|
|
func TestFetchStateSyncEvents_2(t *testing.T) {
|
|
init := buildEthereumInstance(t, rawdb.NewMemoryDatabase())
|
|
chain := init.ethereum.BlockChain()
|
|
engine := init.ethereum.Engine()
|
|
_bor := engine.(*bor.Bor)
|
|
|
|
defer _bor.Close()
|
|
|
|
// 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()
|
|
|
|
h := mocks.NewMockIHeimdallClient(ctrl)
|
|
h.EXPECT().Close().AnyTimes()
|
|
h.EXPECT().Span(gomock.Any(), uint64(1)).Return(&res.Result, 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]
|
|
// Insert 5 events in this time range
|
|
eventRecords := []*clerk.EventRecordWithTime{
|
|
buildStateEvent(sample, 1, 3), // id = 1, time = 1
|
|
buildStateEvent(sample, 2, 1), // id = 2, time = 3
|
|
buildStateEvent(sample, 3, 2), // id = 3, time = 2
|
|
// event with id 5 is missing
|
|
buildStateEvent(sample, 4, 5), // id = 4, time = 5
|
|
buildStateEvent(sample, 6, 4), // id = 6, time = 4
|
|
}
|
|
|
|
h.EXPECT().StateSyncEvents(gomock.Any(), fromID, to).Return(eventRecords, nil).AnyTimes()
|
|
_bor.SetHeimdallClient(h)
|
|
|
|
// Insert blocks for 0th sprint
|
|
block := init.genesis.ToBlock()
|
|
|
|
var currentValidators []*valset.Validator
|
|
|
|
for i := uint64(1); i <= sprintSize; i++ {
|
|
if IsSpanEnd(i) {
|
|
currentValidators = res.Result.ValidatorSet.Validators
|
|
} else {
|
|
currentValidators = []*valset.Validator{valset.NewValidator(addr, 10)}
|
|
}
|
|
|
|
spanner := getMockedSpanner(t, currentValidators)
|
|
_bor.SetSpanner(spanner)
|
|
|
|
block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor, nil, currentValidators)
|
|
insertNewBlock(t, chain, block)
|
|
}
|
|
|
|
lastStateID, _ := _bor.GenesisContractsClient.LastStateId(nil, sprintSize, block.Hash())
|
|
|
|
// state 6 was not written
|
|
require.Equal(t, uint64(4), lastStateID.Uint64())
|
|
|
|
//
|
|
fromID = uint64(5)
|
|
to = int64(chain.GetHeaderByNumber(sprintSize).Time)
|
|
|
|
eventRecords = []*clerk.EventRecordWithTime{
|
|
buildStateEvent(sample, 5, 7),
|
|
buildStateEvent(sample, 6, 4),
|
|
}
|
|
h.EXPECT().StateSyncEvents(gomock.Any(), fromID, to).Return(eventRecords, nil).AnyTimes()
|
|
|
|
for i := sprintSize + 1; i <= spanSize; i++ {
|
|
if IsSpanEnd(i) {
|
|
currentValidators = res.Result.ValidatorSet.Validators
|
|
} else {
|
|
currentValidators = []*valset.Validator{valset.NewValidator(addr, 10)}
|
|
}
|
|
|
|
spanner := getMockedSpanner(t, currentValidators)
|
|
_bor.SetSpanner(spanner)
|
|
|
|
block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor, nil, res.Result.ValidatorSet.Validators)
|
|
insertNewBlock(t, chain, block)
|
|
}
|
|
|
|
lastStateID, _ = _bor.GenesisContractsClient.LastStateId(nil, spanSize, block.Hash())
|
|
require.Equal(t, uint64(6), lastStateID.Uint64())
|
|
}
|
|
|
|
func TestOutOfTurnSigning(t *testing.T) {
|
|
init := buildEthereumInstance(t, rawdb.NewMemoryDatabase())
|
|
chain := init.ethereum.BlockChain()
|
|
engine := init.ethereum.Engine()
|
|
_bor := engine.(*bor.Bor)
|
|
|
|
defer _bor.Close()
|
|
|
|
_, 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()
|
|
|
|
spanner := getMockedSpanner(t, heimdallSpan.ValidatorSet.Validators)
|
|
_bor.SetSpanner(spanner)
|
|
_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, nil, heimdallSpan.ValidatorSet.Validators, setDifficulty)
|
|
insertNewBlock(t, chain, block)
|
|
}
|
|
|
|
// insert spanSize-th block
|
|
// This account is one the out-of-turn validators for 1st (0-indexed) span
|
|
signer := "c8deb0bea5c41afe8e37b4d1bd84e31adff11b09c8c96ff4b605003cce067cd9"
|
|
signerKey, _ := hex.DecodeString(signer)
|
|
newKey, _ := crypto.HexToECDSA(signer)
|
|
newAddr := crypto.PubkeyToAddress(newKey.PublicKey)
|
|
expectedSuccessionNumber := 2
|
|
|
|
parentTime := block.Time()
|
|
|
|
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()
|
|
|
|
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})
|
|
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})
|
|
require.Nil(t, err)
|
|
}
|
|
|
|
func TestSignerNotFound(t *testing.T) {
|
|
init := buildEthereumInstance(t, rawdb.NewMemoryDatabase())
|
|
chain := init.ethereum.BlockChain()
|
|
engine := init.ethereum.Engine()
|
|
_bor := engine.(*bor.Bor)
|
|
|
|
defer _bor.Close()
|
|
|
|
_, heimdallSpan := loadSpanFromFile(t)
|
|
|
|
h, ctrl := getMockedHeimdallClient(t, heimdallSpan)
|
|
defer ctrl.Finish()
|
|
|
|
h.EXPECT().Close().AnyTimes()
|
|
|
|
_bor.SetHeimdallClient(h)
|
|
|
|
block := init.genesis.ToBlock()
|
|
|
|
// random signer account that is not a part of the validator set
|
|
const signer = "3714d99058cd64541433d59c6b391555b2fd9b54629c2b717a6c9c00d1127b6b"
|
|
signerKey, _ := hex.DecodeString(signer)
|
|
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)
|
|
|
|
_, err := chain.InsertChain([]*types.Block{block})
|
|
require.Equal(t,
|
|
*err.(*bor.UnauthorizedSignerError),
|
|
bor.UnauthorizedSignerError{Number: 0, Signer: newAddr.Bytes()})
|
|
}
|
|
|
|
// TestEIP1559Transition tests the following:
|
|
//
|
|
// 1. A transaction whose gasFeeCap is greater than the baseFee is valid.
|
|
// 2. Gas accounting for access lists on EIP-1559 transactions is correct.
|
|
// 3. Only the transaction's tip will be received by the coinbase.
|
|
// 4. The transaction sender pays for both the tip and baseFee.
|
|
// 5. The coinbase receives only the partially realized tip when
|
|
// gasFeeCap - gasTipCap < baseFee.
|
|
// 6. Legacy transaction behave as expected (e.g. gasPrice = gasFeeCap = gasTipCap).
|
|
func TestEIP1559Transition(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),
|
|
},
|
|
},
|
|
}
|
|
)
|
|
|
|
gspec.Config.BerlinBlock = common.Big0
|
|
gspec.Config.LondonBlock = common.Big0
|
|
genesis := gspec.MustCommit(db)
|
|
signer := types.LatestSigner(gspec.Config)
|
|
|
|
blocks, _ := 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{},
|
|
}
|
|
tx := types.NewTx(txdata)
|
|
tx, _ = types.SignTx(tx, signer, key1)
|
|
|
|
b.AddTx(tx)
|
|
})
|
|
|
|
diskdb := rawdb.NewMemoryDatabase()
|
|
gspec.MustCommit(diskdb)
|
|
|
|
chain, err := core.NewBlockChain(diskdb, nil, gspec, nil, 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)
|
|
|
|
// 1+2: Ensure EIP-1559 access lists are accounted for via gas usage.
|
|
expectedGas := params.TxGas + params.TxAccessListAddressGas + params.TxAccessListStorageKeyGas +
|
|
vm.GasQuickStep*2 + params.WarmStorageReadCostEIP2929 + params.ColdSloadCostEIP2929
|
|
if block.GasUsed() != expectedGas {
|
|
t.Fatalf("incorrect amount of gas spent: expected %d, got %d", expectedGas, block.GasUsed())
|
|
}
|
|
|
|
state, _ := chain.State()
|
|
|
|
// 3: Ensure that miner received only the tx's tip.
|
|
actual := state.GetBalance(block.Coinbase())
|
|
expected := new(big.Int).Add(
|
|
new(big.Int).SetUint64(block.GasUsed()*block.Transactions()[0].GasTipCap().Uint64()),
|
|
ethash.ConstantinopleBlockReward,
|
|
)
|
|
if actual.Cmp(expected) != 0 {
|
|
t.Fatalf("miner balance incorrect: expected %d, got %d", expected, actual)
|
|
}
|
|
|
|
// check burnt contract balance
|
|
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 {
|
|
t.Fatalf("burnt contract balance incorrect: expected %d, got %d", expected, actual)
|
|
}
|
|
|
|
// 4: Ensure the tx sender paid for the gasUsed * (tip + block baseFee).
|
|
actual = new(big.Int).Sub(funds, state.GetBalance(addr1))
|
|
expected = new(big.Int).SetUint64(block.GasUsed() * (block.Transactions()[0].GasTipCap().Uint64() + block.BaseFee().Uint64()))
|
|
if actual.Cmp(expected) != 0 {
|
|
t.Fatalf("sender balance incorrect: expected %d, got %d", expected, actual)
|
|
}
|
|
|
|
blocks, _ = core.GenerateChain(gspec.Config, block, engine, db, 1, func(i int, b *core.BlockGen) {
|
|
b.SetCoinbase(common.Address{2})
|
|
|
|
txdata := &types.LegacyTx{
|
|
Nonce: 0,
|
|
To: &aa,
|
|
Gas: 30000,
|
|
GasPrice: newGwei(5),
|
|
}
|
|
tx := types.NewTx(txdata)
|
|
tx, _ = types.SignTx(tx, signer, key2)
|
|
|
|
b.AddTx(tx)
|
|
})
|
|
|
|
if n, err := chain.InsertChain(blocks); err != nil {
|
|
t.Fatalf("block %d: failed to insert into chain: %v", n, err)
|
|
}
|
|
|
|
block = chain.GetBlockByNumber(2)
|
|
state, _ = chain.State()
|
|
effectiveTip := block.Transactions()[0].GasTipCap().Uint64() - block.BaseFee().Uint64()
|
|
|
|
// 6+5: Ensure that miner received only the tx's effective tip.
|
|
actual = state.GetBalance(block.Coinbase())
|
|
expected = new(big.Int).Add(
|
|
new(big.Int).SetUint64(block.GasUsed()*effectiveTip),
|
|
ethash.ConstantinopleBlockReward,
|
|
)
|
|
if actual.Cmp(expected) != 0 {
|
|
t.Fatalf("miner balance incorrect: expected %d, got %d", expected, actual)
|
|
}
|
|
|
|
// check burnt contract balance
|
|
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 {
|
|
t.Fatalf("burnt contract balance incorrect: expected %d, got %d", expected, actual)
|
|
}
|
|
|
|
// 4: Ensure the tx sender paid for the gasUsed * (effectiveTip + block baseFee).
|
|
actual = new(big.Int).Sub(funds, state.GetBalance(addr2))
|
|
expected = new(big.Int).SetUint64(block.GasUsed() * (effectiveTip + block.BaseFee().Uint64()))
|
|
if actual.Cmp(expected) != 0 {
|
|
t.Fatalf("sender balance incorrect: expected %d, got %d", expected, actual)
|
|
}
|
|
|
|
blocks, _ = core.GenerateChain(gspec.Config, block, engine, db, 1, func(i int, b *core.BlockGen) {
|
|
b.SetCoinbase(common.Address{3})
|
|
|
|
txdata := &types.LegacyTx{
|
|
Nonce: 0,
|
|
To: &aa,
|
|
Gas: 30000,
|
|
GasPrice: newGwei(5),
|
|
}
|
|
tx := types.NewTx(txdata)
|
|
tx, _ = types.SignTx(tx, signer, key3)
|
|
|
|
b.AddTx(tx)
|
|
|
|
accesses := types.AccessList{types.AccessTuple{
|
|
Address: aa,
|
|
StorageKeys: []common.Hash{{0}},
|
|
}}
|
|
|
|
txdata2 := &types.DynamicFeeTx{
|
|
ChainID: gspec.Config.ChainID,
|
|
Nonce: 1,
|
|
To: &aa,
|
|
Gas: 30000,
|
|
GasFeeCap: newGwei(5),
|
|
GasTipCap: big.NewInt(2),
|
|
AccessList: accesses,
|
|
Data: []byte{},
|
|
}
|
|
tx = types.NewTx(txdata2)
|
|
tx, _ = types.SignTx(tx, signer, key3)
|
|
|
|
b.AddTx(tx)
|
|
|
|
})
|
|
|
|
if n, err := chain.InsertChain(blocks); err != nil {
|
|
t.Fatalf("block %d: failed to insert into chain: %v", n, err)
|
|
}
|
|
|
|
block = chain.GetBlockByNumber(3)
|
|
state, _ = chain.State()
|
|
|
|
// check burnt contract balance
|
|
actual = state.GetBalance(common.HexToAddress(params.BorUnittestChainConfig.Bor.CalculateBurntContract(block.NumberU64())))
|
|
burntAmount := new(big.Int).Mul(
|
|
block.BaseFee(),
|
|
big.NewInt(int64(block.GasUsed())),
|
|
)
|
|
expected = new(big.Int).Add(burntContractBalance, burntAmount)
|
|
if actual.Cmp(expected) != 0 {
|
|
t.Fatalf("burnt contract balance incorrect: expected %d, got %d", expected, actual)
|
|
}
|
|
}
|
|
|
|
// 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, nil, 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()
|
|
|
|
block := init.genesis.ToBlock()
|
|
|
|
res, _ := loadSpanFromFile(t)
|
|
|
|
spanner := getMockedSpanner(t, res.Result.ValidatorSet.Validators)
|
|
_bor.SetSpanner(spanner)
|
|
|
|
for i := uint64(1); i < sprintSize; i++ {
|
|
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.Uint64()-1 {
|
|
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.Uint64() {
|
|
require.Equal(t, testSealHash(block.Header(), init.genesis.Config.Bor), bor.SealHash(block.Header(), init.genesis.Config.Bor))
|
|
}
|
|
}
|
|
}
|
|
|
|
// SealHash returns the hash of a block prior to it being sealed.
|
|
func testSealHash(header *types.Header, c *params.BorConfig) (hash common.Hash) {
|
|
hasher := sha3.NewLegacyKeccak256()
|
|
testEncodeSigHeader(hasher, header, c)
|
|
hasher.Sum(hash[:0])
|
|
return hash
|
|
}
|
|
|
|
func testEncodeSigHeader(w io.Writer, header *types.Header, c *params.BorConfig) {
|
|
enc := []interface{}{
|
|
header.ParentHash,
|
|
header.UncleHash,
|
|
header.Coinbase,
|
|
header.Root,
|
|
header.TxHash,
|
|
header.ReceiptHash,
|
|
header.Bloom,
|
|
header.Difficulty,
|
|
header.Number,
|
|
header.GasLimit,
|
|
header.GasUsed,
|
|
header.Time,
|
|
header.Extra[:len(header.Extra)-65], // Yes, this will panic if extra is too short
|
|
header.MixDigest,
|
|
header.Nonce,
|
|
}
|
|
if c.IsJaipur(header.Number) {
|
|
if header.BaseFee != nil {
|
|
enc = append(enc, header.BaseFee)
|
|
}
|
|
}
|
|
if err := rlp.Encode(w, enc); err != nil {
|
|
panic("can't encode: " + err.Error())
|
|
}
|
|
}
|