github.com/klaytn/klaytn@v1.12.1/consensus/istanbul/backend/testutil_test.go (about)

     1  // Modifications Copyright 2020 The klaytn Authors
     2  // Copyright 2017 The go-ethereum Authors
     3  // This file is part of the go-ethereum library.
     4  //
     5  // The go-ethereum library is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Lesser General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // The go-ethereum library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  // GNU Lesser General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public License
    16  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package backend
    19  
    20  import (
    21  	"crypto/ecdsa"
    22  	"flag"
    23  	"math/big"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/klaytn/klaytn/blockchain"
    28  	"github.com/klaytn/klaytn/blockchain/types"
    29  	"github.com/klaytn/klaytn/blockchain/vm"
    30  	"github.com/klaytn/klaytn/common"
    31  	"github.com/klaytn/klaytn/consensus"
    32  	"github.com/klaytn/klaytn/consensus/istanbul"
    33  	"github.com/klaytn/klaytn/consensus/istanbul/core"
    34  	"github.com/klaytn/klaytn/crypto"
    35  	"github.com/klaytn/klaytn/crypto/bls"
    36  	"github.com/klaytn/klaytn/governance"
    37  	"github.com/klaytn/klaytn/log"
    38  	"github.com/klaytn/klaytn/params"
    39  	"github.com/klaytn/klaytn/reward"
    40  	"github.com/klaytn/klaytn/rlp"
    41  	"github.com/klaytn/klaytn/storage/database"
    42  )
    43  
    44  var (
    45  	testBaseConfig   *params.ChainConfig
    46  	testKoreConfig   *params.ChainConfig
    47  	testRandaoConfig *params.ChainConfig
    48  )
    49  
    50  func init() {
    51  	testBaseConfig = &params.ChainConfig{
    52  		Istanbul:   params.GetDefaultIstanbulConfig(),
    53  		Governance: params.GetDefaultGovernanceConfig(),
    54  	}
    55  
    56  	testKoreConfig = testBaseConfig.Copy()
    57  	testKoreConfig.IstanbulCompatibleBlock = common.Big0
    58  	testKoreConfig.LondonCompatibleBlock = common.Big0
    59  	testKoreConfig.EthTxTypeCompatibleBlock = common.Big0
    60  	testKoreConfig.MagmaCompatibleBlock = common.Big0
    61  	testKoreConfig.KoreCompatibleBlock = common.Big0
    62  
    63  	testRandaoConfig = testKoreConfig.Copy()
    64  	testRandaoConfig.ShanghaiCompatibleBlock = common.Big0
    65  	testRandaoConfig.CancunCompatibleBlock = common.Big0
    66  	testRandaoConfig.RandaoCompatibleBlock = common.Big0
    67  	testRandaoConfig.RandaoRegistry = &params.RegistryConfig{}
    68  }
    69  
    70  type testOverrides struct {
    71  	node0Key       *ecdsa.PrivateKey // Override node[0] key
    72  	node0BlsKey    bls.SecretKey     // Override node[0] bls key
    73  	blockPeriod    *uint64           // Override block period. If not set, 1 second is used.
    74  	stakingAmounts []uint64          // Override staking amounts. If not set, 0 for all nodes.
    75  }
    76  
    77  // Mock BlsPubkeyProvider that replaces KIP-113 contract query.
    78  type mockBlsPubkeyProvider struct {
    79  	infos map[common.Address]bls.PublicKey
    80  }
    81  
    82  func newMockBlsPubkeyProvider(addrs []common.Address, blsKeys []bls.SecretKey) *mockBlsPubkeyProvider {
    83  	infos := make(map[common.Address]bls.PublicKey)
    84  	for i := 0; i < len(addrs); i++ {
    85  		infos[addrs[i]] = blsKeys[i].PublicKey()
    86  	}
    87  	return &mockBlsPubkeyProvider{infos}
    88  }
    89  
    90  func (m *mockBlsPubkeyProvider) GetBlsPubkey(chain consensus.ChainReader, proposer common.Address, num *big.Int) (bls.PublicKey, error) {
    91  	if pub, ok := m.infos[proposer]; ok {
    92  		return pub, nil
    93  	} else {
    94  		return nil, errNoBlsPub
    95  	}
    96  }
    97  
    98  func (m *mockBlsPubkeyProvider) ResetBlsCache() {}
    99  
   100  type testContext struct {
   101  	config      *params.ChainConfig
   102  	nodeKeys    []*ecdsa.PrivateKey // Generated node keys
   103  	nodeAddrs   []common.Address    // Generated node addrs
   104  	nodeBlsKeys []bls.SecretKey     // Generated node bls keys
   105  
   106  	chain  *blockchain.BlockChain
   107  	engine *backend
   108  	sm     *reward.StakingManager
   109  }
   110  
   111  func newTestContext(numNodes int, config *params.ChainConfig, overrides *testOverrides) *testContext {
   112  	if config == nil {
   113  		config = testBaseConfig
   114  	}
   115  	if overrides == nil {
   116  		overrides = &testOverrides{}
   117  	}
   118  	if overrides.node0Key == nil {
   119  		overrides.node0Key, _ = crypto.GenerateKey()
   120  	}
   121  	if overrides.node0BlsKey == nil {
   122  		overrides.node0BlsKey, _ = bls.DeriveFromECDSA(overrides.node0Key)
   123  	}
   124  	if overrides.blockPeriod == nil {
   125  		one := uint64(1)
   126  		overrides.blockPeriod = &one
   127  	}
   128  	if overrides.stakingAmounts == nil {
   129  		overrides.stakingAmounts = make([]uint64, numNodes)
   130  	}
   131  
   132  	// Create node keys
   133  	var (
   134  		nodeKeys    = make([]*ecdsa.PrivateKey, numNodes)
   135  		nodeAddrs   = make([]common.Address, numNodes)
   136  		nodeBlsKeys = make([]bls.SecretKey, numNodes)
   137  
   138  		dbm = database.NewMemoryDBManager()
   139  		gov = governance.NewMixedEngine(config, dbm)
   140  	)
   141  	nodeKeys[0] = overrides.node0Key
   142  	nodeAddrs[0] = crypto.PubkeyToAddress(nodeKeys[0].PublicKey)
   143  	nodeBlsKeys[0] = overrides.node0BlsKey
   144  	for i := 1; i < numNodes; i++ {
   145  		nodeKeys[i], _ = crypto.GenerateKey()
   146  		nodeAddrs[i] = crypto.PubkeyToAddress(nodeKeys[i].PublicKey)
   147  		nodeBlsKeys[i], _ = bls.DeriveFromECDSA(nodeKeys[i])
   148  	}
   149  
   150  	// Create genesis block
   151  	if config.Governance.GovernanceMode == "single" {
   152  		config.Governance.GoverningNode = nodeAddrs[0]
   153  	}
   154  	genesis := blockchain.DefaultBaobabGenesisBlock()
   155  	genesis.Config = config
   156  	genesis.ExtraData = makeGenesisExtra(nodeAddrs)
   157  	genesis.Timestamp = uint64(time.Now().Unix())
   158  	genesis.MustCommit(dbm)
   159  
   160  	// Create istanbul engine
   161  	istanbulConfig := &istanbul.Config{
   162  		Timeout:        10000,
   163  		BlockPeriod:    *overrides.blockPeriod,
   164  		ProposerPolicy: istanbul.ProposerPolicy(config.Istanbul.ProposerPolicy),
   165  		Epoch:          config.Istanbul.Epoch,
   166  		SubGroupSize:   config.Istanbul.SubGroupSize,
   167  	}
   168  	engine := New(&BackendOpts{
   169  		IstanbulConfig:    istanbulConfig,
   170  		Rewardbase:        common.HexToAddress("0x2A35FE72F847aa0B509e4055883aE90c87558AaD"),
   171  		PrivateKey:        nodeKeys[0],
   172  		BlsSecretKey:      nodeBlsKeys[0],
   173  		DB:                dbm,
   174  		Governance:        gov,
   175  		BlsPubkeyProvider: newMockBlsPubkeyProvider(nodeAddrs, nodeBlsKeys),
   176  		NodeType:          common.CONSENSUSNODE,
   177  	}).(*backend)
   178  	gov.SetNodeAddress(engine.Address())
   179  
   180  	// Override StakingManager
   181  	sm := makeTestStakingManager(nodeAddrs, overrides.stakingAmounts)
   182  
   183  	// Create blockchain
   184  	cacheConfig := &blockchain.CacheConfig{
   185  		ArchiveMode:       false,
   186  		CacheSize:         512,
   187  		BlockInterval:     blockchain.DefaultBlockInterval,
   188  		TriesInMemory:     blockchain.DefaultTriesInMemory,
   189  		SnapshotCacheSize: 0, // Disable state snapshot
   190  	}
   191  	chain, err := blockchain.NewBlockChain(dbm, cacheConfig, config, engine, vm.Config{})
   192  	if err != nil {
   193  		panic(err)
   194  	}
   195  	gov.SetBlockchain(chain)
   196  
   197  	// Start the engine
   198  	if err := engine.Start(chain, chain.CurrentBlock, chain.HasBadBlock); err != nil {
   199  		panic(err)
   200  	}
   201  
   202  	return &testContext{
   203  		config:    config,
   204  		nodeKeys:  nodeKeys,
   205  		nodeAddrs: nodeAddrs,
   206  
   207  		chain:  chain,
   208  		engine: engine,
   209  		sm:     sm,
   210  	}
   211  }
   212  
   213  // Make empty header
   214  func (ctx *testContext) MakeHeader(parent *types.Block) *types.Header {
   215  	header := &types.Header{
   216  		ParentHash: parent.Hash(),
   217  		Number:     parent.Number().Add(parent.Number(), common.Big1),
   218  		GasUsed:    0,
   219  		Extra:      parent.Extra(),
   220  		Time:       new(big.Int).Add(parent.Time(), new(big.Int).SetUint64(ctx.engine.config.BlockPeriod)),
   221  		BlockScore: defaultBlockScore,
   222  	}
   223  	if parent.Header().BaseFee != nil {
   224  		// Assume BaseFee does not change
   225  		header.BaseFee = parent.Header().BaseFee
   226  	}
   227  	return header
   228  }
   229  
   230  // Block with no signature.
   231  func (ctx *testContext) MakeBlock(parent *types.Block) *types.Block {
   232  	chain, engine := ctx.chain, ctx.engine
   233  	header := ctx.MakeHeader(parent)
   234  	if err := engine.Prepare(chain, header); err != nil {
   235  		panic(err)
   236  	}
   237  	state, _ := chain.StateAt(parent.Root())
   238  	block, _ := engine.Finalize(chain, header, state, nil, nil)
   239  	return block
   240  }
   241  
   242  // Block with proposer seal (no committed seals).
   243  func (ctx *testContext) MakeBlockWithSeal(parent *types.Block) *types.Block {
   244  	chain, engine := ctx.chain, ctx.engine
   245  	block := ctx.MakeBlock(parent)
   246  	result, err := engine.Seal(chain, block, make(chan struct{}))
   247  	if err != nil {
   248  		panic(err)
   249  	}
   250  	return result
   251  }
   252  
   253  // Block with proposer seal and all committed seals.
   254  func (ctx *testContext) MakeBlockWithCommittedSeals(parent *types.Block) *types.Block {
   255  	blockWithoutSeal := ctx.MakeBlock(parent)
   256  
   257  	// add proposer seal for the block
   258  	block, err := ctx.engine.updateBlock(blockWithoutSeal)
   259  	if err != nil {
   260  		panic(err)
   261  	}
   262  
   263  	// write validators committed seals to the block
   264  	header := block.Header()
   265  	committedSeals := ctx.MakeCommittedSeals(block.Hash())
   266  	err = writeCommittedSeals(header, committedSeals)
   267  	if err != nil {
   268  		panic(err)
   269  	}
   270  	block = block.WithSeal(header)
   271  
   272  	return block
   273  }
   274  
   275  func (ctx *testContext) MakeCommittedSeals(hash common.Hash) [][]byte {
   276  	committedSeals := make([][]byte, len(ctx.nodeKeys))
   277  	hashData := crypto.Keccak256(core.PrepareCommittedSeal(hash))
   278  	for i, key := range ctx.nodeKeys {
   279  		sig, _ := crypto.Sign(hashData, key)
   280  		committedSeals[i] = make([]byte, types.IstanbulExtraSeal)
   281  		copy(committedSeals[i][:], sig)
   282  	}
   283  	return committedSeals
   284  }
   285  
   286  func (ctx *testContext) Cleanup() {
   287  	ctx.chain.Stop()
   288  	ctx.engine.Stop()
   289  	reward.SetTestStakingManager(ctx.sm)
   290  }
   291  
   292  func makeGenesisExtra(addrs []common.Address) []byte {
   293  	extra := &types.IstanbulExtra{
   294  		Validators:    addrs,
   295  		Seal:          []byte{},
   296  		CommittedSeal: [][]byte{},
   297  	}
   298  	encoded, err := rlp.EncodeToBytes(&extra)
   299  	if err != nil {
   300  		panic(err)
   301  	}
   302  
   303  	vanity := make([]byte, types.IstanbulExtraVanity)
   304  	return append(vanity, encoded...)
   305  }
   306  
   307  // Set StakingInfo with given addresses and amounts, returns the original (old) StakingManager.
   308  // Must call `reward.SetTestStakingManager(oldStakingManager)` after testing
   309  // because StakingManager is a global singleton.
   310  func makeTestStakingManager(addrs []common.Address, amounts []uint64) *reward.StakingManager {
   311  	info := &reward.StakingInfo{BlockNum: 0}
   312  	for i, addr := range addrs {
   313  		// Assign random reward address
   314  		rewardKey, _ := crypto.GenerateKey()
   315  		rewardAddr := crypto.PubkeyToAddress(rewardKey.PublicKey)
   316  
   317  		info.CouncilNodeAddrs = append(info.CouncilNodeAddrs, addr)
   318  		info.CouncilStakingAddrs = append(info.CouncilStakingAddrs, addr)
   319  		info.CouncilStakingAmounts = append(info.CouncilStakingAmounts, amounts[i])
   320  		info.CouncilRewardAddrs = append(info.CouncilRewardAddrs, rewardAddr)
   321  	}
   322  
   323  	// Save old StakingManager, overwrite with the fake one.
   324  	oldStakingManager := reward.GetStakingManager()
   325  	reward.SetTestStakingManagerWithStakingInfoCache(info)
   326  	return oldStakingManager
   327  }
   328  
   329  func TestTestContext(t *testing.T) {
   330  	ctx := newTestContext(1, nil, nil)
   331  	defer ctx.Cleanup()
   332  }
   333  
   334  func TestMain(m *testing.M) {
   335  	// Because api/debug/flag.go sets the global logger Info level,
   336  	// and BlockChain test generates a lot of Info logs, override to Warn level here.
   337  	flag.Parse() // needed for testing.Verbose()
   338  	log.EnableLogForTest(log.LvlCrit, log.LvlWarn)
   339  
   340  	m.Run()
   341  }