github.com/MetalBlockchain/metalgo@v1.11.9/vms/platformvm/txs/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  	"math"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/prometheus/client_golang/prometheus"
    14  	"github.com/stretchr/testify/require"
    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/versiondb"
    23  	"github.com/MetalBlockchain/metalgo/ids"
    24  	"github.com/MetalBlockchain/metalgo/snow"
    25  	"github.com/MetalBlockchain/metalgo/snow/snowtest"
    26  	"github.com/MetalBlockchain/metalgo/snow/uptime"
    27  	"github.com/MetalBlockchain/metalgo/snow/validators"
    28  	"github.com/MetalBlockchain/metalgo/utils"
    29  	"github.com/MetalBlockchain/metalgo/utils/constants"
    30  	"github.com/MetalBlockchain/metalgo/utils/crypto/secp256k1"
    31  	"github.com/MetalBlockchain/metalgo/utils/formatting"
    32  	"github.com/MetalBlockchain/metalgo/utils/formatting/address"
    33  	"github.com/MetalBlockchain/metalgo/utils/json"
    34  	"github.com/MetalBlockchain/metalgo/utils/logging"
    35  	"github.com/MetalBlockchain/metalgo/utils/timer/mockable"
    36  	"github.com/MetalBlockchain/metalgo/utils/units"
    37  	"github.com/MetalBlockchain/metalgo/vms/platformvm/api"
    38  	"github.com/MetalBlockchain/metalgo/vms/platformvm/config"
    39  	"github.com/MetalBlockchain/metalgo/vms/platformvm/fx"
    40  	"github.com/MetalBlockchain/metalgo/vms/platformvm/metrics"
    41  	"github.com/MetalBlockchain/metalgo/vms/platformvm/reward"
    42  	"github.com/MetalBlockchain/metalgo/vms/platformvm/state"
    43  	"github.com/MetalBlockchain/metalgo/vms/platformvm/status"
    44  	"github.com/MetalBlockchain/metalgo/vms/platformvm/txs"
    45  	"github.com/MetalBlockchain/metalgo/vms/platformvm/txs/fee"
    46  	"github.com/MetalBlockchain/metalgo/vms/platformvm/txs/txstest"
    47  	"github.com/MetalBlockchain/metalgo/vms/platformvm/upgrade"
    48  	"github.com/MetalBlockchain/metalgo/vms/platformvm/utxo"
    49  	"github.com/MetalBlockchain/metalgo/vms/secp256k1fx"
    50  	"github.com/MetalBlockchain/metalgo/wallet/subnet/primary/common"
    51  
    52  	walletsigner "github.com/MetalBlockchain/metalgo/wallet/chain/p/signer"
    53  )
    54  
    55  const (
    56  	defaultWeight = 5 * units.MilliAvax
    57  	trackChecksum = false
    58  
    59  	apricotPhase3 fork = iota
    60  	apricotPhase5
    61  	banff
    62  	cortina
    63  	durango
    64  	eUpgrade
    65  )
    66  
    67  var (
    68  	defaultMinStakingDuration = 24 * time.Hour
    69  	defaultMaxStakingDuration = 365 * 24 * time.Hour
    70  	defaultGenesisTime        = time.Date(1997, 1, 1, 0, 0, 0, 0, time.UTC)
    71  	defaultValidateStartTime  = defaultGenesisTime
    72  	defaultValidateEndTime    = defaultValidateStartTime.Add(20 * defaultMinStakingDuration)
    73  	defaultMinValidatorStake  = 5 * units.MilliAvax
    74  	defaultBalance            = 100 * defaultMinValidatorStake
    75  	preFundedKeys             = secp256k1.TestKeys()
    76  	defaultTxFee              = uint64(100)
    77  	lastAcceptedID            = ids.GenerateTestID()
    78  
    79  	testSubnet1            *txs.Tx
    80  	testSubnet1ControlKeys = preFundedKeys[0:3]
    81  
    82  	// Node IDs of genesis validators. Initialized in init function
    83  	genesisNodeIDs []ids.NodeID
    84  )
    85  
    86  func init() {
    87  	genesisNodeIDs = make([]ids.NodeID, len(preFundedKeys))
    88  	for i := range preFundedKeys {
    89  		genesisNodeIDs[i] = ids.GenerateTestNodeID()
    90  	}
    91  }
    92  
    93  type fork uint8
    94  
    95  type mutableSharedMemory struct {
    96  	atomic.SharedMemory
    97  }
    98  
    99  type environment struct {
   100  	isBootstrapped *utils.Atomic[bool]
   101  	config         *config.Config
   102  	clk            *mockable.Clock
   103  	baseDB         *versiondb.Database
   104  	ctx            *snow.Context
   105  	msm            *mutableSharedMemory
   106  	fx             fx.Fx
   107  	state          state.State
   108  	states         map[ids.ID]state.Chain
   109  	uptimes        uptime.Manager
   110  	utxosHandler   utxo.Verifier
   111  	factory        *txstest.WalletFactory
   112  	backend        Backend
   113  }
   114  
   115  func (e *environment) GetState(blkID ids.ID) (state.Chain, bool) {
   116  	if blkID == lastAcceptedID {
   117  		return e.state, true
   118  	}
   119  	chainState, ok := e.states[blkID]
   120  	return chainState, ok
   121  }
   122  
   123  func (e *environment) SetState(blkID ids.ID, chainState state.Chain) {
   124  	e.states[blkID] = chainState
   125  }
   126  
   127  func newEnvironment(t *testing.T, f fork) *environment {
   128  	var isBootstrapped utils.Atomic[bool]
   129  	isBootstrapped.Set(true)
   130  
   131  	config := defaultConfig(t, f)
   132  	clk := defaultClock(f)
   133  
   134  	baseDB := versiondb.New(memdb.New())
   135  	ctx := snowtest.Context(t, snowtest.PChainID)
   136  	m := atomic.NewMemory(baseDB)
   137  	msm := &mutableSharedMemory{
   138  		SharedMemory: m.NewSharedMemory(ctx.ChainID),
   139  	}
   140  	ctx.SharedMemory = msm
   141  
   142  	fx := defaultFx(clk, ctx.Log, isBootstrapped.Get())
   143  
   144  	rewards := reward.NewCalculator(config.RewardConfig)
   145  	baseState := defaultState(config, ctx, baseDB, rewards)
   146  
   147  	uptimes := uptime.NewManager(baseState, clk)
   148  	utxosVerifier := utxo.NewVerifier(ctx, clk, fx)
   149  
   150  	factory := txstest.NewWalletFactory(ctx, config, baseState)
   151  
   152  	backend := Backend{
   153  		Config:       config,
   154  		Ctx:          ctx,
   155  		Clk:          clk,
   156  		Bootstrapped: &isBootstrapped,
   157  		Fx:           fx,
   158  		FlowChecker:  utxosVerifier,
   159  		Uptimes:      uptimes,
   160  		Rewards:      rewards,
   161  	}
   162  
   163  	env := &environment{
   164  		isBootstrapped: &isBootstrapped,
   165  		config:         config,
   166  		clk:            clk,
   167  		baseDB:         baseDB,
   168  		ctx:            ctx,
   169  		msm:            msm,
   170  		fx:             fx,
   171  		state:          baseState,
   172  		states:         make(map[ids.ID]state.Chain),
   173  		uptimes:        uptimes,
   174  		utxosHandler:   utxosVerifier,
   175  		factory:        factory,
   176  		backend:        backend,
   177  	}
   178  
   179  	addSubnet(t, env)
   180  
   181  	t.Cleanup(func() {
   182  		env.ctx.Lock.Lock()
   183  		defer env.ctx.Lock.Unlock()
   184  
   185  		require := require.New(t)
   186  
   187  		if env.isBootstrapped.Get() {
   188  			validatorIDs := env.config.Validators.GetValidatorIDs(constants.PrimaryNetworkID)
   189  
   190  			require.NoError(env.uptimes.StopTracking(validatorIDs, constants.PrimaryNetworkID))
   191  
   192  			for subnetID := range env.config.TrackedSubnets {
   193  				validatorIDs := env.config.Validators.GetValidatorIDs(subnetID)
   194  
   195  				require.NoError(env.uptimes.StopTracking(validatorIDs, subnetID))
   196  			}
   197  			env.state.SetHeight(math.MaxUint64)
   198  			require.NoError(env.state.Commit())
   199  		}
   200  
   201  		require.NoError(env.state.Close())
   202  		require.NoError(env.baseDB.Close())
   203  	})
   204  
   205  	return env
   206  }
   207  
   208  func addSubnet(t *testing.T, env *environment) {
   209  	require := require.New(t)
   210  
   211  	builder, signer := env.factory.NewWallet(preFundedKeys[0])
   212  	utx, err := builder.NewCreateSubnetTx(
   213  		&secp256k1fx.OutputOwners{
   214  			Threshold: 2,
   215  			Addrs: []ids.ShortID{
   216  				preFundedKeys[0].PublicKey().Address(),
   217  				preFundedKeys[1].PublicKey().Address(),
   218  				preFundedKeys[2].PublicKey().Address(),
   219  			},
   220  		},
   221  		common.WithChangeOwner(&secp256k1fx.OutputOwners{
   222  			Threshold: 1,
   223  			Addrs:     []ids.ShortID{preFundedKeys[0].PublicKey().Address()},
   224  		}),
   225  	)
   226  	require.NoError(err)
   227  	testSubnet1, err = walletsigner.SignUnsigned(context.Background(), signer, utx)
   228  	require.NoError(err)
   229  
   230  	stateDiff, err := state.NewDiff(lastAcceptedID, env)
   231  	require.NoError(err)
   232  
   233  	executor := StandardTxExecutor{
   234  		Backend: &env.backend,
   235  		State:   stateDiff,
   236  		Tx:      testSubnet1,
   237  	}
   238  	require.NoError(testSubnet1.Unsigned.Visit(&executor))
   239  
   240  	stateDiff.AddTx(testSubnet1, status.Committed)
   241  	require.NoError(stateDiff.Apply(env.state))
   242  	require.NoError(env.state.Commit())
   243  }
   244  
   245  func defaultState(
   246  	cfg *config.Config,
   247  	ctx *snow.Context,
   248  	db database.Database,
   249  	rewards reward.Calculator,
   250  ) state.State {
   251  	genesisBytes := buildGenesisTest(ctx)
   252  	execCfg, _ := config.GetExecutionConfig(nil)
   253  	state, err := state.New(
   254  		db,
   255  		genesisBytes,
   256  		prometheus.NewRegistry(),
   257  		cfg,
   258  		execCfg,
   259  		ctx,
   260  		metrics.Noop,
   261  		rewards,
   262  	)
   263  	if err != nil {
   264  		panic(err)
   265  	}
   266  
   267  	// persist and reload to init a bunch of in-memory stuff
   268  	state.SetHeight(0)
   269  	if err := state.Commit(); err != nil {
   270  		panic(err)
   271  	}
   272  	lastAcceptedID = state.GetLastAccepted()
   273  	return state
   274  }
   275  
   276  func defaultConfig(t *testing.T, f fork) *config.Config {
   277  	c := &config.Config{
   278  		Chains:                 chains.TestManager,
   279  		UptimeLockedCalculator: uptime.NewLockedCalculator(),
   280  		Validators:             validators.NewManager(),
   281  		StaticFeeConfig: fee.StaticConfig{
   282  			TxFee:                 defaultTxFee,
   283  			CreateSubnetTxFee:     100 * defaultTxFee,
   284  			CreateBlockchainTxFee: 100 * defaultTxFee,
   285  		},
   286  		MinValidatorStake: 5 * units.MilliAvax,
   287  		MaxValidatorStake: 500 * units.MilliAvax,
   288  		MinDelegatorStake: 1 * units.MilliAvax,
   289  		MinStakeDuration:  defaultMinStakingDuration,
   290  		MaxStakeDuration:  defaultMaxStakingDuration,
   291  		RewardConfig: reward.Config{
   292  			MaxConsumptionRate: .12 * reward.PercentDenominator,
   293  			MinConsumptionRate: .10 * reward.PercentDenominator,
   294  			MintingPeriod:      365 * 24 * time.Hour,
   295  			SupplyCap:          720 * units.MegaAvax,
   296  		},
   297  		UpgradeConfig: upgrade.Config{
   298  			ApricotPhase3Time: mockable.MaxTime,
   299  			ApricotPhase5Time: mockable.MaxTime,
   300  			BanffTime:         mockable.MaxTime,
   301  			CortinaTime:       mockable.MaxTime,
   302  			DurangoTime:       mockable.MaxTime,
   303  			EUpgradeTime:      mockable.MaxTime,
   304  		},
   305  	}
   306  
   307  	switch f {
   308  	case eUpgrade:
   309  		c.UpgradeConfig.EUpgradeTime = defaultValidateStartTime.Add(-2 * time.Second)
   310  		fallthrough
   311  	case durango:
   312  		c.UpgradeConfig.DurangoTime = defaultValidateStartTime.Add(-2 * time.Second)
   313  		fallthrough
   314  	case cortina:
   315  		c.UpgradeConfig.CortinaTime = defaultValidateStartTime.Add(-2 * time.Second)
   316  		fallthrough
   317  	case banff:
   318  		c.UpgradeConfig.BanffTime = defaultValidateStartTime.Add(-2 * time.Second)
   319  		fallthrough
   320  	case apricotPhase5:
   321  		c.UpgradeConfig.ApricotPhase5Time = defaultValidateEndTime
   322  		fallthrough
   323  	case apricotPhase3:
   324  		c.UpgradeConfig.ApricotPhase3Time = defaultValidateEndTime
   325  	default:
   326  		require.FailNow(t, "unhandled fork", f)
   327  	}
   328  
   329  	return c
   330  }
   331  
   332  func defaultClock(f fork) *mockable.Clock {
   333  	now := defaultGenesisTime
   334  	if f >= banff {
   335  		// 1 second after active fork
   336  		now = defaultValidateEndTime.Add(-2 * time.Second)
   337  	}
   338  	clk := &mockable.Clock{}
   339  	clk.Set(now)
   340  	return clk
   341  }
   342  
   343  type fxVMInt struct {
   344  	registry codec.Registry
   345  	clk      *mockable.Clock
   346  	log      logging.Logger
   347  }
   348  
   349  func (fvi *fxVMInt) CodecRegistry() codec.Registry {
   350  	return fvi.registry
   351  }
   352  
   353  func (fvi *fxVMInt) Clock() *mockable.Clock {
   354  	return fvi.clk
   355  }
   356  
   357  func (fvi *fxVMInt) Logger() logging.Logger {
   358  	return fvi.log
   359  }
   360  
   361  func defaultFx(clk *mockable.Clock, log logging.Logger, isBootstrapped bool) fx.Fx {
   362  	fxVMInt := &fxVMInt{
   363  		registry: linearcodec.NewDefault(),
   364  		clk:      clk,
   365  		log:      log,
   366  	}
   367  	res := &secp256k1fx.Fx{}
   368  	if err := res.Initialize(fxVMInt); err != nil {
   369  		panic(err)
   370  	}
   371  	if isBootstrapped {
   372  		if err := res.Bootstrapped(); err != nil {
   373  			panic(err)
   374  		}
   375  	}
   376  	return res
   377  }
   378  
   379  func buildGenesisTest(ctx *snow.Context) []byte {
   380  	genesisUTXOs := make([]api.UTXO, len(preFundedKeys))
   381  	for i, key := range preFundedKeys {
   382  		id := key.PublicKey().Address()
   383  		addr, err := address.FormatBech32(constants.UnitTestHRP, id.Bytes())
   384  		if err != nil {
   385  			panic(err)
   386  		}
   387  		genesisUTXOs[i] = api.UTXO{
   388  			Amount:  json.Uint64(defaultBalance),
   389  			Address: addr,
   390  		}
   391  	}
   392  
   393  	genesisValidators := make([]api.GenesisPermissionlessValidator, len(genesisNodeIDs))
   394  	for i, nodeID := range genesisNodeIDs {
   395  		addr, err := address.FormatBech32(constants.UnitTestHRP, nodeID.Bytes())
   396  		if err != nil {
   397  			panic(err)
   398  		}
   399  		genesisValidators[i] = api.GenesisPermissionlessValidator{
   400  			GenesisValidator: api.GenesisValidator{
   401  				StartTime: json.Uint64(defaultValidateStartTime.Unix()),
   402  				EndTime:   json.Uint64(defaultValidateEndTime.Unix()),
   403  				NodeID:    nodeID,
   404  			},
   405  			RewardOwner: &api.Owner{
   406  				Threshold: 1,
   407  				Addresses: []string{addr},
   408  			},
   409  			Staked: []api.UTXO{{
   410  				Amount:  json.Uint64(defaultWeight),
   411  				Address: addr,
   412  			}},
   413  			DelegationFee: reward.PercentDenominator,
   414  		}
   415  	}
   416  
   417  	buildGenesisArgs := api.BuildGenesisArgs{
   418  		NetworkID:     json.Uint32(constants.UnitTestID),
   419  		AvaxAssetID:   ctx.AVAXAssetID,
   420  		UTXOs:         genesisUTXOs,
   421  		Validators:    genesisValidators,
   422  		Chains:        nil,
   423  		Time:          json.Uint64(defaultGenesisTime.Unix()),
   424  		InitialSupply: json.Uint64(360 * units.MegaAvax),
   425  		Encoding:      formatting.Hex,
   426  	}
   427  
   428  	buildGenesisResponse := api.BuildGenesisReply{}
   429  	platformvmSS := api.StaticService{}
   430  	if err := platformvmSS.BuildGenesis(nil, &buildGenesisArgs, &buildGenesisResponse); err != nil {
   431  		panic(fmt.Errorf("problem while building platform chain's genesis state: %w", err))
   432  	}
   433  
   434  	genesisBytes, err := formatting.Decode(buildGenesisResponse.Encoding, buildGenesisResponse.Bytes)
   435  	if err != nil {
   436  		panic(err)
   437  	}
   438  
   439  	return genesisBytes
   440  }