github.com/ava-labs/avalanchego@v1.11.11/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  	"fmt"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/prometheus/client_golang/prometheus"
    12  	"github.com/stretchr/testify/require"
    13  	"go.uber.org/mock/gomock"
    14  
    15  	"github.com/ava-labs/avalanchego/chains"
    16  	"github.com/ava-labs/avalanchego/chains/atomic"
    17  	"github.com/ava-labs/avalanchego/codec"
    18  	"github.com/ava-labs/avalanchego/codec/linearcodec"
    19  	"github.com/ava-labs/avalanchego/database/memdb"
    20  	"github.com/ava-labs/avalanchego/database/prefixdb"
    21  	"github.com/ava-labs/avalanchego/database/versiondb"
    22  	"github.com/ava-labs/avalanchego/ids"
    23  	"github.com/ava-labs/avalanchego/snow"
    24  	"github.com/ava-labs/avalanchego/snow/engine/enginetest"
    25  	"github.com/ava-labs/avalanchego/snow/snowtest"
    26  	"github.com/ava-labs/avalanchego/snow/uptime"
    27  	"github.com/ava-labs/avalanchego/snow/validators"
    28  	"github.com/ava-labs/avalanchego/upgrade/upgradetest"
    29  	"github.com/ava-labs/avalanchego/utils"
    30  	"github.com/ava-labs/avalanchego/utils/constants"
    31  	"github.com/ava-labs/avalanchego/utils/crypto/secp256k1"
    32  	"github.com/ava-labs/avalanchego/utils/logging"
    33  	"github.com/ava-labs/avalanchego/utils/timer/mockable"
    34  	"github.com/ava-labs/avalanchego/utils/units"
    35  	"github.com/ava-labs/avalanchego/vms/platformvm/config"
    36  	"github.com/ava-labs/avalanchego/vms/platformvm/fx"
    37  	"github.com/ava-labs/avalanchego/vms/platformvm/genesis/genesistest"
    38  	"github.com/ava-labs/avalanchego/vms/platformvm/metrics"
    39  	"github.com/ava-labs/avalanchego/vms/platformvm/reward"
    40  	"github.com/ava-labs/avalanchego/vms/platformvm/state"
    41  	"github.com/ava-labs/avalanchego/vms/platformvm/state/statetest"
    42  	"github.com/ava-labs/avalanchego/vms/platformvm/status"
    43  	"github.com/ava-labs/avalanchego/vms/platformvm/txs"
    44  	"github.com/ava-labs/avalanchego/vms/platformvm/txs/executor"
    45  	"github.com/ava-labs/avalanchego/vms/platformvm/txs/fee"
    46  	"github.com/ava-labs/avalanchego/vms/platformvm/txs/mempool"
    47  	"github.com/ava-labs/avalanchego/vms/platformvm/txs/txstest"
    48  	"github.com/ava-labs/avalanchego/vms/platformvm/utxo"
    49  	"github.com/ava-labs/avalanchego/vms/secp256k1fx"
    50  	"github.com/ava-labs/avalanchego/wallet/chain/p/wallet"
    51  
    52  	pvalidators "github.com/ava-labs/avalanchego/vms/platformvm/validators"
    53  )
    54  
    55  const (
    56  	pending stakerStatus = iota
    57  	current
    58  
    59  	defaultMinStakingDuration = 24 * time.Hour
    60  	defaultMaxStakingDuration = 365 * 24 * time.Hour
    61  
    62  	defaultTxFee = 100 * units.NanoAvax
    63  )
    64  
    65  var testSubnet1 *txs.Tx
    66  
    67  type stakerStatus uint
    68  
    69  type staker struct {
    70  	nodeID             ids.NodeID
    71  	rewardAddress      ids.ShortID
    72  	startTime, endTime time.Time
    73  }
    74  
    75  type test struct {
    76  	description           string
    77  	stakers               []staker
    78  	subnetStakers         []staker
    79  	advanceTimeTo         []time.Time
    80  	expectedStakers       map[ids.NodeID]stakerStatus
    81  	expectedSubnetStakers map[ids.NodeID]stakerStatus
    82  }
    83  
    84  type environment struct {
    85  	blkManager Manager
    86  	mempool    mempool.Mempool
    87  	sender     *enginetest.Sender
    88  
    89  	isBootstrapped *utils.Atomic[bool]
    90  	config         *config.Config
    91  	clk            *mockable.Clock
    92  	baseDB         *versiondb.Database
    93  	ctx            *snow.Context
    94  	fx             fx.Fx
    95  	state          state.State
    96  	mockedState    *state.MockState
    97  	uptimes        uptime.Manager
    98  	utxosVerifier  utxo.Verifier
    99  	backend        *executor.Backend
   100  }
   101  
   102  func newEnvironment(t *testing.T, ctrl *gomock.Controller, f upgradetest.Fork) *environment {
   103  	res := &environment{
   104  		isBootstrapped: &utils.Atomic[bool]{},
   105  		config:         defaultConfig(f),
   106  		clk:            defaultClock(),
   107  	}
   108  	res.isBootstrapped.Set(true)
   109  
   110  	res.baseDB = versiondb.New(memdb.New())
   111  	atomicDB := prefixdb.New([]byte{1}, res.baseDB)
   112  	m := atomic.NewMemory(atomicDB)
   113  
   114  	res.ctx = snowtest.Context(t, snowtest.PChainID)
   115  	res.ctx.SharedMemory = m.NewSharedMemory(res.ctx.ChainID)
   116  
   117  	res.fx = defaultFx(res.clk, res.ctx.Log, res.isBootstrapped.Get())
   118  
   119  	rewardsCalc := reward.NewCalculator(res.config.RewardConfig)
   120  
   121  	if ctrl == nil {
   122  		res.state = statetest.New(t, statetest.Config{
   123  			DB:         res.baseDB,
   124  			Genesis:    genesistest.NewBytes(t, genesistest.Config{}),
   125  			Validators: res.config.Validators,
   126  			Context:    res.ctx,
   127  			Rewards:    rewardsCalc,
   128  		})
   129  
   130  		res.uptimes = uptime.NewManager(res.state, res.clk)
   131  		res.utxosVerifier = utxo.NewVerifier(res.ctx, res.clk, res.fx)
   132  	} else {
   133  		res.mockedState = state.NewMockState(ctrl)
   134  		res.uptimes = uptime.NewManager(res.mockedState, res.clk)
   135  		res.utxosVerifier = utxo.NewVerifier(res.ctx, res.clk, res.fx)
   136  
   137  		// setup expectations strictly needed for environment creation
   138  		res.mockedState.EXPECT().GetLastAccepted().Return(ids.GenerateTestID()).Times(1)
   139  	}
   140  
   141  	res.backend = &executor.Backend{
   142  		Config:       res.config,
   143  		Ctx:          res.ctx,
   144  		Clk:          res.clk,
   145  		Bootstrapped: res.isBootstrapped,
   146  		Fx:           res.fx,
   147  		FlowChecker:  res.utxosVerifier,
   148  		Uptimes:      res.uptimes,
   149  		Rewards:      rewardsCalc,
   150  	}
   151  
   152  	registerer := prometheus.NewRegistry()
   153  	res.sender = &enginetest.Sender{T: t}
   154  
   155  	metrics := metrics.Noop
   156  
   157  	var err error
   158  	res.mempool, err = mempool.New("mempool", registerer, nil)
   159  	if err != nil {
   160  		panic(fmt.Errorf("failed to create mempool: %w", err))
   161  	}
   162  
   163  	if ctrl == nil {
   164  		res.blkManager = NewManager(
   165  			res.mempool,
   166  			metrics,
   167  			res.state,
   168  			res.backend,
   169  			pvalidators.TestManager,
   170  		)
   171  		addSubnet(t, res)
   172  	} else {
   173  		res.blkManager = NewManager(
   174  			res.mempool,
   175  			metrics,
   176  			res.mockedState,
   177  			res.backend,
   178  			pvalidators.TestManager,
   179  		)
   180  		// we do not add any subnet to state, since we can mock
   181  		// whatever we need
   182  	}
   183  
   184  	t.Cleanup(func() {
   185  		res.ctx.Lock.Lock()
   186  		defer res.ctx.Lock.Unlock()
   187  
   188  		if res.mockedState != nil {
   189  			// state is mocked, nothing to do here
   190  			return
   191  		}
   192  
   193  		require := require.New(t)
   194  
   195  		if res.isBootstrapped.Get() {
   196  			validatorIDs := res.config.Validators.GetValidatorIDs(constants.PrimaryNetworkID)
   197  
   198  			require.NoError(res.uptimes.StopTracking(validatorIDs, constants.PrimaryNetworkID))
   199  			require.NoError(res.state.Commit())
   200  		}
   201  
   202  		if res.state != nil {
   203  			require.NoError(res.state.Close())
   204  		}
   205  
   206  		require.NoError(res.baseDB.Close())
   207  	})
   208  
   209  	return res
   210  }
   211  
   212  type walletConfig struct {
   213  	keys      []*secp256k1.PrivateKey
   214  	subnetIDs []ids.ID
   215  }
   216  
   217  func newWallet(t testing.TB, e *environment, c walletConfig) wallet.Wallet {
   218  	if len(c.keys) == 0 {
   219  		c.keys = genesistest.DefaultFundedKeys
   220  	}
   221  	return txstest.NewWallet(
   222  		t,
   223  		e.ctx,
   224  		e.config,
   225  		e.state,
   226  		secp256k1fx.NewKeychain(c.keys...),
   227  		c.subnetIDs,
   228  		[]ids.ID{e.ctx.CChainID, e.ctx.XChainID},
   229  	)
   230  }
   231  
   232  func addSubnet(t testing.TB, env *environment) {
   233  	require := require.New(t)
   234  
   235  	wallet := newWallet(t, env, walletConfig{
   236  		keys: genesistest.DefaultFundedKeys[:1],
   237  	})
   238  
   239  	var err error
   240  	testSubnet1, err = wallet.IssueCreateSubnetTx(
   241  		&secp256k1fx.OutputOwners{
   242  			Threshold: 2,
   243  			Addrs: []ids.ShortID{
   244  				genesistest.DefaultFundedKeys[0].Address(),
   245  				genesistest.DefaultFundedKeys[1].Address(),
   246  				genesistest.DefaultFundedKeys[2].Address(),
   247  			},
   248  		},
   249  	)
   250  	require.NoError(err)
   251  
   252  	genesisID := env.state.GetLastAccepted()
   253  	stateDiff, err := state.NewDiff(genesisID, env.blkManager)
   254  	require.NoError(err)
   255  
   256  	feeCalculator := state.PickFeeCalculator(env.config, stateDiff)
   257  	executor := executor.StandardTxExecutor{
   258  		Backend:       env.backend,
   259  		State:         stateDiff,
   260  		FeeCalculator: feeCalculator,
   261  		Tx:            testSubnet1,
   262  	}
   263  	require.NoError(testSubnet1.Unsigned.Visit(&executor))
   264  
   265  	stateDiff.AddTx(testSubnet1, status.Committed)
   266  	require.NoError(stateDiff.Apply(env.state))
   267  	require.NoError(env.state.Commit())
   268  }
   269  
   270  func defaultConfig(f upgradetest.Fork) *config.Config {
   271  	upgrades := upgradetest.GetConfigWithUpgradeTime(f, time.Time{})
   272  	// This package neglects fork ordering
   273  	upgradetest.SetTimesTo(
   274  		&upgrades,
   275  		min(f, upgradetest.ApricotPhase5),
   276  		genesistest.DefaultValidatorEndTime,
   277  	)
   278  
   279  	return &config.Config{
   280  		Chains:                 chains.TestManager,
   281  		UptimeLockedCalculator: uptime.NewLockedCalculator(),
   282  		Validators:             validators.NewManager(),
   283  		StaticFeeConfig: fee.StaticConfig{
   284  			TxFee:                 defaultTxFee,
   285  			CreateSubnetTxFee:     100 * defaultTxFee,
   286  			CreateBlockchainTxFee: 100 * defaultTxFee,
   287  		},
   288  		MinValidatorStake: 5 * units.MilliAvax,
   289  		MaxValidatorStake: 500 * units.MilliAvax,
   290  		MinDelegatorStake: 1 * units.MilliAvax,
   291  		MinStakeDuration:  defaultMinStakingDuration,
   292  		MaxStakeDuration:  defaultMaxStakingDuration,
   293  		RewardConfig: reward.Config{
   294  			MaxConsumptionRate: .12 * reward.PercentDenominator,
   295  			MinConsumptionRate: .10 * reward.PercentDenominator,
   296  			MintingPeriod:      365 * 24 * time.Hour,
   297  			SupplyCap:          720 * units.MegaAvax,
   298  		},
   299  		UpgradeConfig: upgrades,
   300  	}
   301  }
   302  
   303  func defaultClock() *mockable.Clock {
   304  	clk := &mockable.Clock{}
   305  	clk.Set(genesistest.DefaultValidatorStartTime)
   306  	return clk
   307  }
   308  
   309  type fxVMInt struct {
   310  	registry codec.Registry
   311  	clk      *mockable.Clock
   312  	log      logging.Logger
   313  }
   314  
   315  func (fvi *fxVMInt) CodecRegistry() codec.Registry {
   316  	return fvi.registry
   317  }
   318  
   319  func (fvi *fxVMInt) Clock() *mockable.Clock {
   320  	return fvi.clk
   321  }
   322  
   323  func (fvi *fxVMInt) Logger() logging.Logger {
   324  	return fvi.log
   325  }
   326  
   327  func defaultFx(clk *mockable.Clock, log logging.Logger, isBootstrapped bool) fx.Fx {
   328  	fxVMInt := &fxVMInt{
   329  		registry: linearcodec.NewDefault(),
   330  		clk:      clk,
   331  		log:      log,
   332  	}
   333  	res := &secp256k1fx.Fx{}
   334  	if err := res.Initialize(fxVMInt); err != nil {
   335  		panic(err)
   336  	}
   337  	if isBootstrapped {
   338  		if err := res.Bootstrapped(); err != nil {
   339  			panic(err)
   340  		}
   341  	}
   342  	return res
   343  }
   344  
   345  func addPendingValidator(
   346  	t testing.TB,
   347  	env *environment,
   348  	startTime time.Time,
   349  	endTime time.Time,
   350  	nodeID ids.NodeID,
   351  	rewardAddress ids.ShortID,
   352  	keys []*secp256k1.PrivateKey,
   353  ) *txs.Tx {
   354  	require := require.New(t)
   355  
   356  	wallet := newWallet(t, env, walletConfig{
   357  		keys: keys,
   358  	})
   359  
   360  	addValidatorTx, err := wallet.IssueAddValidatorTx(
   361  		&txs.Validator{
   362  			NodeID: nodeID,
   363  			Start:  uint64(startTime.Unix()),
   364  			End:    uint64(endTime.Unix()),
   365  			Wght:   env.config.MinValidatorStake,
   366  		},
   367  		&secp256k1fx.OutputOwners{
   368  			Threshold: 1,
   369  			Addrs:     []ids.ShortID{rewardAddress},
   370  		},
   371  		reward.PercentDenominator,
   372  	)
   373  	require.NoError(err)
   374  
   375  	staker, err := state.NewPendingStaker(
   376  		addValidatorTx.ID(),
   377  		addValidatorTx.Unsigned.(*txs.AddValidatorTx),
   378  	)
   379  	require.NoError(err)
   380  
   381  	require.NoError(env.state.PutPendingValidator(staker))
   382  	env.state.AddTx(addValidatorTx, status.Committed)
   383  	env.state.SetHeight(1)
   384  	require.NoError(env.state.Commit())
   385  	return addValidatorTx
   386  }