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