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 }