github.com/klaytn/klaytn@v1.12.1/tests/testutil_blockchain_test.go (about)

     1  package tests
     2  
     3  import (
     4  	"crypto/ecdsa"
     5  	"crypto/sha512"
     6  	"errors"
     7  	"fmt"
     8  	"math/big"
     9  	"os"
    10  	"path/filepath"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/klaytn/klaytn/accounts/abi/bind"
    15  	"github.com/klaytn/klaytn/blockchain"
    16  	"github.com/klaytn/klaytn/blockchain/types"
    17  	"github.com/klaytn/klaytn/common"
    18  	"github.com/klaytn/klaytn/consensus/istanbul"
    19  	"github.com/klaytn/klaytn/crypto"
    20  	"github.com/klaytn/klaytn/crypto/bls"
    21  	"github.com/klaytn/klaytn/networks/p2p"
    22  	"github.com/klaytn/klaytn/networks/p2p/discover"
    23  	"github.com/klaytn/klaytn/networks/rpc"
    24  	"github.com/klaytn/klaytn/node"
    25  	"github.com/klaytn/klaytn/node/cn"
    26  	"github.com/klaytn/klaytn/params"
    27  	"github.com/klaytn/klaytn/reward"
    28  	"github.com/klaytn/klaytn/rlp"
    29  	"github.com/stretchr/testify/assert"
    30  	"github.com/tyler-smith/go-bip32"
    31  	"golang.org/x/crypto/pbkdf2"
    32  )
    33  
    34  // Full blockchain test context.
    35  // TODO: replace newBlockchain()
    36  type blockchainTestContext struct {
    37  	numNodes     int
    38  	accountKeys  []*ecdsa.PrivateKey
    39  	accountAddrs []common.Address
    40  	accounts     []*bind.TransactOpts // accounts[0:numNodes] are node keys
    41  	config       *params.ChainConfig
    42  	genesis      *blockchain.Genesis
    43  
    44  	workspace string
    45  	nodes     []*blockchainTestNode
    46  }
    47  
    48  type blockchainTestNode struct {
    49  	datadir string
    50  	node    *node.Node
    51  	cn      *cn.CN
    52  }
    53  
    54  type blockchainTestOverrides struct {
    55  	numNodes    int                     // default: 1
    56  	numAccounts int                     // default: numNodes
    57  	config      *params.ChainConfig     // default: blockchainTestChainConfig
    58  	alloc       blockchain.GenesisAlloc // default: 10_000_000 KLAY for each account
    59  }
    60  
    61  var blockchainTestChainConfig = &params.ChainConfig{
    62  	ChainID:       big.NewInt(31337),
    63  	DeriveShaImpl: 2,
    64  	UnitPrice:     25 * params.Ston,
    65  	Governance: &params.GovernanceConfig{
    66  		GovernanceMode: "none",
    67  		Reward: &params.RewardConfig{
    68  			MintingAmount:          big.NewInt(params.KLAY * 6.4),
    69  			Ratio:                  "100/0/0",
    70  			Kip82Ratio:             "20/80",
    71  			UseGiniCoeff:           false,
    72  			DeferredTxFee:          true,
    73  			StakingUpdateInterval:  60,
    74  			ProposerUpdateInterval: 30,
    75  			MinimumStake:           big.NewInt(5_000_000),
    76  		},
    77  	},
    78  	Istanbul: &params.IstanbulConfig{
    79  		Epoch:          120,
    80  		ProposerPolicy: uint64(istanbul.RoundRobin),
    81  		SubGroupSize:   100,
    82  	},
    83  }
    84  
    85  func newBlockchainTestContext(overrides *blockchainTestOverrides) (*blockchainTestContext, error) {
    86  	if overrides == nil {
    87  		overrides = &blockchainTestOverrides{}
    88  	}
    89  	if overrides.numNodes == 0 {
    90  		overrides.numNodes = 1
    91  	}
    92  	if overrides.numAccounts == 0 {
    93  		overrides.numAccounts = overrides.numNodes
    94  	}
    95  	if overrides.numAccounts < overrides.numNodes {
    96  		return nil, errors.New("numAccounts less than numNodes")
    97  	}
    98  	if overrides.config == nil {
    99  		overrides.config = blockchainTestChainConfig
   100  	}
   101  	if overrides.alloc == nil {
   102  		overrides.alloc = make(blockchain.GenesisAlloc)
   103  	}
   104  
   105  	ctx := &blockchainTestContext{
   106  		numNodes: overrides.numNodes,
   107  	}
   108  	ctx.setAccounts(overrides.numAccounts)
   109  	ctx.setConfig(overrides.config)
   110  	ctx.setGenesis(overrides.alloc)
   111  	ctx.setWorkspace()
   112  	err := ctx.setNodes(ctx.numNodes)
   113  	return ctx, err
   114  }
   115  
   116  func (ctx *blockchainTestContext) setAccounts(count int) {
   117  	ctx.accountKeys = make([]*ecdsa.PrivateKey, count)
   118  	ctx.accountAddrs = make([]common.Address, count)
   119  	ctx.accounts = make([]*bind.TransactOpts, count)
   120  	for i := 0; i < count; i++ {
   121  		privateKey := deriveTestAccount(i)
   122  		ctx.accountKeys[i] = privateKey
   123  		ctx.accountAddrs[i] = crypto.PubkeyToAddress(privateKey.PublicKey)
   124  		ctx.accounts[i] = bind.NewKeyedTransactor(privateKey)
   125  	}
   126  }
   127  
   128  func (ctx *blockchainTestContext) setConfig(config *params.ChainConfig) {
   129  	ctx.config = config.Copy()
   130  	ctx.config.Istanbul.SubGroupSize = uint64(ctx.numNodes)
   131  }
   132  
   133  func (ctx *blockchainTestContext) setGenesis(alloc blockchain.GenesisAlloc) {
   134  	// Genesis ExtraData from nodeAddrs
   135  	extra, _ := rlp.EncodeToBytes(&types.IstanbulExtra{
   136  		Validators:    ctx.accountAddrs[:ctx.numNodes],
   137  		Seal:          []byte{},
   138  		CommittedSeal: [][]byte{},
   139  	})
   140  	vanity := make([]byte, types.IstanbulExtraVanity)
   141  
   142  	// Genesis Alloc from overrides.alloc + rich accountAddrs
   143  	richBalance := new(big.Int).Mul(big.NewInt(params.KLAY), big.NewInt(10_000_000))
   144  	for _, addr := range ctx.accountAddrs {
   145  		account := alloc[addr]
   146  		account.Balance = richBalance
   147  		alloc[addr] = account
   148  	}
   149  
   150  	ctx.genesis = &blockchain.Genesis{
   151  		Config:     ctx.config,
   152  		Timestamp:  uint64(time.Now().Unix()),
   153  		ExtraData:  append(vanity, extra...),
   154  		BlockScore: common.Big1,
   155  		Alloc:      alloc,
   156  	}
   157  }
   158  
   159  func (ctx *blockchainTestContext) setWorkspace() {
   160  	workspace, _ := os.MkdirTemp("", "klaytn-test-state")
   161  	ctx.workspace = workspace
   162  }
   163  
   164  func (ctx *blockchainTestContext) setNodes(numNodes int) error {
   165  	ctx.nodes = make([]*blockchainTestNode, numNodes)
   166  	for i := 0; i < numNodes; i++ {
   167  		if err := ctx.setNode(i); err != nil {
   168  			return err
   169  		}
   170  	}
   171  	return nil
   172  }
   173  
   174  func (ctx *blockchainTestContext) setNode(nodeIndex int) (err error) {
   175  	tn := &blockchainTestNode{}
   176  	tn.datadir = filepath.Join(ctx.workspace, fmt.Sprintf("node%d", nodeIndex))
   177  
   178  	// P2P ports: 32000, 32001, 32002...
   179  	// RPC ports: 38000, 38001, 38002...
   180  	peers := make([]*discover.Node, ctx.numNodes)
   181  	for i := 0; i < ctx.numNodes; i++ {
   182  		id := crypto.FromECDSAPub(&ctx.accountKeys[i].PublicKey)[1:] // strip 0x04 prefix byte
   183  		kni := fmt.Sprintf("kni://%x@127.0.0.1:%d?discport=0&type=cn", id, 32000+i)
   184  		peers[i], err = discover.ParseNode(kni)
   185  		if err != nil {
   186  			return
   187  		}
   188  	}
   189  	peers = append(peers[:nodeIndex], peers[nodeIndex+1:]...) // remove self
   190  
   191  	nodeKey := ctx.accountKeys[nodeIndex]
   192  	blsKey, _ := bls.DeriveFromECDSA(nodeKey)
   193  	nodeConf := &node.Config{
   194  		DataDir:           tn.datadir,
   195  		UseLightweightKDF: true,
   196  		P2P: p2p.Config{
   197  			PrivateKey:             nodeKey,
   198  			MaxPhysicalConnections: 100, // big enough
   199  			ConnectionType:         common.CONSENSUSNODE,
   200  			NoDiscovery:            true,
   201  			StaticNodes:            peers,
   202  			ListenAddr:             fmt.Sprintf("0.0.0.0:%d", 32000+nodeIndex),
   203  		},
   204  		BlsKey:           blsKey,
   205  		IPCPath:          "klay.ipc",
   206  		HTTPHost:         "0.0.0.0",
   207  		HTTPPort:         38000 + nodeIndex,
   208  		HTTPVirtualHosts: []string{"*"},
   209  		HTTPTimeouts:     rpc.DefaultHTTPTimeouts,
   210  		NtpRemoteServer:  "",
   211  	}
   212  	if tn.node, err = node.New(nodeConf); err != nil {
   213  		return
   214  	}
   215  
   216  	cnConf := cn.GetDefaultConfig()
   217  	cnConf.NetworkId = ctx.config.ChainID.Uint64()
   218  	cnConf.Genesis = ctx.genesis
   219  	cnConf.Rewardbase = ctx.accountAddrs[nodeIndex]
   220  	cnConf.SingleDB = false       // identical to regular CN
   221  	cnConf.NumStateTrieShards = 4 // identical to regular CN
   222  	cnConf.NoPruning = true       // archive mode
   223  	err = tn.node.Register(func(ctx *node.ServiceContext) (node.Service, error) {
   224  		return cn.New(ctx, cnConf)
   225  	})
   226  	if err != nil {
   227  		return
   228  	}
   229  	if err = tn.node.Start(); err != nil {
   230  		return
   231  	}
   232  	if err = tn.node.Service(&tn.cn); err != nil {
   233  		return
   234  	}
   235  	ctx.nodes[nodeIndex] = tn
   236  	return
   237  }
   238  
   239  func (ctx *blockchainTestContext) forEachNode(f func(*blockchainTestNode) error) error {
   240  	for _, tn := range ctx.nodes {
   241  		if err := f(tn); err != nil {
   242  			return err
   243  		}
   244  	}
   245  	return nil
   246  }
   247  
   248  func (ctx *blockchainTestContext) Start() error {
   249  	return ctx.forEachNode(func(tn *blockchainTestNode) error {
   250  		return tn.cn.StartMining(false)
   251  	})
   252  }
   253  
   254  func (ctx *blockchainTestContext) Stop() error {
   255  	err := ctx.forEachNode(func(tn *blockchainTestNode) error {
   256  		return tn.node.Stop()
   257  	})
   258  	if err != nil {
   259  		return err
   260  	}
   261  
   262  	// TODO: make StakingManager not singleton OR recreate new in cn.New()
   263  	// StakingManager is a global singleton and it never gets recreated.
   264  	// Manually clear StakingManager-related global states so that
   265  	// other tests can use StakingManager as if it's fresh.
   266  	reward.PurgeStakingInfoCache()
   267  	blockchain.ClearMigrationPrerequisites()
   268  	return nil
   269  }
   270  
   271  func (ctx *blockchainTestContext) Restart() error {
   272  	if err := ctx.Stop(); err != nil {
   273  		return err
   274  	}
   275  	// Recreate nodes
   276  	if err := ctx.setNodes(ctx.numNodes); err != nil {
   277  		return err
   278  	}
   279  	return ctx.Start()
   280  }
   281  
   282  func (ctx *blockchainTestContext) Cleanup() error {
   283  	if err := ctx.Stop(); err != nil {
   284  		return err
   285  	}
   286  	return os.RemoveAll(ctx.workspace)
   287  }
   288  
   289  func (ctx *blockchainTestContext) WaitBlock(t *testing.T, num uint64) {
   290  	block := waitBlock(ctx.nodes[0].cn.BlockChain(), num)
   291  	assert.NotNil(t, block)
   292  }
   293  
   294  func (ctx *blockchainTestContext) WaitTx(t *testing.T, txhash common.Hash) {
   295  	rc := waitReceipt(ctx.nodes[0].cn.BlockChain().(*blockchain.BlockChain), txhash)
   296  	assert.NotNil(t, rc)
   297  	if rc != nil {
   298  		assert.Equal(t, types.ReceiptStatusSuccessful, rc.Status)
   299  	}
   300  }
   301  
   302  func (ctx *blockchainTestContext) Dump(t *testing.T) {
   303  	for i, node := range ctx.nodes {
   304  		t.Logf("node[%d] http://%s  %s", i, node.node.HTTPEndpoint(), node.node.IPCEndpoint())
   305  	}
   306  	for i, addr := range ctx.accountAddrs {
   307  		t.Logf("account[%d] %s", i, addr.Hex())
   308  	}
   309  }
   310  
   311  func (ctx *blockchainTestContext) Subscribe(t *testing.T, logFunc func(ev *blockchain.ChainEvent)) {
   312  	if logFunc == nil {
   313  		logFunc = func(ev *blockchain.ChainEvent) {
   314  			t.Logf("block[%d] txs=%d", ev.Block.NumberU64(), ev.Block.Transactions().Len())
   315  		}
   316  	}
   317  
   318  	go func() {
   319  		chain := ctx.nodes[0].cn.BlockChain()
   320  		chainEventCh := make(chan blockchain.ChainEvent)
   321  		subscription := chain.SubscribeChainEvent(chainEventCh)
   322  		defer subscription.Unsubscribe()
   323  		for {
   324  			ev := <-chainEventCh
   325  			logFunc(&ev)
   326  		}
   327  	}()
   328  }
   329  
   330  func deriveTestAccount(index int) *ecdsa.PrivateKey {
   331  	// "m/44'/60'/0'/0/0"
   332  	mnemonic := "test test test test test test test test test test test junk"
   333  	seed := pbkdf2.Key([]byte(mnemonic), []byte("mnemonic"), 2048, 64, sha512.New)
   334  	key, _ := bip32.NewMasterKey(seed)
   335  	key, _ = key.NewChildKey(0x8000002c)
   336  	key, _ = key.NewChildKey(0x8000003c)
   337  	key, _ = key.NewChildKey(0x80000000)
   338  	key, _ = key.NewChildKey(0x00000000)
   339  
   340  	child, _ := key.NewChildKey(uint32(index))
   341  	privateKey, _ := crypto.ToECDSA(child.Key)
   342  	return privateKey
   343  }