github.com/MetalBlockchain/metalgo@v1.11.9/vms/platformvm/block/executor/helpers_test.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package executor
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/prometheus/client_golang/prometheus"
    13  	"github.com/stretchr/testify/require"
    14  	"go.uber.org/mock/gomock"
    15  
    16  	"github.com/MetalBlockchain/metalgo/chains"
    17  	"github.com/MetalBlockchain/metalgo/chains/atomic"
    18  	"github.com/MetalBlockchain/metalgo/codec"
    19  	"github.com/MetalBlockchain/metalgo/codec/linearcodec"
    20  	"github.com/MetalBlockchain/metalgo/database"
    21  	"github.com/MetalBlockchain/metalgo/database/memdb"
    22  	"github.com/MetalBlockchain/metalgo/database/prefixdb"
    23  	"github.com/MetalBlockchain/metalgo/database/versiondb"
    24  	"github.com/MetalBlockchain/metalgo/ids"
    25  	"github.com/MetalBlockchain/metalgo/snow"
    26  	"github.com/MetalBlockchain/metalgo/snow/engine/common"
    27  	"github.com/MetalBlockchain/metalgo/snow/snowtest"
    28  	"github.com/MetalBlockchain/metalgo/snow/uptime"
    29  	"github.com/MetalBlockchain/metalgo/snow/validators"
    30  	"github.com/MetalBlockchain/metalgo/utils"
    31  	"github.com/MetalBlockchain/metalgo/utils/constants"
    32  	"github.com/MetalBlockchain/metalgo/utils/crypto/secp256k1"
    33  	"github.com/MetalBlockchain/metalgo/utils/formatting"
    34  	"github.com/MetalBlockchain/metalgo/utils/formatting/address"
    35  	"github.com/MetalBlockchain/metalgo/utils/json"
    36  	"github.com/MetalBlockchain/metalgo/utils/logging"
    37  	"github.com/MetalBlockchain/metalgo/utils/timer/mockable"
    38  	"github.com/MetalBlockchain/metalgo/utils/units"
    39  	"github.com/MetalBlockchain/metalgo/vms/platformvm/api"
    40  	"github.com/MetalBlockchain/metalgo/vms/platformvm/config"
    41  	"github.com/MetalBlockchain/metalgo/vms/platformvm/fx"
    42  	"github.com/MetalBlockchain/metalgo/vms/platformvm/metrics"
    43  	"github.com/MetalBlockchain/metalgo/vms/platformvm/reward"
    44  	"github.com/MetalBlockchain/metalgo/vms/platformvm/state"
    45  	"github.com/MetalBlockchain/metalgo/vms/platformvm/status"
    46  	"github.com/MetalBlockchain/metalgo/vms/platformvm/txs"
    47  	"github.com/MetalBlockchain/metalgo/vms/platformvm/txs/executor"
    48  	"github.com/MetalBlockchain/metalgo/vms/platformvm/txs/fee"
    49  	"github.com/MetalBlockchain/metalgo/vms/platformvm/txs/mempool"
    50  	"github.com/MetalBlockchain/metalgo/vms/platformvm/txs/txstest"
    51  	"github.com/MetalBlockchain/metalgo/vms/platformvm/upgrade"
    52  	"github.com/MetalBlockchain/metalgo/vms/platformvm/utxo"
    53  	"github.com/MetalBlockchain/metalgo/vms/secp256k1fx"
    54  
    55  	pvalidators "github.com/MetalBlockchain/metalgo/vms/platformvm/validators"
    56  	walletsigner "github.com/MetalBlockchain/metalgo/wallet/chain/p/signer"
    57  	walletcommon "github.com/MetalBlockchain/metalgo/wallet/subnet/primary/common"
    58  )
    59  
    60  const (
    61  	pending stakerStatus = iota
    62  	current
    63  
    64  	defaultWeight = 10000
    65  	trackChecksum = false
    66  
    67  	apricotPhase3 fork = iota
    68  	apricotPhase5
    69  	banff
    70  	cortina
    71  	durango
    72  	eUpgrade
    73  )
    74  
    75  var (
    76  	defaultMinStakingDuration = 24 * time.Hour
    77  	defaultMaxStakingDuration = 365 * 24 * time.Hour
    78  	defaultGenesisTime        = time.Date(1997, 1, 1, 0, 0, 0, 0, time.UTC)
    79  	defaultValidateStartTime  = defaultGenesisTime
    80  	defaultValidateEndTime    = defaultValidateStartTime.Add(10 * defaultMinStakingDuration)
    81  	defaultMinValidatorStake  = 5 * units.MilliAvax
    82  	defaultBalance            = 100 * defaultMinValidatorStake
    83  	preFundedKeys             = secp256k1.TestKeys()
    84  	avaxAssetID               = ids.ID{'y', 'e', 'e', 't'}
    85  	defaultTxFee              = uint64(100)
    86  
    87  	genesisBlkID ids.ID
    88  	testSubnet1  *txs.Tx
    89  
    90  	// Node IDs of genesis validators. Initialized in init function
    91  	genesisNodeIDs []ids.NodeID
    92  )
    93  
    94  func init() {
    95  	genesisNodeIDs = make([]ids.NodeID, len(preFundedKeys))
    96  	for i := range preFundedKeys {
    97  		genesisNodeIDs[i] = ids.GenerateTestNodeID()
    98  	}
    99  }
   100  
   101  type stakerStatus uint
   102  
   103  type fork uint8
   104  
   105  type staker struct {
   106  	nodeID             ids.NodeID
   107  	rewardAddress      ids.ShortID
   108  	startTime, endTime time.Time
   109  }
   110  
   111  type test struct {
   112  	description           string
   113  	stakers               []staker
   114  	subnetStakers         []staker
   115  	advanceTimeTo         []time.Time
   116  	expectedStakers       map[ids.NodeID]stakerStatus
   117  	expectedSubnetStakers map[ids.NodeID]stakerStatus
   118  }
   119  
   120  type environment struct {
   121  	blkManager Manager
   122  	mempool    mempool.Mempool
   123  	sender     *common.SenderTest
   124  
   125  	isBootstrapped *utils.Atomic[bool]
   126  	config         *config.Config
   127  	clk            *mockable.Clock
   128  	baseDB         *versiondb.Database
   129  	ctx            *snow.Context
   130  	fx             fx.Fx
   131  	state          state.State
   132  	mockedState    *state.MockState
   133  	uptimes        uptime.Manager
   134  	utxosVerifier  utxo.Verifier
   135  	factory        *txstest.WalletFactory
   136  	backend        *executor.Backend
   137  }
   138  
   139  func newEnvironment(t *testing.T, ctrl *gomock.Controller, f fork) *environment {
   140  	res := &environment{
   141  		isBootstrapped: &utils.Atomic[bool]{},
   142  		config:         defaultConfig(t, f),
   143  		clk:            defaultClock(),
   144  	}
   145  	res.isBootstrapped.Set(true)
   146  
   147  	res.baseDB = versiondb.New(memdb.New())
   148  	atomicDB := prefixdb.New([]byte{1}, res.baseDB)
   149  	m := atomic.NewMemory(atomicDB)
   150  
   151  	res.ctx = snowtest.Context(t, snowtest.PChainID)
   152  	res.ctx.AVAXAssetID = avaxAssetID
   153  	res.ctx.SharedMemory = m.NewSharedMemory(res.ctx.ChainID)
   154  
   155  	res.fx = defaultFx(res.clk, res.ctx.Log, res.isBootstrapped.Get())
   156  
   157  	rewardsCalc := reward.NewCalculator(res.config.RewardConfig)
   158  
   159  	if ctrl == nil {
   160  		res.state = defaultState(res.config, res.ctx, res.baseDB, rewardsCalc)
   161  		res.uptimes = uptime.NewManager(res.state, res.clk)
   162  		res.utxosVerifier = utxo.NewVerifier(res.ctx, res.clk, res.fx)
   163  		res.factory = txstest.NewWalletFactory(
   164  			res.ctx,
   165  			res.config,
   166  			res.state,
   167  		)
   168  	} else {
   169  		genesisBlkID = ids.GenerateTestID()
   170  		res.mockedState = state.NewMockState(ctrl)
   171  		res.uptimes = uptime.NewManager(res.mockedState, res.clk)
   172  		res.utxosVerifier = utxo.NewVerifier(res.ctx, res.clk, res.fx)
   173  		res.factory = txstest.NewWalletFactory(
   174  			res.ctx,
   175  			res.config,
   176  			res.mockedState,
   177  		)
   178  
   179  		// setup expectations strictly needed for environment creation
   180  		res.mockedState.EXPECT().GetLastAccepted().Return(genesisBlkID).Times(1)
   181  	}
   182  
   183  	res.backend = &executor.Backend{
   184  		Config:       res.config,
   185  		Ctx:          res.ctx,
   186  		Clk:          res.clk,
   187  		Bootstrapped: res.isBootstrapped,
   188  		Fx:           res.fx,
   189  		FlowChecker:  res.utxosVerifier,
   190  		Uptimes:      res.uptimes,
   191  		Rewards:      rewardsCalc,
   192  	}
   193  
   194  	registerer := prometheus.NewRegistry()
   195  	res.sender = &common.SenderTest{T: t}
   196  
   197  	metrics := metrics.Noop
   198  
   199  	var err error
   200  	res.mempool, err = mempool.New("mempool", registerer, nil)
   201  	if err != nil {
   202  		panic(fmt.Errorf("failed to create mempool: %w", err))
   203  	}
   204  
   205  	if ctrl == nil {
   206  		res.blkManager = NewManager(
   207  			res.mempool,
   208  			metrics,
   209  			res.state,
   210  			res.backend,
   211  			pvalidators.TestManager,
   212  		)
   213  		addSubnet(res)
   214  	} else {
   215  		res.blkManager = NewManager(
   216  			res.mempool,
   217  			metrics,
   218  			res.mockedState,
   219  			res.backend,
   220  			pvalidators.TestManager,
   221  		)
   222  		// we do not add any subnet to state, since we can mock
   223  		// whatever we need
   224  	}
   225  
   226  	t.Cleanup(func() {
   227  		res.ctx.Lock.Lock()
   228  		defer res.ctx.Lock.Unlock()
   229  
   230  		if res.mockedState != nil {
   231  			// state is mocked, nothing to do here
   232  			return
   233  		}
   234  
   235  		require := require.New(t)
   236  
   237  		if res.isBootstrapped.Get() {
   238  			validatorIDs := res.config.Validators.GetValidatorIDs(constants.PrimaryNetworkID)
   239  
   240  			require.NoError(res.uptimes.StopTracking(validatorIDs, constants.PrimaryNetworkID))
   241  			require.NoError(res.state.Commit())
   242  		}
   243  
   244  		if res.state != nil {
   245  			require.NoError(res.state.Close())
   246  		}
   247  
   248  		require.NoError(res.baseDB.Close())
   249  	})
   250  
   251  	return res
   252  }
   253  
   254  func addSubnet(env *environment) {
   255  	builder, signer := env.factory.NewWallet(preFundedKeys[0])
   256  	utx, err := builder.NewCreateSubnetTx(
   257  		&secp256k1fx.OutputOwners{
   258  			Threshold: 2,
   259  			Addrs: []ids.ShortID{
   260  				preFundedKeys[0].PublicKey().Address(),
   261  				preFundedKeys[1].PublicKey().Address(),
   262  				preFundedKeys[2].PublicKey().Address(),
   263  			},
   264  		},
   265  		walletcommon.WithChangeOwner(&secp256k1fx.OutputOwners{
   266  			Threshold: 1,
   267  			Addrs:     []ids.ShortID{preFundedKeys[0].PublicKey().Address()},
   268  		}),
   269  	)
   270  	if err != nil {
   271  		panic(err)
   272  	}
   273  	testSubnet1, err = walletsigner.SignUnsigned(context.Background(), signer, utx)
   274  	if err != nil {
   275  		panic(err)
   276  	}
   277  
   278  	genesisID := env.state.GetLastAccepted()
   279  	stateDiff, err := state.NewDiff(genesisID, env.blkManager)
   280  	if err != nil {
   281  		panic(err)
   282  	}
   283  
   284  	executor := executor.StandardTxExecutor{
   285  		Backend: env.backend,
   286  		State:   stateDiff,
   287  		Tx:      testSubnet1,
   288  	}
   289  	err = testSubnet1.Unsigned.Visit(&executor)
   290  	if err != nil {
   291  		panic(err)
   292  	}
   293  
   294  	stateDiff.AddTx(testSubnet1, status.Committed)
   295  	if err := stateDiff.Apply(env.state); err != nil {
   296  		panic(err)
   297  	}
   298  }
   299  
   300  func defaultState(
   301  	cfg *config.Config,
   302  	ctx *snow.Context,
   303  	db database.Database,
   304  	rewards reward.Calculator,
   305  ) state.State {
   306  	genesisBytes := buildGenesisTest(ctx)
   307  	execCfg, _ := config.GetExecutionConfig([]byte(`{}`))
   308  	state, err := state.New(
   309  		db,
   310  		genesisBytes,
   311  		prometheus.NewRegistry(),
   312  		cfg,
   313  		execCfg,
   314  		ctx,
   315  		metrics.Noop,
   316  		rewards,
   317  	)
   318  	if err != nil {
   319  		panic(err)
   320  	}
   321  
   322  	// persist and reload to init a bunch of in-memory stuff
   323  	state.SetHeight(0)
   324  	if err := state.Commit(); err != nil {
   325  		panic(err)
   326  	}
   327  	genesisBlkID = state.GetLastAccepted()
   328  	return state
   329  }
   330  
   331  func defaultConfig(t *testing.T, f fork) *config.Config {
   332  	c := &config.Config{
   333  		Chains:                 chains.TestManager,
   334  		UptimeLockedCalculator: uptime.NewLockedCalculator(),
   335  		Validators:             validators.NewManager(),
   336  		StaticFeeConfig: fee.StaticConfig{
   337  			TxFee:                 defaultTxFee,
   338  			CreateSubnetTxFee:     100 * defaultTxFee,
   339  			CreateBlockchainTxFee: 100 * defaultTxFee,
   340  		},
   341  		MinValidatorStake: 5 * units.MilliAvax,
   342  		MaxValidatorStake: 500 * units.MilliAvax,
   343  		MinDelegatorStake: 1 * units.MilliAvax,
   344  		MinStakeDuration:  defaultMinStakingDuration,
   345  		MaxStakeDuration:  defaultMaxStakingDuration,
   346  		RewardConfig: reward.Config{
   347  			MaxConsumptionRate: .12 * reward.PercentDenominator,
   348  			MinConsumptionRate: .10 * reward.PercentDenominator,
   349  			MintingPeriod:      365 * 24 * time.Hour,
   350  			SupplyCap:          720 * units.MegaAvax,
   351  		},
   352  		UpgradeConfig: upgrade.Config{
   353  			ApricotPhase3Time: mockable.MaxTime,
   354  			ApricotPhase5Time: mockable.MaxTime,
   355  			BanffTime:         mockable.MaxTime,
   356  			CortinaTime:       mockable.MaxTime,
   357  			DurangoTime:       mockable.MaxTime,
   358  			EUpgradeTime:      mockable.MaxTime,
   359  		},
   360  	}
   361  
   362  	switch f {
   363  	case eUpgrade:
   364  		c.UpgradeConfig.EUpgradeTime = time.Time{} // neglecting fork ordering this for package tests
   365  		fallthrough
   366  	case durango:
   367  		c.UpgradeConfig.DurangoTime = time.Time{} // neglecting fork ordering for this package's tests
   368  		fallthrough
   369  	case cortina:
   370  		c.UpgradeConfig.CortinaTime = time.Time{} // neglecting fork ordering for this package's tests
   371  		fallthrough
   372  	case banff:
   373  		c.UpgradeConfig.BanffTime = time.Time{} // neglecting fork ordering for this package's tests
   374  		fallthrough
   375  	case apricotPhase5:
   376  		c.UpgradeConfig.ApricotPhase5Time = defaultValidateEndTime
   377  		fallthrough
   378  	case apricotPhase3:
   379  		c.UpgradeConfig.ApricotPhase3Time = defaultValidateEndTime
   380  	default:
   381  		require.FailNow(t, "unhandled fork", f)
   382  	}
   383  
   384  	return c
   385  }
   386  
   387  func defaultClock() *mockable.Clock {
   388  	clk := &mockable.Clock{}
   389  	clk.Set(defaultGenesisTime)
   390  	return clk
   391  }
   392  
   393  type fxVMInt struct {
   394  	registry codec.Registry
   395  	clk      *mockable.Clock
   396  	log      logging.Logger
   397  }
   398  
   399  func (fvi *fxVMInt) CodecRegistry() codec.Registry {
   400  	return fvi.registry
   401  }
   402  
   403  func (fvi *fxVMInt) Clock() *mockable.Clock {
   404  	return fvi.clk
   405  }
   406  
   407  func (fvi *fxVMInt) Logger() logging.Logger {
   408  	return fvi.log
   409  }
   410  
   411  func defaultFx(clk *mockable.Clock, log logging.Logger, isBootstrapped bool) fx.Fx {
   412  	fxVMInt := &fxVMInt{
   413  		registry: linearcodec.NewDefault(),
   414  		clk:      clk,
   415  		log:      log,
   416  	}
   417  	res := &secp256k1fx.Fx{}
   418  	if err := res.Initialize(fxVMInt); err != nil {
   419  		panic(err)
   420  	}
   421  	if isBootstrapped {
   422  		if err := res.Bootstrapped(); err != nil {
   423  			panic(err)
   424  		}
   425  	}
   426  	return res
   427  }
   428  
   429  func buildGenesisTest(ctx *snow.Context) []byte {
   430  	genesisUTXOs := make([]api.UTXO, len(preFundedKeys))
   431  	for i, key := range preFundedKeys {
   432  		id := key.PublicKey().Address()
   433  		addr, err := address.FormatBech32(constants.UnitTestHRP, id.Bytes())
   434  		if err != nil {
   435  			panic(err)
   436  		}
   437  		genesisUTXOs[i] = api.UTXO{
   438  			Amount:  json.Uint64(defaultBalance),
   439  			Address: addr,
   440  		}
   441  	}
   442  
   443  	genesisValidators := make([]api.GenesisPermissionlessValidator, len(genesisNodeIDs))
   444  	for i, nodeID := range genesisNodeIDs {
   445  		addr, err := address.FormatBech32(constants.UnitTestHRP, nodeID.Bytes())
   446  		if err != nil {
   447  			panic(err)
   448  		}
   449  		genesisValidators[i] = api.GenesisPermissionlessValidator{
   450  			GenesisValidator: api.GenesisValidator{
   451  				StartTime: json.Uint64(defaultValidateStartTime.Unix()),
   452  				EndTime:   json.Uint64(defaultValidateEndTime.Unix()),
   453  				NodeID:    nodeID,
   454  			},
   455  			RewardOwner: &api.Owner{
   456  				Threshold: 1,
   457  				Addresses: []string{addr},
   458  			},
   459  			Staked: []api.UTXO{{
   460  				Amount:  json.Uint64(defaultWeight),
   461  				Address: addr,
   462  			}},
   463  			DelegationFee: reward.PercentDenominator,
   464  		}
   465  	}
   466  
   467  	buildGenesisArgs := api.BuildGenesisArgs{
   468  		NetworkID:     json.Uint32(constants.UnitTestID),
   469  		AvaxAssetID:   ctx.AVAXAssetID,
   470  		UTXOs:         genesisUTXOs,
   471  		Validators:    genesisValidators,
   472  		Chains:        nil,
   473  		Time:          json.Uint64(defaultGenesisTime.Unix()),
   474  		InitialSupply: json.Uint64(360 * units.MegaAvax),
   475  		Encoding:      formatting.Hex,
   476  	}
   477  
   478  	buildGenesisResponse := api.BuildGenesisReply{}
   479  	platformvmSS := api.StaticService{}
   480  	if err := platformvmSS.BuildGenesis(nil, &buildGenesisArgs, &buildGenesisResponse); err != nil {
   481  		panic(fmt.Errorf("problem while building platform chain's genesis state: %w", err))
   482  	}
   483  
   484  	genesisBytes, err := formatting.Decode(buildGenesisResponse.Encoding, buildGenesisResponse.Bytes)
   485  	if err != nil {
   486  		panic(err)
   487  	}
   488  
   489  	return genesisBytes
   490  }
   491  
   492  func addPendingValidator(
   493  	env *environment,
   494  	startTime time.Time,
   495  	endTime time.Time,
   496  	nodeID ids.NodeID,
   497  	rewardAddress ids.ShortID,
   498  	keys []*secp256k1.PrivateKey,
   499  ) (*txs.Tx, error) {
   500  	builder, signer := env.factory.NewWallet(keys...)
   501  	utx, err := builder.NewAddValidatorTx(
   502  		&txs.Validator{
   503  			NodeID: nodeID,
   504  			Start:  uint64(startTime.Unix()),
   505  			End:    uint64(endTime.Unix()),
   506  			Wght:   env.config.MinValidatorStake,
   507  		},
   508  		&secp256k1fx.OutputOwners{
   509  			Threshold: 1,
   510  			Addrs:     []ids.ShortID{rewardAddress},
   511  		},
   512  		reward.PercentDenominator,
   513  	)
   514  	if err != nil {
   515  		return nil, err
   516  	}
   517  	addPendingValidatorTx, err := walletsigner.SignUnsigned(context.Background(), signer, utx)
   518  	if err != nil {
   519  		return nil, err
   520  	}
   521  
   522  	staker, err := state.NewPendingStaker(
   523  		addPendingValidatorTx.ID(),
   524  		addPendingValidatorTx.Unsigned.(*txs.AddValidatorTx),
   525  	)
   526  	if err != nil {
   527  		return nil, err
   528  	}
   529  
   530  	env.state.PutPendingValidator(staker)
   531  	env.state.AddTx(addPendingValidatorTx, status.Committed)
   532  	dummyHeight := uint64(1)
   533  	env.state.SetHeight(dummyHeight)
   534  	if err := env.state.Commit(); err != nil {
   535  		return nil, err
   536  	}
   537  	return addPendingValidatorTx, nil
   538  }