github.com/MetalBlockchain/metalgo@v1.11.9/vms/avm/environment_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 avm 5 6 import ( 7 "context" 8 "encoding/json" 9 "math/rand" 10 "testing" 11 "time" 12 13 "github.com/stretchr/testify/require" 14 15 "github.com/MetalBlockchain/metalgo/api/keystore" 16 "github.com/MetalBlockchain/metalgo/chains/atomic" 17 "github.com/MetalBlockchain/metalgo/database/memdb" 18 "github.com/MetalBlockchain/metalgo/database/prefixdb" 19 "github.com/MetalBlockchain/metalgo/ids" 20 "github.com/MetalBlockchain/metalgo/snow" 21 "github.com/MetalBlockchain/metalgo/snow/engine/common" 22 "github.com/MetalBlockchain/metalgo/snow/snowtest" 23 "github.com/MetalBlockchain/metalgo/utils/constants" 24 "github.com/MetalBlockchain/metalgo/utils/crypto/secp256k1" 25 "github.com/MetalBlockchain/metalgo/utils/formatting" 26 "github.com/MetalBlockchain/metalgo/utils/formatting/address" 27 "github.com/MetalBlockchain/metalgo/utils/logging" 28 "github.com/MetalBlockchain/metalgo/utils/sampler" 29 "github.com/MetalBlockchain/metalgo/utils/timer/mockable" 30 "github.com/MetalBlockchain/metalgo/vms/avm/block/executor" 31 "github.com/MetalBlockchain/metalgo/vms/avm/config" 32 "github.com/MetalBlockchain/metalgo/vms/avm/fxs" 33 "github.com/MetalBlockchain/metalgo/vms/avm/txs" 34 "github.com/MetalBlockchain/metalgo/vms/avm/txs/txstest" 35 "github.com/MetalBlockchain/metalgo/vms/components/avax" 36 "github.com/MetalBlockchain/metalgo/vms/nftfx" 37 "github.com/MetalBlockchain/metalgo/vms/secp256k1fx" 38 39 avajson "github.com/MetalBlockchain/metalgo/utils/json" 40 keystoreutils "github.com/MetalBlockchain/metalgo/vms/components/keystore" 41 ) 42 43 type fork uint8 44 45 const ( 46 durango fork = iota 47 eUpgrade 48 49 latest = durango 50 51 testTxFee uint64 = 1000 52 startBalance uint64 = 50000 53 54 username = "bobby" 55 password = "StrnasfqewiurPasswdn56d" //#nosec G101 56 feeAssetName = "TEST" 57 otherAssetName = "OTHER" 58 ) 59 60 var ( 61 testChangeAddr = ids.GenerateTestShortID() 62 testCases = []struct { 63 name string 64 avaxAsset bool 65 }{ 66 { 67 name: "genesis asset is AVAX", 68 avaxAsset: true, 69 }, 70 { 71 name: "genesis asset is TEST", 72 avaxAsset: false, 73 }, 74 } 75 76 assetID = ids.ID{1, 2, 3} 77 78 keys = secp256k1.TestKeys()[:3] // TODO: Remove [:3] 79 addrs []ids.ShortID // addrs[i] corresponds to keys[i] 80 ) 81 82 func init() { 83 addrs = make([]ids.ShortID, len(keys)) 84 for i, key := range keys { 85 addrs[i] = key.Address() 86 } 87 } 88 89 type user struct { 90 username string 91 password string 92 initialKeys []*secp256k1.PrivateKey 93 } 94 95 type envConfig struct { 96 fork fork 97 isCustomFeeAsset bool 98 keystoreUsers []*user 99 vmStaticConfig *config.Config 100 vmDynamicConfig *Config 101 additionalFxs []*common.Fx 102 notLinearized bool 103 notBootstrapped bool 104 } 105 106 type environment struct { 107 genesisBytes []byte 108 genesisTx *txs.Tx 109 sharedMemory *atomic.Memory 110 issuer chan common.Message 111 vm *VM 112 txBuilder *txstest.Builder 113 } 114 115 // setup the testing environment 116 func setup(tb testing.TB, c *envConfig) *environment { 117 require := require.New(tb) 118 119 var ( 120 genesisArgs *BuildGenesisArgs 121 assetName = "AVAX" 122 ) 123 if c.isCustomFeeAsset { 124 genesisArgs = makeCustomAssetGenesis(tb) 125 assetName = feeAssetName 126 } else { 127 genesisArgs = makeDefaultGenesis(tb) 128 } 129 130 genesisBytes := buildGenesisTestWithArgs(tb, genesisArgs) 131 132 ctx := snowtest.Context(tb, snowtest.XChainID) 133 134 baseDB := memdb.New() 135 m := atomic.NewMemory(prefixdb.New([]byte{0}, baseDB)) 136 ctx.SharedMemory = m.NewSharedMemory(ctx.ChainID) 137 138 // NB: this lock is intentionally left locked when this function returns. 139 // The caller of this function is responsible for unlocking. 140 ctx.Lock.Lock() 141 142 userKeystore := keystore.New(logging.NoLog{}, memdb.New()) 143 ctx.Keystore = userKeystore.NewBlockchainKeyStore(ctx.ChainID) 144 145 for _, user := range c.keystoreUsers { 146 require.NoError(userKeystore.CreateUser(user.username, user.password)) 147 148 // Import the initially funded private keys 149 keystoreUser, err := keystoreutils.NewUserFromKeystore(ctx.Keystore, user.username, user.password) 150 require.NoError(err) 151 152 require.NoError(keystoreUser.PutKeys(user.initialKeys...)) 153 require.NoError(keystoreUser.Close()) 154 } 155 156 vmStaticConfig := staticConfig(tb, c.fork) 157 if c.vmStaticConfig != nil { 158 vmStaticConfig = *c.vmStaticConfig 159 } 160 161 vm := &VM{ 162 Config: vmStaticConfig, 163 } 164 165 vmDynamicConfig := DefaultConfig 166 vmDynamicConfig.IndexTransactions = true 167 if c.vmDynamicConfig != nil { 168 vmDynamicConfig = *c.vmDynamicConfig 169 } 170 configBytes, err := json.Marshal(vmDynamicConfig) 171 require.NoError(err) 172 173 require.NoError(vm.Initialize( 174 context.Background(), 175 ctx, 176 prefixdb.New([]byte{1}, baseDB), 177 genesisBytes, 178 nil, 179 configBytes, 180 nil, 181 append( 182 []*common.Fx{ 183 { 184 ID: secp256k1fx.ID, 185 Fx: &secp256k1fx.Fx{}, 186 }, 187 { 188 ID: nftfx.ID, 189 Fx: &nftfx.Fx{}, 190 }, 191 }, 192 c.additionalFxs..., 193 ), 194 &common.SenderTest{}, 195 )) 196 197 stopVertexID := ids.GenerateTestID() 198 issuer := make(chan common.Message, 1) 199 200 env := &environment{ 201 genesisBytes: genesisBytes, 202 genesisTx: getCreateTxFromGenesisTest(tb, genesisBytes, assetName), 203 sharedMemory: m, 204 issuer: issuer, 205 vm: vm, 206 txBuilder: txstest.New(vm.parser.Codec(), vm.ctx, &vm.Config, vm.feeAssetID, vm.state), 207 } 208 209 require.NoError(vm.SetState(context.Background(), snow.Bootstrapping)) 210 if c.notLinearized { 211 return env 212 } 213 214 require.NoError(vm.Linearize(context.Background(), stopVertexID, issuer)) 215 if c.notBootstrapped { 216 return env 217 } 218 219 require.NoError(vm.SetState(context.Background(), snow.NormalOp)) 220 221 tb.Cleanup(func() { 222 env.vm.ctx.Lock.Lock() 223 defer env.vm.ctx.Lock.Unlock() 224 225 require.NoError(env.vm.Shutdown(context.Background())) 226 }) 227 228 return env 229 } 230 231 func staticConfig(tb testing.TB, f fork) config.Config { 232 c := config.Config{ 233 TxFee: testTxFee, 234 CreateAssetTxFee: testTxFee, 235 EUpgradeTime: mockable.MaxTime, 236 } 237 238 switch f { 239 case eUpgrade: 240 c.EUpgradeTime = time.Time{} 241 case durango: 242 default: 243 require.FailNow(tb, "unhandled fork", f) 244 } 245 246 return c 247 } 248 249 // Returns: 250 // 251 // 1. tx in genesis that creates asset 252 // 2. the index of the output 253 func getCreateTxFromGenesisTest(tb testing.TB, genesisBytes []byte, assetName string) *txs.Tx { 254 require := require.New(tb) 255 256 parser, err := txs.NewParser( 257 []fxs.Fx{ 258 &secp256k1fx.Fx{}, 259 }, 260 ) 261 require.NoError(err) 262 263 cm := parser.GenesisCodec() 264 genesis := Genesis{} 265 _, err = cm.Unmarshal(genesisBytes, &genesis) 266 require.NoError(err) 267 require.NotEmpty(genesis.Txs) 268 269 var assetTx *GenesisAsset 270 for _, tx := range genesis.Txs { 271 if tx.Name == assetName { 272 assetTx = tx 273 break 274 } 275 } 276 require.NotNil(assetTx) 277 278 tx := &txs.Tx{ 279 Unsigned: &assetTx.CreateAssetTx, 280 } 281 require.NoError(tx.Initialize(parser.GenesisCodec())) 282 return tx 283 } 284 285 // buildGenesisTest is the common Genesis builder for most tests 286 func buildGenesisTest(tb testing.TB) []byte { 287 defaultArgs := makeDefaultGenesis(tb) 288 return buildGenesisTestWithArgs(tb, defaultArgs) 289 } 290 291 // buildGenesisTestWithArgs allows building the genesis while injecting different starting points (args) 292 func buildGenesisTestWithArgs(tb testing.TB, args *BuildGenesisArgs) []byte { 293 require := require.New(tb) 294 295 ss := CreateStaticService() 296 297 reply := BuildGenesisReply{} 298 require.NoError(ss.BuildGenesis(nil, args, &reply)) 299 300 b, err := formatting.Decode(reply.Encoding, reply.Bytes) 301 require.NoError(err) 302 return b 303 } 304 305 func newTx(tb testing.TB, genesisBytes []byte, chainID ids.ID, parser txs.Parser, assetName string) *txs.Tx { 306 require := require.New(tb) 307 308 createTx := getCreateTxFromGenesisTest(tb, genesisBytes, assetName) 309 tx := &txs.Tx{Unsigned: &txs.BaseTx{ 310 BaseTx: avax.BaseTx{ 311 NetworkID: constants.UnitTestID, 312 BlockchainID: chainID, 313 Ins: []*avax.TransferableInput{{ 314 UTXOID: avax.UTXOID{ 315 TxID: createTx.ID(), 316 OutputIndex: 2, 317 }, 318 Asset: avax.Asset{ID: createTx.ID()}, 319 In: &secp256k1fx.TransferInput{ 320 Amt: startBalance, 321 Input: secp256k1fx.Input{ 322 SigIndices: []uint32{ 323 0, 324 }, 325 }, 326 }, 327 }}, 328 }, 329 }} 330 require.NoError(tx.SignSECP256K1Fx(parser.Codec(), [][]*secp256k1.PrivateKey{{keys[0]}})) 331 return tx 332 } 333 334 // Sample from a set of addresses and return them raw and formatted as strings. 335 // The size of the sample is between 1 and len(addrs) 336 // If len(addrs) == 0, returns nil 337 func sampleAddrs(tb testing.TB, addressFormatter avax.AddressManager, addrs []ids.ShortID) ([]ids.ShortID, []string) { 338 require := require.New(tb) 339 340 sampledAddrs := []ids.ShortID{} 341 sampledAddrsStr := []string{} 342 343 sampler := sampler.NewUniform() 344 sampler.Initialize(uint64(len(addrs))) 345 346 numAddrs := 1 + rand.Intn(len(addrs)) // #nosec G404 347 indices, ok := sampler.Sample(numAddrs) 348 require.True(ok) 349 for _, index := range indices { 350 addr := addrs[index] 351 addrStr, err := addressFormatter.FormatLocalAddress(addr) 352 require.NoError(err) 353 354 sampledAddrs = append(sampledAddrs, addr) 355 sampledAddrsStr = append(sampledAddrsStr, addrStr) 356 } 357 return sampledAddrs, sampledAddrsStr 358 } 359 360 func makeDefaultGenesis(tb testing.TB) *BuildGenesisArgs { 361 require := require.New(tb) 362 363 addr0Str, err := address.FormatBech32(constants.UnitTestHRP, addrs[0].Bytes()) 364 require.NoError(err) 365 366 addr1Str, err := address.FormatBech32(constants.UnitTestHRP, addrs[1].Bytes()) 367 require.NoError(err) 368 369 addr2Str, err := address.FormatBech32(constants.UnitTestHRP, addrs[2].Bytes()) 370 require.NoError(err) 371 372 return &BuildGenesisArgs{ 373 Encoding: formatting.Hex, 374 GenesisData: map[string]AssetDefinition{ 375 "asset1": { 376 Name: "AVAX", 377 Symbol: "SYMB", 378 InitialState: map[string][]interface{}{ 379 "fixedCap": { 380 Holder{ 381 Amount: avajson.Uint64(startBalance), 382 Address: addr0Str, 383 }, 384 Holder{ 385 Amount: avajson.Uint64(startBalance), 386 Address: addr1Str, 387 }, 388 Holder{ 389 Amount: avajson.Uint64(startBalance), 390 Address: addr2Str, 391 }, 392 }, 393 }, 394 }, 395 "asset2": { 396 Name: "myVarCapAsset", 397 Symbol: "MVCA", 398 InitialState: map[string][]interface{}{ 399 "variableCap": { 400 Owners{ 401 Threshold: 1, 402 Minters: []string{ 403 addr0Str, 404 addr1Str, 405 }, 406 }, 407 Owners{ 408 Threshold: 2, 409 Minters: []string{ 410 addr0Str, 411 addr1Str, 412 addr2Str, 413 }, 414 }, 415 }, 416 }, 417 }, 418 "asset3": { 419 Name: "myOtherVarCapAsset", 420 InitialState: map[string][]interface{}{ 421 "variableCap": { 422 Owners{ 423 Threshold: 1, 424 Minters: []string{ 425 addr0Str, 426 }, 427 }, 428 }, 429 }, 430 }, 431 "asset4": { 432 Name: "myFixedCapAsset", 433 InitialState: map[string][]interface{}{ 434 "fixedCap": { 435 Holder{ 436 Amount: avajson.Uint64(startBalance), 437 Address: addr0Str, 438 }, 439 Holder{ 440 Amount: avajson.Uint64(startBalance), 441 Address: addr1Str, 442 }, 443 }, 444 }, 445 }, 446 }, 447 } 448 } 449 450 func makeCustomAssetGenesis(tb testing.TB) *BuildGenesisArgs { 451 require := require.New(tb) 452 453 addr0Str, err := address.FormatBech32(constants.UnitTestHRP, addrs[0].Bytes()) 454 require.NoError(err) 455 456 addr1Str, err := address.FormatBech32(constants.UnitTestHRP, addrs[1].Bytes()) 457 require.NoError(err) 458 459 addr2Str, err := address.FormatBech32(constants.UnitTestHRP, addrs[2].Bytes()) 460 require.NoError(err) 461 462 return &BuildGenesisArgs{ 463 Encoding: formatting.Hex, 464 GenesisData: map[string]AssetDefinition{ 465 "asset1": { 466 Name: feeAssetName, 467 Symbol: "TST", 468 InitialState: map[string][]interface{}{ 469 "fixedCap": { 470 Holder{ 471 Amount: avajson.Uint64(startBalance), 472 Address: addr0Str, 473 }, 474 Holder{ 475 Amount: avajson.Uint64(startBalance), 476 Address: addr1Str, 477 }, 478 Holder{ 479 Amount: avajson.Uint64(startBalance), 480 Address: addr2Str, 481 }, 482 }, 483 }, 484 }, 485 "asset2": { 486 Name: otherAssetName, 487 Symbol: "OTH", 488 InitialState: map[string][]interface{}{ 489 "fixedCap": { 490 Holder{ 491 Amount: avajson.Uint64(startBalance), 492 Address: addr0Str, 493 }, 494 Holder{ 495 Amount: avajson.Uint64(startBalance), 496 Address: addr1Str, 497 }, 498 Holder{ 499 Amount: avajson.Uint64(startBalance), 500 Address: addr2Str, 501 }, 502 }, 503 }, 504 }, 505 }, 506 } 507 } 508 509 // issueAndAccept expects the context lock not to be held 510 func issueAndAccept( 511 require *require.Assertions, 512 vm *VM, 513 issuer <-chan common.Message, 514 tx *txs.Tx, 515 ) { 516 txID, err := vm.issueTxFromRPC(tx) 517 require.NoError(err) 518 require.Equal(tx.ID(), txID) 519 520 buildAndAccept(require, vm, issuer, txID) 521 } 522 523 // buildAndAccept expects the context lock not to be held 524 func buildAndAccept( 525 require *require.Assertions, 526 vm *VM, 527 issuer <-chan common.Message, 528 txID ids.ID, 529 ) { 530 require.Equal(common.PendingTxs, <-issuer) 531 532 vm.ctx.Lock.Lock() 533 defer vm.ctx.Lock.Unlock() 534 535 blkIntf, err := vm.BuildBlock(context.Background()) 536 require.NoError(err) 537 require.IsType(&executor.Block{}, blkIntf) 538 539 blk := blkIntf.(*executor.Block) 540 txs := blk.Txs() 541 require.Len(txs, 1) 542 543 issuedTx := txs[0] 544 require.Equal(txID, issuedTx.ID()) 545 require.NoError(blk.Verify(context.Background())) 546 require.NoError(vm.SetPreference(context.Background(), blk.ID())) 547 require.NoError(blk.Accept(context.Background())) 548 }