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