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