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 }