github.com/ava-labs/avalanchego@v1.11.11/vms/platformvm/service_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 platformvm 5 6 import ( 7 "context" 8 "encoding/json" 9 "errors" 10 "fmt" 11 "math" 12 "math/rand" 13 "testing" 14 "time" 15 16 "github.com/stretchr/testify/require" 17 "go.uber.org/mock/gomock" 18 19 "github.com/ava-labs/avalanchego/api" 20 "github.com/ava-labs/avalanchego/api/keystore" 21 "github.com/ava-labs/avalanchego/cache" 22 "github.com/ava-labs/avalanchego/chains/atomic" 23 "github.com/ava-labs/avalanchego/database" 24 "github.com/ava-labs/avalanchego/database/memdb" 25 "github.com/ava-labs/avalanchego/database/prefixdb" 26 "github.com/ava-labs/avalanchego/ids" 27 "github.com/ava-labs/avalanchego/snow" 28 "github.com/ava-labs/avalanchego/snow/consensus/snowman" 29 "github.com/ava-labs/avalanchego/snow/validators" 30 "github.com/ava-labs/avalanchego/upgrade/upgradetest" 31 "github.com/ava-labs/avalanchego/utils/constants" 32 "github.com/ava-labs/avalanchego/utils/crypto/bls" 33 "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" 34 "github.com/ava-labs/avalanchego/utils/formatting" 35 "github.com/ava-labs/avalanchego/utils/formatting/address" 36 "github.com/ava-labs/avalanchego/utils/logging" 37 "github.com/ava-labs/avalanchego/vms/components/avax" 38 "github.com/ava-labs/avalanchego/vms/components/gas" 39 "github.com/ava-labs/avalanchego/vms/platformvm/block" 40 "github.com/ava-labs/avalanchego/vms/platformvm/block/executor/executormock" 41 "github.com/ava-labs/avalanchego/vms/platformvm/genesis/genesistest" 42 "github.com/ava-labs/avalanchego/vms/platformvm/signer" 43 "github.com/ava-labs/avalanchego/vms/platformvm/state" 44 "github.com/ava-labs/avalanchego/vms/platformvm/status" 45 "github.com/ava-labs/avalanchego/vms/platformvm/txs" 46 "github.com/ava-labs/avalanchego/vms/secp256k1fx" 47 "github.com/ava-labs/avalanchego/wallet/subnet/primary/common" 48 49 avajson "github.com/ava-labs/avalanchego/utils/json" 50 vmkeystore "github.com/ava-labs/avalanchego/vms/components/keystore" 51 pchainapi "github.com/ava-labs/avalanchego/vms/platformvm/api" 52 blockbuilder "github.com/ava-labs/avalanchego/vms/platformvm/block/builder" 53 blockexecutor "github.com/ava-labs/avalanchego/vms/platformvm/block/executor" 54 txexecutor "github.com/ava-labs/avalanchego/vms/platformvm/txs/executor" 55 ) 56 57 var ( 58 // Test user username 59 testUsername = "ScoobyUser" 60 61 // Test user password, must meet minimum complexity/length requirements 62 testPassword = "ShaggyPassword1Zoinks!" 63 64 // Bytes decoded from CB58 "ewoqjP7PxY4yr3iLTpLisriqt94hdyDFNgchSxGGztUrTXtNN" 65 testPrivateKey = []byte{ 66 0x56, 0x28, 0x9e, 0x99, 0xc9, 0x4b, 0x69, 0x12, 67 0xbf, 0xc1, 0x2a, 0xdc, 0x09, 0x3c, 0x9b, 0x51, 68 0x12, 0x4f, 0x0d, 0xc5, 0x4a, 0xc7, 0xa7, 0x66, 69 0xb2, 0xbc, 0x5c, 0xcf, 0x55, 0x8d, 0x80, 0x27, 70 } 71 72 // 3cb7d3842e8cee6a0ebd09f1fe884f6861e1b29c 73 // Platform address resulting from the above private key 74 testAddress = "P-testing18jma8ppw3nhx5r4ap8clazz0dps7rv5umpc36y" 75 76 encodings = []formatting.Encoding{ 77 formatting.JSON, formatting.Hex, 78 } 79 ) 80 81 func defaultService(t *testing.T, fork upgradetest.Fork) (*Service, *mutableSharedMemory) { 82 vm, _, mutableSharedMemory := defaultVM(t, fork) 83 return &Service{ 84 vm: vm, 85 addrManager: avax.NewAddressManager(vm.ctx), 86 stakerAttributesCache: &cache.LRU[ids.ID, *stakerAttributes]{ 87 Size: stakerAttributesCacheSize, 88 }, 89 }, mutableSharedMemory 90 } 91 92 func TestExportKey(t *testing.T) { 93 require := require.New(t) 94 95 service, _ := defaultService(t, upgradetest.Latest) 96 service.vm.ctx.Lock.Lock() 97 98 ks := keystore.New(logging.NoLog{}, memdb.New()) 99 require.NoError(ks.CreateUser(testUsername, testPassword)) 100 service.vm.ctx.Keystore = ks.NewBlockchainKeyStore(service.vm.ctx.ChainID) 101 102 user, err := vmkeystore.NewUserFromKeystore(service.vm.ctx.Keystore, testUsername, testPassword) 103 require.NoError(err) 104 105 pk, err := secp256k1.ToPrivateKey(testPrivateKey) 106 require.NoError(err) 107 108 require.NoError(user.PutKeys(pk, genesistest.DefaultFundedKeys[0])) 109 110 service.vm.ctx.Lock.Unlock() 111 112 jsonString := `{"username":"` + testUsername + `","password":"` + testPassword + `","address":"` + testAddress + `"}` 113 args := ExportKeyArgs{} 114 require.NoError(json.Unmarshal([]byte(jsonString), &args)) 115 116 reply := ExportKeyReply{} 117 require.NoError(service.ExportKey(nil, &args, &reply)) 118 119 require.Equal(testPrivateKey, reply.PrivateKey.Bytes()) 120 } 121 122 // Test issuing a tx and accepted 123 func TestGetTxStatus(t *testing.T) { 124 require := require.New(t) 125 service, mutableSharedMemory := defaultService(t, upgradetest.Latest) 126 service.vm.ctx.Lock.Lock() 127 128 recipientKey, err := secp256k1.NewPrivateKey() 129 require.NoError(err) 130 131 m := atomic.NewMemory(prefixdb.New([]byte{}, service.vm.db)) 132 133 sm := m.NewSharedMemory(service.vm.ctx.ChainID) 134 peerSharedMemory := m.NewSharedMemory(service.vm.ctx.XChainID) 135 136 randSrc := rand.NewSource(0) 137 138 utxo := &avax.UTXO{ 139 UTXOID: avax.UTXOID{ 140 TxID: ids.GenerateTestID(), 141 OutputIndex: uint32(randSrc.Int63()), 142 }, 143 Asset: avax.Asset{ID: service.vm.ctx.AVAXAssetID}, 144 Out: &secp256k1fx.TransferOutput{ 145 Amt: 1234567, 146 OutputOwners: secp256k1fx.OutputOwners{ 147 Locktime: 0, 148 Addrs: []ids.ShortID{recipientKey.Address()}, 149 Threshold: 1, 150 }, 151 }, 152 } 153 utxoBytes, err := txs.Codec.Marshal(txs.CodecVersion, utxo) 154 require.NoError(err) 155 156 inputID := utxo.InputID() 157 require.NoError(peerSharedMemory.Apply(map[ids.ID]*atomic.Requests{ 158 service.vm.ctx.ChainID: { 159 PutRequests: []*atomic.Element{ 160 { 161 Key: inputID[:], 162 Value: utxoBytes, 163 Traits: [][]byte{ 164 recipientKey.Address().Bytes(), 165 }, 166 }, 167 }, 168 }, 169 })) 170 171 mutableSharedMemory.SharedMemory = sm 172 173 wallet := newWallet(t, service.vm, walletConfig{ 174 keys: []*secp256k1.PrivateKey{recipientKey}, 175 }) 176 tx, err := wallet.IssueImportTx( 177 service.vm.ctx.XChainID, 178 &secp256k1fx.OutputOwners{ 179 Threshold: 1, 180 Addrs: []ids.ShortID{ids.ShortEmpty}, 181 }, 182 ) 183 require.NoError(err) 184 185 service.vm.ctx.Lock.Unlock() 186 187 var ( 188 arg = &GetTxStatusArgs{TxID: tx.ID()} 189 resp GetTxStatusResponse 190 ) 191 require.NoError(service.GetTxStatus(nil, arg, &resp)) 192 require.Equal(status.Unknown, resp.Status) 193 require.Zero(resp.Reason) 194 195 // put the chain in existing chain list 196 require.NoError(service.vm.Network.IssueTxFromRPC(tx)) 197 service.vm.ctx.Lock.Lock() 198 199 block, err := service.vm.BuildBlock(context.Background()) 200 require.NoError(err) 201 202 blk := block.(*blockexecutor.Block) 203 require.NoError(blk.Verify(context.Background())) 204 205 require.NoError(blk.Accept(context.Background())) 206 207 service.vm.ctx.Lock.Unlock() 208 209 resp = GetTxStatusResponse{} // reset 210 require.NoError(service.GetTxStatus(nil, arg, &resp)) 211 require.Equal(status.Committed, resp.Status) 212 require.Zero(resp.Reason) 213 } 214 215 // Test issuing and then retrieving a transaction 216 func TestGetTx(t *testing.T) { 217 type test struct { 218 description string 219 createTx func(t testing.TB, s *Service) *txs.Tx 220 } 221 222 tests := []test{ 223 { 224 "standard block", 225 func(t testing.TB, s *Service) *txs.Tx { 226 subnetID := testSubnet1.ID() 227 wallet := newWallet(t, s.vm, walletConfig{ 228 subnetIDs: []ids.ID{subnetID}, 229 }) 230 231 tx, err := wallet.IssueCreateChainTx( 232 subnetID, 233 []byte{}, 234 constants.AVMID, 235 []ids.ID{}, 236 "chain name", 237 common.WithMemo([]byte{}), 238 ) 239 require.NoError(t, err) 240 return tx 241 }, 242 }, 243 { 244 "proposal block", 245 func(t testing.TB, s *Service) *txs.Tx { 246 wallet := newWallet(t, s.vm, walletConfig{}) 247 248 sk, err := bls.NewSecretKey() 249 require.NoError(t, err) 250 251 rewardsOwner := &secp256k1fx.OutputOwners{ 252 Threshold: 1, 253 Addrs: []ids.ShortID{ids.GenerateTestShortID()}, 254 } 255 tx, err := wallet.IssueAddPermissionlessValidatorTx( 256 &txs.SubnetValidator{ 257 Validator: txs.Validator{ 258 NodeID: ids.GenerateTestNodeID(), 259 Start: uint64(s.vm.clock.Time().Add(txexecutor.SyncBound).Unix()), 260 End: uint64(s.vm.clock.Time().Add(txexecutor.SyncBound).Add(defaultMinStakingDuration).Unix()), 261 Wght: s.vm.MinValidatorStake, 262 }, 263 Subnet: constants.PrimaryNetworkID, 264 }, 265 signer.NewProofOfPossession(sk), 266 s.vm.ctx.AVAXAssetID, 267 rewardsOwner, 268 rewardsOwner, 269 0, 270 common.WithMemo([]byte{}), 271 ) 272 require.NoError(t, err) 273 return tx 274 }, 275 }, 276 { 277 "atomic block", 278 func(t testing.TB, s *Service) *txs.Tx { 279 wallet := newWallet(t, s.vm, walletConfig{}) 280 281 tx, err := wallet.IssueExportTx( 282 s.vm.ctx.XChainID, 283 []*avax.TransferableOutput{{ 284 Asset: avax.Asset{ID: s.vm.ctx.AVAXAssetID}, 285 Out: &secp256k1fx.TransferOutput{ 286 Amt: 100, 287 OutputOwners: secp256k1fx.OutputOwners{ 288 Locktime: 0, 289 Threshold: 1, 290 Addrs: []ids.ShortID{ids.GenerateTestShortID()}, 291 }, 292 }, 293 }}, 294 common.WithMemo([]byte{}), 295 ) 296 require.NoError(t, err) 297 return tx 298 }, 299 }, 300 } 301 302 for _, test := range tests { 303 for _, encoding := range encodings { 304 testName := fmt.Sprintf("test '%s - %s'", 305 test.description, 306 encoding.String(), 307 ) 308 t.Run(testName, func(t *testing.T) { 309 require := require.New(t) 310 service, _ := defaultService(t, upgradetest.Latest) 311 312 service.vm.ctx.Lock.Lock() 313 tx := test.createTx(t, service) 314 service.vm.ctx.Lock.Unlock() 315 316 arg := &api.GetTxArgs{ 317 TxID: tx.ID(), 318 Encoding: encoding, 319 } 320 var response api.GetTxReply 321 err := service.GetTx(nil, arg, &response) 322 require.ErrorIs(err, database.ErrNotFound) // We haven't issued the tx yet 323 324 require.NoError(service.vm.Network.IssueTxFromRPC(tx)) 325 service.vm.ctx.Lock.Lock() 326 327 blk, err := service.vm.BuildBlock(context.Background()) 328 require.NoError(err) 329 330 require.NoError(blk.Verify(context.Background())) 331 332 require.NoError(blk.Accept(context.Background())) 333 334 if blk, ok := blk.(snowman.OracleBlock); ok { // For proposal blocks, commit them 335 options, err := blk.Options(context.Background()) 336 if !errors.Is(err, snowman.ErrNotOracle) { 337 require.NoError(err) 338 339 commit := options[0].(*blockexecutor.Block) 340 require.IsType(&block.BanffCommitBlock{}, commit.Block) 341 require.NoError(commit.Verify(context.Background())) 342 require.NoError(commit.Accept(context.Background())) 343 } 344 } 345 346 service.vm.ctx.Lock.Unlock() 347 348 require.NoError(service.GetTx(nil, arg, &response)) 349 350 switch encoding { 351 case formatting.Hex: 352 // we're always guaranteed a string for hex encodings. 353 var txStr string 354 require.NoError(json.Unmarshal(response.Tx, &txStr)) 355 responseTxBytes, err := formatting.Decode(response.Encoding, txStr) 356 require.NoError(err) 357 require.Equal(tx.Bytes(), responseTxBytes) 358 359 case formatting.JSON: 360 tx.Unsigned.InitCtx(service.vm.ctx) 361 expectedTxJSON, err := json.Marshal(tx) 362 require.NoError(err) 363 require.Equal(expectedTxJSON, []byte(response.Tx)) 364 } 365 }) 366 } 367 } 368 } 369 370 func TestGetBalance(t *testing.T) { 371 require := require.New(t) 372 service, _ := defaultService(t, upgradetest.Durango) 373 374 feeCalculator := state.PickFeeCalculator(&service.vm.Config, service.vm.state) 375 createSubnetFee, err := feeCalculator.CalculateFee(testSubnet1.Unsigned) 376 require.NoError(err) 377 378 // Ensure GetStake is correct for each of the genesis validators 379 genesis := genesistest.New(t, genesistest.Config{}) 380 for idx, utxo := range genesis.UTXOs { 381 out := utxo.Out.(*secp256k1fx.TransferOutput) 382 require.Len(out.Addrs, 1) 383 384 addr := out.Addrs[0] 385 addrStr, err := address.Format("P", constants.UnitTestHRP, addr.Bytes()) 386 require.NoError(err) 387 388 request := GetBalanceRequest{ 389 Addresses: []string{ 390 addrStr, 391 }, 392 } 393 reply := GetBalanceResponse{} 394 395 require.NoError(service.GetBalance(nil, &request, &reply)) 396 balance := genesistest.DefaultInitialBalance 397 if idx == 0 { 398 // we use the first key to fund a subnet creation in [defaultGenesis]. 399 // As such we need to account for the subnet creation fee 400 balance = genesistest.DefaultInitialBalance - createSubnetFee 401 } 402 require.Equal(avajson.Uint64(balance), reply.Balance) 403 require.Equal(avajson.Uint64(balance), reply.Unlocked) 404 require.Equal(avajson.Uint64(0), reply.LockedStakeable) 405 require.Equal(avajson.Uint64(0), reply.LockedNotStakeable) 406 } 407 } 408 409 func TestGetStake(t *testing.T) { 410 require := require.New(t) 411 service, _ := defaultService(t, upgradetest.Latest) 412 413 // Ensure GetStake is correct for each of the genesis validators 414 genesis := genesistest.New(t, genesistest.Config{}) 415 addrsStrs := []string{} 416 for _, validatorTx := range genesis.Validators { 417 validator := validatorTx.Unsigned.(*txs.AddValidatorTx) 418 require.Len(validator.StakeOuts, 1) 419 stakeOut := validator.StakeOuts[0].Out.(*secp256k1fx.TransferOutput) 420 require.Len(stakeOut.Addrs, 1) 421 addr := stakeOut.Addrs[0] 422 423 addrStr, err := address.Format("P", constants.UnitTestHRP, addr.Bytes()) 424 require.NoError(err) 425 426 addrsStrs = append(addrsStrs, addrStr) 427 428 args := GetStakeArgs{ 429 JSONAddresses: api.JSONAddresses{ 430 Addresses: []string{addrStr}, 431 }, 432 Encoding: formatting.Hex, 433 } 434 response := GetStakeReply{} 435 require.NoError(service.GetStake(nil, &args, &response)) 436 require.Equal(genesistest.DefaultValidatorWeight, uint64(response.Staked)) 437 require.Len(response.Outputs, 1) 438 439 // Unmarshal into an output 440 outputBytes, err := formatting.Decode(args.Encoding, response.Outputs[0]) 441 require.NoError(err) 442 443 var output avax.TransferableOutput 444 _, err = txs.Codec.Unmarshal(outputBytes, &output) 445 require.NoError(err) 446 447 require.Equal( 448 avax.TransferableOutput{ 449 Asset: avax.Asset{ 450 ID: service.vm.ctx.AVAXAssetID, 451 }, 452 Out: &secp256k1fx.TransferOutput{ 453 Amt: genesistest.DefaultValidatorWeight, 454 OutputOwners: secp256k1fx.OutputOwners{ 455 Threshold: 1, 456 Addrs: []ids.ShortID{ 457 addr, 458 }, 459 }, 460 }, 461 }, 462 output, 463 ) 464 } 465 466 // Make sure this works for multiple addresses 467 args := GetStakeArgs{ 468 JSONAddresses: api.JSONAddresses{ 469 Addresses: addrsStrs, 470 }, 471 Encoding: formatting.Hex, 472 } 473 response := GetStakeReply{} 474 require.NoError(service.GetStake(nil, &args, &response)) 475 require.Equal(len(genesis.Validators)*int(genesistest.DefaultValidatorWeight), int(response.Staked)) 476 require.Len(response.Outputs, len(genesis.Validators)) 477 478 for _, outputStr := range response.Outputs { 479 outputBytes, err := formatting.Decode(args.Encoding, outputStr) 480 require.NoError(err) 481 482 var output avax.TransferableOutput 483 _, err = txs.Codec.Unmarshal(outputBytes, &output) 484 require.NoError(err) 485 486 out := output.Out.(*secp256k1fx.TransferOutput) 487 require.Equal(genesistest.DefaultValidatorWeight, out.Amt) 488 require.Equal(uint32(1), out.Threshold) 489 require.Zero(out.Locktime) 490 require.Len(out.Addrs, 1) 491 } 492 493 oldStake := genesistest.DefaultValidatorWeight 494 495 service.vm.ctx.Lock.Lock() 496 497 wallet := newWallet(t, service.vm, walletConfig{}) 498 499 // Add a delegator 500 stakeAmount := service.vm.MinDelegatorStake + 12345 501 delegatorNodeID := genesistest.DefaultNodeIDs[0] 502 delegatorEndTime := genesistest.DefaultValidatorStartTime.Add(defaultMinStakingDuration) 503 rewardsOwner := &secp256k1fx.OutputOwners{ 504 Threshold: 1, 505 Addrs: []ids.ShortID{ids.GenerateTestShortID()}, 506 } 507 withChangeOwner := common.WithChangeOwner(&secp256k1fx.OutputOwners{ 508 Threshold: 1, 509 Addrs: []ids.ShortID{genesistest.DefaultFundedKeys[0].Address()}, 510 }) 511 tx, err := wallet.IssueAddDelegatorTx( 512 &txs.Validator{ 513 NodeID: delegatorNodeID, 514 Start: genesistest.DefaultValidatorStartTimeUnix, 515 End: uint64(delegatorEndTime.Unix()), 516 Wght: stakeAmount, 517 }, 518 rewardsOwner, 519 withChangeOwner, 520 ) 521 require.NoError(err) 522 523 addDelTx := tx.Unsigned.(*txs.AddDelegatorTx) 524 staker, err := state.NewCurrentStaker( 525 tx.ID(), 526 addDelTx, 527 genesistest.DefaultValidatorStartTime, 528 0, 529 ) 530 require.NoError(err) 531 532 service.vm.state.PutCurrentDelegator(staker) 533 service.vm.state.AddTx(tx, status.Committed) 534 require.NoError(service.vm.state.Commit()) 535 536 service.vm.ctx.Lock.Unlock() 537 538 // Make sure the delegator addr has the right stake (old stake + stakeAmount) 539 addr, _ := service.addrManager.FormatLocalAddress(genesistest.DefaultFundedKeys[0].Address()) 540 args.Addresses = []string{addr} 541 require.NoError(service.GetStake(nil, &args, &response)) 542 require.Equal(oldStake+stakeAmount, uint64(response.Staked)) 543 require.Len(response.Outputs, 2) 544 545 // Unmarshal into transferable outputs 546 outputs := make([]avax.TransferableOutput, 2) 547 for i := range outputs { 548 outputBytes, err := formatting.Decode(args.Encoding, response.Outputs[i]) 549 require.NoError(err) 550 _, err = txs.Codec.Unmarshal(outputBytes, &outputs[i]) 551 require.NoError(err) 552 } 553 554 // Make sure the stake amount is as expected 555 require.Equal(stakeAmount+oldStake, outputs[0].Out.Amount()+outputs[1].Out.Amount()) 556 557 oldStake = uint64(response.Staked) 558 559 service.vm.ctx.Lock.Lock() 560 561 // Make sure this works for pending stakers 562 // Add a pending staker 563 stakeAmount = service.vm.MinValidatorStake + 54321 564 pendingStakerNodeID := ids.GenerateTestNodeID() 565 pendingStakerEndTime := uint64(genesistest.DefaultValidatorStartTime.Add(defaultMinStakingDuration).Unix()) 566 tx, err = wallet.IssueAddValidatorTx( 567 &txs.Validator{ 568 NodeID: pendingStakerNodeID, 569 Start: uint64(genesistest.DefaultValidatorStartTime.Unix()), 570 End: pendingStakerEndTime, 571 Wght: stakeAmount, 572 }, 573 rewardsOwner, 574 0, 575 withChangeOwner, 576 ) 577 require.NoError(err) 578 579 staker, err = state.NewPendingStaker( 580 tx.ID(), 581 tx.Unsigned.(*txs.AddValidatorTx), 582 ) 583 require.NoError(err) 584 585 require.NoError(service.vm.state.PutPendingValidator(staker)) 586 service.vm.state.AddTx(tx, status.Committed) 587 require.NoError(service.vm.state.Commit()) 588 589 service.vm.ctx.Lock.Unlock() 590 591 // Make sure the delegator has the right stake (old stake + stakeAmount) 592 require.NoError(service.GetStake(nil, &args, &response)) 593 require.Equal(oldStake+stakeAmount, uint64(response.Staked)) 594 require.Len(response.Outputs, 3) 595 596 // Unmarshal 597 outputs = make([]avax.TransferableOutput, 3) 598 for i := range outputs { 599 outputBytes, err := formatting.Decode(args.Encoding, response.Outputs[i]) 600 require.NoError(err) 601 _, err = txs.Codec.Unmarshal(outputBytes, &outputs[i]) 602 require.NoError(err) 603 } 604 605 // Make sure the stake amount is as expected 606 require.Equal(stakeAmount+oldStake, outputs[0].Out.Amount()+outputs[1].Out.Amount()+outputs[2].Out.Amount()) 607 } 608 609 func TestGetCurrentValidators(t *testing.T) { 610 require := require.New(t) 611 service, _ := defaultService(t, upgradetest.Latest) 612 613 genesis := genesistest.New(t, genesistest.Config{}) 614 615 // Call getValidators 616 args := GetCurrentValidatorsArgs{SubnetID: constants.PrimaryNetworkID} 617 response := GetCurrentValidatorsReply{} 618 619 require.NoError(service.GetCurrentValidators(nil, &args, &response)) 620 require.Len(response.Validators, len(genesis.Validators)) 621 622 for _, validatorTx := range genesis.Validators { 623 validator := validatorTx.Unsigned.(*txs.AddValidatorTx) 624 nodeID := validator.NodeID() 625 626 found := false 627 for i := 0; i < len(response.Validators); i++ { 628 gotVdr := response.Validators[i].(pchainapi.PermissionlessValidator) 629 if gotVdr.NodeID != nodeID { 630 continue 631 } 632 633 require.Equal(validator.EndTime().Unix(), int64(gotVdr.EndTime)) 634 require.Equal(validator.StartTime().Unix(), int64(gotVdr.StartTime)) 635 found = true 636 break 637 } 638 require.True(found, "expected validators to contain %s but didn't", nodeID) 639 } 640 641 // Add a delegator 642 stakeAmount := service.vm.MinDelegatorStake + 12345 643 validatorNodeID := genesistest.DefaultNodeIDs[1] 644 delegatorEndTime := genesistest.DefaultValidatorStartTime.Add(defaultMinStakingDuration) 645 646 service.vm.ctx.Lock.Lock() 647 648 wallet := newWallet(t, service.vm, walletConfig{}) 649 delTx, err := wallet.IssueAddDelegatorTx( 650 &txs.Validator{ 651 NodeID: validatorNodeID, 652 Start: genesistest.DefaultValidatorStartTimeUnix, 653 End: uint64(delegatorEndTime.Unix()), 654 Wght: stakeAmount, 655 }, 656 &secp256k1fx.OutputOwners{ 657 Threshold: 1, 658 Addrs: []ids.ShortID{ids.GenerateTestShortID()}, 659 }, 660 common.WithChangeOwner(&secp256k1fx.OutputOwners{ 661 Threshold: 1, 662 Addrs: []ids.ShortID{genesistest.DefaultFundedKeys[0].Address()}, 663 }), 664 ) 665 require.NoError(err) 666 667 addDelTx := delTx.Unsigned.(*txs.AddDelegatorTx) 668 staker, err := state.NewCurrentStaker( 669 delTx.ID(), 670 addDelTx, 671 genesistest.DefaultValidatorStartTime, 672 0, 673 ) 674 require.NoError(err) 675 676 service.vm.state.PutCurrentDelegator(staker) 677 service.vm.state.AddTx(delTx, status.Committed) 678 require.NoError(service.vm.state.Commit()) 679 680 service.vm.ctx.Lock.Unlock() 681 682 // Call getCurrentValidators 683 args = GetCurrentValidatorsArgs{SubnetID: constants.PrimaryNetworkID} 684 require.NoError(service.GetCurrentValidators(nil, &args, &response)) 685 require.Len(response.Validators, len(genesis.Validators)) 686 687 // Make sure the delegator is there 688 found := false 689 for i := 0; i < len(response.Validators) && !found; i++ { 690 vdr := response.Validators[i].(pchainapi.PermissionlessValidator) 691 if vdr.NodeID != validatorNodeID { 692 continue 693 } 694 found = true 695 696 require.Nil(vdr.Delegators) 697 698 innerArgs := GetCurrentValidatorsArgs{ 699 SubnetID: constants.PrimaryNetworkID, 700 NodeIDs: []ids.NodeID{vdr.NodeID}, 701 } 702 innerResponse := GetCurrentValidatorsReply{} 703 require.NoError(service.GetCurrentValidators(nil, &innerArgs, &innerResponse)) 704 require.Len(innerResponse.Validators, 1) 705 706 innerVdr := innerResponse.Validators[0].(pchainapi.PermissionlessValidator) 707 require.Equal(vdr.NodeID, innerVdr.NodeID) 708 709 require.NotNil(innerVdr.Delegators) 710 require.Len(*innerVdr.Delegators, 1) 711 delegator := (*innerVdr.Delegators)[0] 712 require.Equal(delegator.NodeID, innerVdr.NodeID) 713 require.Equal(uint64(delegator.StartTime), genesistest.DefaultValidatorStartTimeUnix) 714 require.Equal(int64(delegator.EndTime), delegatorEndTime.Unix()) 715 require.Equal(uint64(delegator.Weight), stakeAmount) 716 } 717 require.True(found) 718 719 service.vm.ctx.Lock.Lock() 720 721 // Reward the delegator 722 tx, err := blockbuilder.NewRewardValidatorTx(service.vm.ctx, delTx.ID()) 723 require.NoError(err) 724 service.vm.state.AddTx(tx, status.Committed) 725 service.vm.state.DeleteCurrentDelegator(staker) 726 require.NoError(service.vm.state.SetDelegateeReward(staker.SubnetID, staker.NodeID, 100000)) 727 require.NoError(service.vm.state.Commit()) 728 729 service.vm.ctx.Lock.Unlock() 730 731 // Call getValidators 732 response = GetCurrentValidatorsReply{} 733 require.NoError(service.GetCurrentValidators(nil, &args, &response)) 734 require.Len(response.Validators, len(genesis.Validators)) 735 736 for _, vdr := range response.Validators { 737 castVdr := vdr.(pchainapi.PermissionlessValidator) 738 if castVdr.NodeID != validatorNodeID { 739 continue 740 } 741 require.Equal(uint64(100000), uint64(*castVdr.AccruedDelegateeReward)) 742 } 743 } 744 745 func TestGetTimestamp(t *testing.T) { 746 require := require.New(t) 747 service, _ := defaultService(t, upgradetest.Latest) 748 749 reply := GetTimestampReply{} 750 require.NoError(service.GetTimestamp(nil, nil, &reply)) 751 752 service.vm.ctx.Lock.Lock() 753 754 require.Equal(service.vm.state.GetTimestamp(), reply.Timestamp) 755 756 newTimestamp := reply.Timestamp.Add(time.Second) 757 service.vm.state.SetTimestamp(newTimestamp) 758 759 service.vm.ctx.Lock.Unlock() 760 761 require.NoError(service.GetTimestamp(nil, nil, &reply)) 762 require.Equal(newTimestamp, reply.Timestamp) 763 } 764 765 func TestGetBlock(t *testing.T) { 766 tests := []struct { 767 name string 768 encoding formatting.Encoding 769 }{ 770 { 771 name: "json", 772 encoding: formatting.JSON, 773 }, 774 { 775 name: "hex", 776 encoding: formatting.Hex, 777 }, 778 } 779 780 for _, test := range tests { 781 t.Run(test.name, func(t *testing.T) { 782 require := require.New(t) 783 service, _ := defaultService(t, upgradetest.Latest) 784 service.vm.ctx.Lock.Lock() 785 786 service.vm.Config.CreateAssetTxFee = 100 * defaultTxFee 787 788 subnetID := testSubnet1.ID() 789 wallet := newWallet(t, service.vm, walletConfig{ 790 subnetIDs: []ids.ID{subnetID}, 791 }) 792 tx, err := wallet.IssueCreateChainTx( 793 subnetID, 794 []byte{}, 795 constants.AVMID, 796 []ids.ID{}, 797 "chain name", 798 common.WithMemo([]byte{}), 799 ) 800 require.NoError(err) 801 802 preferredID := service.vm.manager.Preferred() 803 preferred, err := service.vm.manager.GetBlock(preferredID) 804 require.NoError(err) 805 806 statelessBlock, err := block.NewBanffStandardBlock( 807 preferred.Timestamp(), 808 preferred.ID(), 809 preferred.Height()+1, 810 []*txs.Tx{tx}, 811 ) 812 require.NoError(err) 813 814 blk := service.vm.manager.NewBlock(statelessBlock) 815 816 require.NoError(blk.Verify(context.Background())) 817 require.NoError(blk.Accept(context.Background())) 818 819 service.vm.ctx.Lock.Unlock() 820 821 args := api.GetBlockArgs{ 822 BlockID: blk.ID(), 823 Encoding: test.encoding, 824 } 825 response := api.GetBlockResponse{} 826 require.NoError(service.GetBlock(nil, &args, &response)) 827 828 switch { 829 case test.encoding == formatting.JSON: 830 statelessBlock.InitCtx(service.vm.ctx) 831 expectedBlockJSON, err := json.Marshal(statelessBlock) 832 require.NoError(err) 833 require.Equal(expectedBlockJSON, []byte(response.Block)) 834 default: 835 var blockStr string 836 require.NoError(json.Unmarshal(response.Block, &blockStr)) 837 responseBlockBytes, err := formatting.Decode(response.Encoding, blockStr) 838 require.NoError(err) 839 require.Equal(blk.Bytes(), responseBlockBytes) 840 } 841 842 require.Equal(test.encoding, response.Encoding) 843 }) 844 } 845 } 846 847 func TestGetValidatorsAtReplyMarshalling(t *testing.T) { 848 require := require.New(t) 849 850 reply := &GetValidatorsAtReply{ 851 Validators: make(map[ids.NodeID]*validators.GetValidatorOutput), 852 } 853 854 { 855 reply.Validators[ids.EmptyNodeID] = &validators.GetValidatorOutput{ 856 NodeID: ids.EmptyNodeID, 857 PublicKey: nil, 858 Weight: 0, 859 } 860 } 861 { 862 nodeID := ids.GenerateTestNodeID() 863 sk, err := bls.NewSecretKey() 864 require.NoError(err) 865 reply.Validators[nodeID] = &validators.GetValidatorOutput{ 866 NodeID: nodeID, 867 PublicKey: bls.PublicFromSecretKey(sk), 868 Weight: math.MaxUint64, 869 } 870 } 871 872 replyJSON, err := reply.MarshalJSON() 873 require.NoError(err) 874 875 var parsedReply GetValidatorsAtReply 876 require.NoError(parsedReply.UnmarshalJSON(replyJSON)) 877 require.Equal(reply, &parsedReply) 878 } 879 880 func TestServiceGetBlockByHeight(t *testing.T) { 881 ctrl := gomock.NewController(t) 882 883 blockID := ids.GenerateTestID() 884 blockHeight := uint64(1337) 885 886 type test struct { 887 name string 888 serviceAndExpectedBlockFunc func(t *testing.T, ctrl *gomock.Controller) (*Service, interface{}) 889 encoding formatting.Encoding 890 expectedErr error 891 } 892 893 tests := []test{ 894 { 895 name: "block height not found", 896 serviceAndExpectedBlockFunc: func(_ *testing.T, ctrl *gomock.Controller) (*Service, interface{}) { 897 state := state.NewMockState(ctrl) 898 state.EXPECT().GetBlockIDAtHeight(blockHeight).Return(ids.Empty, database.ErrNotFound) 899 900 manager := executormock.NewManager(ctrl) 901 return &Service{ 902 vm: &VM{ 903 state: state, 904 manager: manager, 905 ctx: &snow.Context{ 906 Log: logging.NoLog{}, 907 }, 908 }, 909 }, nil 910 }, 911 encoding: formatting.Hex, 912 expectedErr: database.ErrNotFound, 913 }, 914 { 915 name: "block not found", 916 serviceAndExpectedBlockFunc: func(_ *testing.T, ctrl *gomock.Controller) (*Service, interface{}) { 917 state := state.NewMockState(ctrl) 918 state.EXPECT().GetBlockIDAtHeight(blockHeight).Return(blockID, nil) 919 920 manager := executormock.NewManager(ctrl) 921 manager.EXPECT().GetStatelessBlock(blockID).Return(nil, database.ErrNotFound) 922 return &Service{ 923 vm: &VM{ 924 state: state, 925 manager: manager, 926 ctx: &snow.Context{ 927 Log: logging.NoLog{}, 928 }, 929 }, 930 }, nil 931 }, 932 encoding: formatting.Hex, 933 expectedErr: database.ErrNotFound, 934 }, 935 { 936 name: "JSON format", 937 serviceAndExpectedBlockFunc: func(_ *testing.T, ctrl *gomock.Controller) (*Service, interface{}) { 938 block := block.NewMockBlock(ctrl) 939 block.EXPECT().InitCtx(gomock.Any()) 940 941 state := state.NewMockState(ctrl) 942 state.EXPECT().GetBlockIDAtHeight(blockHeight).Return(blockID, nil) 943 944 manager := executormock.NewManager(ctrl) 945 manager.EXPECT().GetStatelessBlock(blockID).Return(block, nil) 946 return &Service{ 947 vm: &VM{ 948 state: state, 949 manager: manager, 950 ctx: &snow.Context{ 951 Log: logging.NoLog{}, 952 }, 953 }, 954 }, block 955 }, 956 encoding: formatting.JSON, 957 expectedErr: nil, 958 }, 959 { 960 name: "hex format", 961 serviceAndExpectedBlockFunc: func(t *testing.T, ctrl *gomock.Controller) (*Service, interface{}) { 962 block := block.NewMockBlock(ctrl) 963 blockBytes := []byte("hi mom") 964 block.EXPECT().Bytes().Return(blockBytes) 965 966 state := state.NewMockState(ctrl) 967 state.EXPECT().GetBlockIDAtHeight(blockHeight).Return(blockID, nil) 968 969 expected, err := formatting.Encode(formatting.Hex, blockBytes) 970 require.NoError(t, err) 971 972 manager := executormock.NewManager(ctrl) 973 manager.EXPECT().GetStatelessBlock(blockID).Return(block, nil) 974 return &Service{ 975 vm: &VM{ 976 state: state, 977 manager: manager, 978 ctx: &snow.Context{ 979 Log: logging.NoLog{}, 980 }, 981 }, 982 }, expected 983 }, 984 encoding: formatting.Hex, 985 expectedErr: nil, 986 }, 987 { 988 name: "hexc format", 989 serviceAndExpectedBlockFunc: func(t *testing.T, ctrl *gomock.Controller) (*Service, interface{}) { 990 block := block.NewMockBlock(ctrl) 991 blockBytes := []byte("hi mom") 992 block.EXPECT().Bytes().Return(blockBytes) 993 994 state := state.NewMockState(ctrl) 995 state.EXPECT().GetBlockIDAtHeight(blockHeight).Return(blockID, nil) 996 997 expected, err := formatting.Encode(formatting.HexC, blockBytes) 998 require.NoError(t, err) 999 1000 manager := executormock.NewManager(ctrl) 1001 manager.EXPECT().GetStatelessBlock(blockID).Return(block, nil) 1002 return &Service{ 1003 vm: &VM{ 1004 state: state, 1005 manager: manager, 1006 ctx: &snow.Context{ 1007 Log: logging.NoLog{}, 1008 }, 1009 }, 1010 }, expected 1011 }, 1012 encoding: formatting.HexC, 1013 expectedErr: nil, 1014 }, 1015 { 1016 name: "hexnc format", 1017 serviceAndExpectedBlockFunc: func(t *testing.T, ctrl *gomock.Controller) (*Service, interface{}) { 1018 block := block.NewMockBlock(ctrl) 1019 blockBytes := []byte("hi mom") 1020 block.EXPECT().Bytes().Return(blockBytes) 1021 1022 state := state.NewMockState(ctrl) 1023 state.EXPECT().GetBlockIDAtHeight(blockHeight).Return(blockID, nil) 1024 1025 expected, err := formatting.Encode(formatting.HexNC, blockBytes) 1026 require.NoError(t, err) 1027 1028 manager := executormock.NewManager(ctrl) 1029 manager.EXPECT().GetStatelessBlock(blockID).Return(block, nil) 1030 return &Service{ 1031 vm: &VM{ 1032 state: state, 1033 manager: manager, 1034 ctx: &snow.Context{ 1035 Log: logging.NoLog{}, 1036 }, 1037 }, 1038 }, expected 1039 }, 1040 encoding: formatting.HexNC, 1041 expectedErr: nil, 1042 }, 1043 } 1044 1045 for _, tt := range tests { 1046 t.Run(tt.name, func(t *testing.T) { 1047 require := require.New(t) 1048 1049 service, expected := tt.serviceAndExpectedBlockFunc(t, ctrl) 1050 1051 args := &api.GetBlockByHeightArgs{ 1052 Height: avajson.Uint64(blockHeight), 1053 Encoding: tt.encoding, 1054 } 1055 reply := &api.GetBlockResponse{} 1056 err := service.GetBlockByHeight(nil, args, reply) 1057 require.ErrorIs(err, tt.expectedErr) 1058 if tt.expectedErr != nil { 1059 return 1060 } 1061 require.Equal(tt.encoding, reply.Encoding) 1062 1063 expectedJSON, err := json.Marshal(expected) 1064 require.NoError(err) 1065 1066 require.Equal(json.RawMessage(expectedJSON), reply.Block) 1067 }) 1068 } 1069 } 1070 1071 func TestServiceGetSubnets(t *testing.T) { 1072 require := require.New(t) 1073 service, _ := defaultService(t, upgradetest.Latest) 1074 1075 testSubnet1ID := testSubnet1.ID() 1076 1077 var response GetSubnetsResponse 1078 require.NoError(service.GetSubnets(nil, &GetSubnetsArgs{}, &response)) 1079 require.Equal([]APISubnet{ 1080 { 1081 ID: testSubnet1ID, 1082 ControlKeys: []string{ 1083 "P-testing1d6kkj0qh4wcmus3tk59npwt3rluc6en72ngurd", 1084 "P-testing17fpqs358de5lgu7a5ftpw2t8axf0pm33983krk", 1085 "P-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e", 1086 }, 1087 Threshold: 2, 1088 }, 1089 { 1090 ID: constants.PrimaryNetworkID, 1091 ControlKeys: []string{}, 1092 Threshold: 0, 1093 }, 1094 }, response.Subnets) 1095 1096 newOwnerIDStr := "P-testing1t73fa4p4dypa4s3kgufuvr6hmprjclw66mgqgm" 1097 newOwnerID, err := service.addrManager.ParseLocalAddress(newOwnerIDStr) 1098 require.NoError(err) 1099 1100 service.vm.ctx.Lock.Lock() 1101 service.vm.state.SetSubnetOwner(testSubnet1ID, &secp256k1fx.OutputOwners{ 1102 Addrs: []ids.ShortID{newOwnerID}, 1103 Threshold: 1, 1104 }) 1105 service.vm.ctx.Lock.Unlock() 1106 1107 require.NoError(service.GetSubnets(nil, &GetSubnetsArgs{}, &response)) 1108 require.Equal([]APISubnet{ 1109 { 1110 ID: testSubnet1ID, 1111 ControlKeys: []string{ 1112 newOwnerIDStr, 1113 }, 1114 Threshold: 1, 1115 }, 1116 { 1117 ID: constants.PrimaryNetworkID, 1118 ControlKeys: []string{}, 1119 Threshold: 0, 1120 }, 1121 }, response.Subnets) 1122 } 1123 1124 func TestGetFeeConfig(t *testing.T) { 1125 tests := []struct { 1126 name string 1127 etnaTime time.Time 1128 expected gas.Config 1129 }{ 1130 { 1131 name: "pre-etna", 1132 etnaTime: time.Now().Add(time.Hour), 1133 expected: gas.Config{}, 1134 }, 1135 { 1136 name: "post-etna", 1137 etnaTime: time.Now().Add(-time.Hour), 1138 expected: defaultDynamicFeeConfig, 1139 }, 1140 } 1141 for _, test := range tests { 1142 t.Run(test.name, func(t *testing.T) { 1143 require := require.New(t) 1144 1145 service, _ := defaultService(t, upgradetest.Latest) 1146 service.vm.Config.UpgradeConfig.EtnaTime = test.etnaTime 1147 1148 var reply gas.Config 1149 require.NoError(service.GetFeeConfig(nil, nil, &reply)) 1150 require.Equal(test.expected, reply) 1151 }) 1152 } 1153 } 1154 1155 func FuzzGetFeeState(f *testing.F) { 1156 f.Fuzz(func(t *testing.T, capacity, excess uint64) { 1157 require := require.New(t) 1158 1159 service, _ := defaultService(t, upgradetest.Latest) 1160 1161 var ( 1162 expectedState = gas.State{ 1163 Capacity: gas.Gas(capacity), 1164 Excess: gas.Gas(excess), 1165 } 1166 expectedTime = time.Now() 1167 expectedReply = GetFeeStateReply{ 1168 State: expectedState, 1169 Price: gas.CalculatePrice( 1170 defaultDynamicFeeConfig.MinPrice, 1171 expectedState.Excess, 1172 defaultDynamicFeeConfig.ExcessConversionConstant, 1173 ), 1174 Time: expectedTime, 1175 } 1176 ) 1177 1178 service.vm.ctx.Lock.Lock() 1179 service.vm.state.SetFeeState(expectedState) 1180 service.vm.state.SetTimestamp(expectedTime) 1181 service.vm.ctx.Lock.Unlock() 1182 1183 var reply GetFeeStateReply 1184 require.NoError(service.GetFeeState(nil, nil, &reply)) 1185 require.Equal(expectedReply, reply) 1186 }) 1187 }