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 }