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

     1  // Copyright 2022 The klaytn Authors
     2  // This file is part of the klaytn library.
     3  //
     4  // The klaytn library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The klaytn library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the klaytn library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package tests
    18  
    19  import (
    20  	"encoding/hex"
    21  	"math/big"
    22  	"os"
    23  	"strings"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/klaytn/klaytn/accounts/abi"
    28  	"github.com/klaytn/klaytn/blockchain"
    29  	"github.com/klaytn/klaytn/blockchain/types"
    30  	"github.com/klaytn/klaytn/common"
    31  	"github.com/klaytn/klaytn/consensus/istanbul"
    32  	govcontract "github.com/klaytn/klaytn/contracts/gov"
    33  	"github.com/klaytn/klaytn/crypto"
    34  	"github.com/klaytn/klaytn/log"
    35  	"github.com/klaytn/klaytn/node/cn"
    36  	"github.com/klaytn/klaytn/params"
    37  	"github.com/stretchr/testify/assert"
    38  	"github.com/stretchr/testify/require"
    39  )
    40  
    41  // TestGovernance_Engines tests MixedEngine, ContractEngine, and their
    42  // (1) CurrentParams() and (2) EffectiveParams() results.
    43  func TestGovernance_Engines(t *testing.T) {
    44  	log.EnableLogForTest(log.LvlCrit, log.LvlDebug)
    45  
    46  	config := params.CypressChainConfig.Copy()
    47  	config.IstanbulCompatibleBlock = new(big.Int).SetUint64(0)
    48  	config.LondonCompatibleBlock = new(big.Int).SetUint64(0)
    49  	config.EthTxTypeCompatibleBlock = new(big.Int).SetUint64(0)
    50  	config.MagmaCompatibleBlock = new(big.Int).SetUint64(0)
    51  	config.KoreCompatibleBlock = new(big.Int).SetUint64(0)
    52  
    53  	config.Istanbul.Epoch = 2
    54  	config.Istanbul.SubGroupSize = 1
    55  	config.Istanbul.ProposerPolicy = uint64(istanbul.RoundRobin)
    56  	config.Governance.Reward.MintingAmount = new(big.Int).Mul(big.NewInt(9000000000000000000), big.NewInt(params.KLAY))
    57  	config.Governance.Reward.Kip82Ratio = params.DefaultKip82Ratio
    58  
    59  	config.Governance.GovParamContract = common.Address{}
    60  	config.Governance.GovernanceMode = "none"
    61  
    62  	fullNode, node, validator, chainId, workspace := newBlockchain(t, config, nil)
    63  	defer os.RemoveAll(workspace)
    64  	defer fullNode.Stop()
    65  
    66  	var (
    67  		chain        = node.BlockChain().(*blockchain.BlockChain)
    68  		owner        = validator
    69  		contractAddr = crypto.CreateAddress(owner.Addr, owner.Nonce)
    70  
    71  		paramName  = "istanbul.committeesize"
    72  		oldVal     = config.Istanbul.SubGroupSize
    73  		newVal     = uint64(22)
    74  		paramBytes = []byte{22}
    75  
    76  		govBlock  uint64 // Before vote: 0, After vote: the governance block
    77  		stopBlock uint64 // Before govBlock is set: 0, After: the block to stop receiving new blocks
    78  	)
    79  
    80  	// Here we are running (tx sender) and (param reader) in parallel.
    81  	// This is to check that param reader (mixed engine) works in such situations:
    82  	// (a) contract engine disabled
    83  	// (b) contract engine enabled (via vote)
    84  
    85  	// Run tx sender thread
    86  	go func() {
    87  		deployGovParamTx_constructor(t, node, owner, chainId)
    88  
    89  		// Give some time for txpool to recognize the contract, because otherwise
    90  		// the txpool may reject the setParam tx with 'not a program account'
    91  		time.Sleep(2 * time.Second)
    92  
    93  		deployGovParamTx_setParamIn(t, node, owner, chainId, contractAddr, paramName, paramBytes)
    94  
    95  		node.Governance().AddVote("governance.govparamcontract", contractAddr)
    96  	}()
    97  
    98  	// Run param reader thread
    99  	mixedEngine := node.Governance()
   100  	contractEngine := node.Governance().ContractGov()
   101  
   102  	// Validate current params from mixedEngine.CurrentParams() & contractEngine.CurrentParams(),
   103  	// alongside block processing.
   104  	// At block #N, CurrentParams() returns the parameters to be used when building
   105  	// block #N+1 (i.e. pending block).
   106  	chainEventCh := make(chan blockchain.ChainEvent)
   107  	subscription := chain.SubscribeChainEvent(chainEventCh)
   108  	defer subscription.Unsubscribe()
   109  
   110  	// 1. test CurrentParams() while subscribing new blocks
   111  	for {
   112  		ev := <-chainEventCh
   113  		time.Sleep(100 * time.Millisecond) // wait for tx sender thread to set deployBlock, etc.
   114  
   115  		num := ev.Block.Number().Uint64()
   116  		mixedEngine.UpdateParams(num)
   117  
   118  		mixedVal, _ := mixedEngine.CurrentParams().Get(params.CommitteeSize)
   119  		contractVal, _ := contractEngine.CurrentParams().Get(params.CommitteeSize)
   120  
   121  		if len(ev.Block.Header().Governance) > 0 {
   122  			govBlock = num
   123  			// stopBlock is the epoch block, so we stop when receiving it
   124  			// otherwise, EffectiveParams(stopBlock) may fail
   125  			stopBlock = govBlock + 5
   126  			stopBlock = stopBlock - (stopBlock % config.Istanbul.Epoch)
   127  			t.Logf("Governance at block=%2d, stopBlock=%2d", num, stopBlock)
   128  		}
   129  
   130  		if govBlock == 0 || num <= govBlock { // ContractEngine disabled
   131  			assert.Equal(t, oldVal, mixedVal)
   132  			assert.Equal(t, nil, contractVal)
   133  		} else { // ContractEngine enabled
   134  			assert.Equal(t, newVal, mixedVal)
   135  			assert.Equal(t, newVal, contractVal)
   136  		}
   137  
   138  		if stopBlock != 0 && num >= stopBlock {
   139  			break
   140  		}
   141  
   142  		if num >= 60 {
   143  			t.Fatal("test taking too long; something must be wrong")
   144  		}
   145  	}
   146  
   147  	// 2. test EffectiveParams():  Validate historic params from both Engines
   148  	for num := uint64(0); num < stopBlock; num++ {
   149  		mixedpset, err := mixedEngine.EffectiveParams(num)
   150  		assert.Nil(t, err)
   151  		mixedVal, _ := mixedpset.Get(params.CommitteeSize)
   152  
   153  		contractpset, err := contractEngine.EffectiveParams(num)
   154  		assert.Nil(t, err)
   155  
   156  		if num <= govBlock+1 { // ContractEngine disabled
   157  			assert.Equal(t, oldVal, mixedVal)
   158  			assert.Equal(t, params.NewGovParamSet(), contractpset)
   159  		} else { // ContractEngine enabled
   160  			assert.Equal(t, newVal, mixedVal)
   161  			contractVal, _ := contractpset.Get(params.CommitteeSize)
   162  			assert.Equal(t, newVal, contractVal)
   163  		}
   164  	}
   165  }
   166  
   167  func deployGovParamTx_constructor(t *testing.T, node *cn.CN, owner *TestAccountType, chainId *big.Int,
   168  ) (uint64, common.Address, *types.Transaction) {
   169  	var (
   170  		// Deploy contract: constructor(address _owner)
   171  		contractAbi, _ = abi.JSON(strings.NewReader(govcontract.GovParamABI))
   172  		contractBin    = govcontract.GovParamBin
   173  		ctorArgs, _    = contractAbi.Pack("")
   174  		code           = contractBin + hex.EncodeToString(ctorArgs)
   175  	)
   176  
   177  	// Deploy contract
   178  	tx, addr := deployContractDeployTx(t, node.TxPool(), chainId, owner, code)
   179  
   180  	chain := node.BlockChain().(*blockchain.BlockChain)
   181  	receipt := waitReceipt(chain, tx.Hash())
   182  	require.NotNil(t, receipt)
   183  	require.Equal(t, types.ReceiptStatusSuccessful, receipt.Status)
   184  
   185  	_, _, num, _ := chain.GetTxAndLookupInfo(tx.Hash())
   186  	t.Logf("GovParam deployed at block=%2d, addr=%s", num, addr.Hex())
   187  
   188  	return num, addr, tx
   189  }
   190  
   191  func deployGovParamTx_setParamIn(t *testing.T, node *cn.CN, owner *TestAccountType, chainId *big.Int,
   192  	contractAddr common.Address, name string, value []byte,
   193  ) (uint64, *types.Transaction) {
   194  	var (
   195  		contractAbi, _ = abi.JSON(strings.NewReader(govcontract.GovParamABI))
   196  		callArgs, _    = contractAbi.Pack("setParamIn", name, true, value, big.NewInt(1))
   197  		data           = common.ToHex(callArgs)
   198  	)
   199  
   200  	tx := deployContractExecutionTx(t, node.TxPool(), chainId, owner, contractAddr, data)
   201  
   202  	chain := node.BlockChain().(*blockchain.BlockChain)
   203  	receipt := waitReceipt(chain, tx.Hash())
   204  	require.NotNil(t, receipt)
   205  	require.Equal(t, types.ReceiptStatusSuccessful, receipt.Status, "setParamIn failed")
   206  
   207  	_, _, num, _ := chain.GetTxAndLookupInfo(tx.Hash())
   208  	t.Logf("GovParam.setParamIn executed at block=%2d", num)
   209  	return num, tx
   210  }
   211  
   212  func deployGovParamTx_batchSetParamIn(t *testing.T, node *cn.CN, owner *TestAccountType, chainId *big.Int,
   213  	contractAddr common.Address, bytesMap map[string][]byte,
   214  ) []*types.Transaction {
   215  	var (
   216  		chain          = node.BlockChain().(*blockchain.BlockChain)
   217  		beginBlock     = chain.CurrentHeader().Number.Uint64()
   218  		contractAbi, _ = abi.JSON(strings.NewReader(govcontract.GovParamABI))
   219  		txs            = []*types.Transaction{}
   220  	)
   221  
   222  	// Send all setParamIn() calls at once
   223  	for name, value := range bytesMap {
   224  		callArgs, _ := contractAbi.Pack("setParamIn", name, true, value, big.NewInt(1))
   225  		data := common.ToHex(callArgs)
   226  		tx := deployContractExecutionTx(t, node.TxPool(), chainId, owner, contractAddr, data)
   227  		txs = append(txs, tx)
   228  	}
   229  
   230  	// Wait for all txs
   231  	for _, tx := range txs {
   232  		receipt := waitReceipt(chain, tx.Hash())
   233  		require.NotNil(t, receipt)
   234  		require.Equal(t, types.ReceiptStatusSuccessful, receipt.Status, "batchSetParamIn failed")
   235  	}
   236  	num := chain.CurrentHeader().Number.Uint64()
   237  	t.Logf("GovParam.setParamIn executed %d times between blocks=%2d,%2d", len(txs), beginBlock, num)
   238  	return txs
   239  }
   240  
   241  // Klaytn node only decodes the byte-array param values (refer to params/governance_paramset.go).
   242  // Encoding is the job of transaction senders (i.e. clients and dApps).
   243  // This is a reference implementation of such encoder.
   244  func chainConfigToBytesMap(t *testing.T, config *params.ChainConfig) map[string][]byte {
   245  	pset, err := params.NewGovParamSetChainConfig(config)
   246  	require.Nil(t, err)
   247  	strMap := pset.StrMap()
   248  
   249  	bytesMap := map[string][]byte{}
   250  	for name, value := range strMap {
   251  		switch value.(type) {
   252  		case string:
   253  			bytesMap[name] = []byte(value.(string))
   254  		case common.Address:
   255  			bytesMap[name] = value.(common.Address).Bytes()
   256  		case uint64:
   257  			bytesMap[name] = new(big.Int).SetUint64(value.(uint64)).Bytes()
   258  		case bool:
   259  			if value.(bool) == true {
   260  				bytesMap[name] = []byte{0x01}
   261  			} else {
   262  				bytesMap[name] = []byte{0x00}
   263  			}
   264  		}
   265  	}
   266  
   267  	// Check that bytesMap is correct just in case
   268  	qset, err := params.NewGovParamSetBytesMap(bytesMap)
   269  	require.Nil(t, err)
   270  	require.Equal(t, pset.StrMap(), qset.StrMap())
   271  	return bytesMap
   272  }