// (c) 2019-2020, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package platformvm import ( "bytes" "container/heap" "errors" "testing" "time" "github.com/prometheus/client_golang/prometheus" "github.com/ava-labs/gecko/chains" "github.com/ava-labs/gecko/chains/atomic" "github.com/ava-labs/gecko/database/memdb" "github.com/ava-labs/gecko/database/prefixdb" "github.com/ava-labs/gecko/ids" "github.com/ava-labs/gecko/snow" "github.com/ava-labs/gecko/snow/choices" "github.com/ava-labs/gecko/snow/consensus/snowball" "github.com/ava-labs/gecko/snow/engine/common" "github.com/ava-labs/gecko/snow/engine/common/queue" "github.com/ava-labs/gecko/snow/networking/router" "github.com/ava-labs/gecko/snow/networking/sender" "github.com/ava-labs/gecko/snow/networking/timeout" "github.com/ava-labs/gecko/snow/validators" "github.com/ava-labs/gecko/utils/crypto" "github.com/ava-labs/gecko/utils/formatting" "github.com/ava-labs/gecko/utils/logging" "github.com/ava-labs/gecko/vms/components/ava" "github.com/ava-labs/gecko/vms/components/core" "github.com/ava-labs/gecko/vms/secp256k1fx" "github.com/ava-labs/gecko/vms/timestampvm" smcon "github.com/ava-labs/gecko/snow/consensus/snowman" smeng "github.com/ava-labs/gecko/snow/engine/snowman" ) var ( // chain timestamp at genesis defaultGenesisTime = time.Now().Round(time.Second) // time that genesis validators start validating defaultValidateStartTime = defaultGenesisTime.Add(1 * time.Second) // time that genesis validators stop validating defaultValidateEndTime = defaultValidateStartTime.Add(10 * MinimumStakingDuration) // each key corresponds to an account that has $AVA and a genesis validator keys []*crypto.PrivateKeySECP256K1R // amount all genesis validators stake in defaultVM defaultStakeAmount uint64 // balance of accounts that exist at genesis in defaultVM defaultBalance = 100 * MinimumStakeAmount // At genesis this account has AVA and is validating the default subnet defaultKey *crypto.PrivateKeySECP256K1R // non-default Subnet that exists at genesis in defaultVM // Its controlKeys are keys[0], keys[1], keys[2] testSubnet1 *CreateSubnetTx testSubnet1ControlKeys []*crypto.PrivateKeySECP256K1R ) var ( errShouldNotifyEngine = errors.New("should have notified engine of block ready") errShouldPrefCommit = errors.New("should prefer to commit proposal") errShouldPrefAbort = errors.New("should prefer to abort proposal") ) const ( testNetworkID = 10 // To be used in tests defaultNonce = 1 defaultWeight = 1 ) func init() { ctx := defaultContext() byteFormatter := formatting.CB58{} factory := crypto.FactorySECP256K1R{} for _, key := range []string{ "24jUJ9vZexUM6expyMcT48LBx27k1m7xpraoV62oSQAHdziao5", "2MMvUMsxx6zsHSNXJdFD8yc5XkancvwyKPwpw4xUK3TCGDuNBY", "cxb7KpGWhDMALTjNNSJ7UQkkomPesyWAPUaWRGdyeBNzR6f35", "ewoqjP7PxY4yr3iLTpLisriqt94hdyDFNgchSxGGztUrTXtNN", "2RWLv6YVEXDiWLpaCbXhhqxtLbnFaKQsWPSSMSPhpWo47uJAeV", } { ctx.Log.AssertNoError(byteFormatter.FromString(key)) pk, err := factory.ToPrivateKey(byteFormatter.Bytes) ctx.Log.AssertNoError(err) keys = append(keys, pk.(*crypto.PrivateKeySECP256K1R)) } defaultStakeAmount = defaultBalance - txFee defaultKey = keys[0] testSubnet1ControlKeys = keys[0:3] } func defaultContext() *snow.Context { ctx := snow.DefaultContextTest() ctx.NetworkID = testNetworkID return ctx } func defaultVM() *VM { genesisAccounts := GenesisAccounts() genesisValidators := GenesisCurrentValidators() genesisChains := make([]*CreateChainTx, 0) genesisState := Genesis{ Accounts: genesisAccounts, Validators: genesisValidators, Chains: genesisChains, Timestamp: uint64(defaultGenesisTime.Unix()), } genesisBytes, err := Codec.Marshal(genesisState) if err != nil { panic(err) } vm := &VM{ SnowmanVM: &core.SnowmanVM{}, chainManager: chains.MockManager{}, } defaultSubnet := validators.NewSet() vm.validators = validators.NewManager() vm.validators.PutValidatorSet(DefaultSubnetID, defaultSubnet) vm.clock.Set(defaultGenesisTime) db := memdb.New() msgChan := make(chan common.Message, 1) ctx := defaultContext() ctx.Lock.Lock() defer ctx.Lock.Unlock() if err := vm.Initialize(ctx, db, genesisBytes, msgChan, nil); err != nil { panic(err) } // Create 1 non-default subnet and store it in testSubnet1 tx, err := vm.newCreateSubnetTx( testNetworkID, 0, []ids.ShortID{keys[0].PublicKey().Address(), keys[1].PublicKey().Address(), keys[2].PublicKey().Address()}, // control keys are keys[0], keys[1], keys[2] 2, // threshold; 2 sigs from keys[0], keys[1], keys[2] needed to add validator to this subnet keys[0], ) if err != nil { panic(err) } if testSubnet1 == nil { testSubnet1 = tx } if err := vm.putSubnets(vm.DB, []*CreateSubnetTx{tx}); err != nil { panic(err) } err = vm.putCurrentValidators( vm.DB, &EventHeap{ SortByStartTime: false, }, tx.id, ) if err != nil { panic(err) } err = vm.putPendingValidators( vm.DB, &EventHeap{ SortByStartTime: true, }, tx.id, ) if err != nil { panic(err) } subnets, err := vm.getSubnets(vm.DB) if err != nil { panic(err) } if len(subnets) == 0 { panic("no subnets found") } // end delete return vm } // The returned accounts have nil for their vm field func GenesisAccounts() []Account { accounts := []Account(nil) for _, key := range keys { accounts = append(accounts, newAccount( key.PublicKey().Address(), // address defaultNonce, // nonce defaultBalance, // balance )) } return accounts } // Returns the validators validating at genesis in tests func GenesisCurrentValidators() *EventHeap { vm := &VM{} validators := &EventHeap{SortByStartTime: false} for _, key := range keys { validator, _ := vm.newAddDefaultSubnetValidatorTx( defaultNonce, // nonce defaultStakeAmount, // weight uint64(defaultValidateStartTime.Unix()), // start time uint64(defaultValidateEndTime.Unix()), // end time key.PublicKey().Address(), // nodeID key.PublicKey().Address(), // destination NumberOfShares, // shares testNetworkID, // network ID key, // key paying tx fee and stake ) heap.Push(validators, validator) } return validators } // Ensure genesis state is parsed from bytes and stored correctly func TestGenesis(t *testing.T) { vm := defaultVM() vm.Ctx.Lock.Lock() defer func() { vm.Shutdown() vm.Ctx.Lock.Unlock() }() // Ensure the genesis block has been accepted and stored genesisBlockID := vm.LastAccepted() // lastAccepted should be ID of genesis block genesisBlock, err := vm.getBlock(genesisBlockID) if err != nil { t.Fatalf("couldn't get genesis block: %v", err) } if genesisBlock.Status() != choices.Accepted { t.Fatal("genesis block should be accepted") } // Ensure all the genesis accounts are stored for _, account := range GenesisAccounts() { vmAccount, err := vm.getAccount(vm.DB, account.Address) if err != nil { t.Fatal("couldn't find account in vm's db") } if !vmAccount.Address.Equals(account.Address) { t.Fatal("account IDs should match") } if vmAccount.Balance != account.Balance { t.Fatal("balances should match") } if vmAccount.Nonce != account.Nonce { t.Fatal("nonces should match") } } // Ensure current validator set of default subnet is correct currentValidators, err := vm.getCurrentValidators(vm.DB, DefaultSubnetID) if err != nil { t.Fatal(err) } if len(currentValidators.Txs) != len(keys) { t.Fatal("vm's current validator set is wrong") } if currentValidators.SortByStartTime == true { t.Fatal("vm's current validators should be sorted by end time") } currentSampler := validators.NewSet() currentSampler.Set(vm.getValidators(currentValidators)) for _, key := range keys { if addr := key.PublicKey().Address(); !currentSampler.Contains(addr) { t.Fatalf("should have had validator with NodeID %s", addr) } } // Ensure pending validator set is correct (empty) pendingValidators, err := vm.getPendingValidators(vm.DB, DefaultSubnetID) if err != nil { t.Fatal(err) } if pendingValidators.Len() != 0 { t.Fatal("vm's pending validator set should be empty") } // Ensure genesis timestamp is correct time, err := vm.getTimestamp(vm.DB) if err != nil { t.Fatal(err) } if !time.Equal(defaultGenesisTime) { t.Fatalf("vm's time is incorrect. Expected %s got %s", defaultGenesisTime, time) } } // accept proposal to add validator to default subnet func TestAddDefaultSubnetValidatorCommit(t *testing.T) { vm := defaultVM() vm.Ctx.Lock.Lock() defer func() { vm.Shutdown() vm.Ctx.Lock.Unlock() }() startTime := defaultGenesisTime.Add(Delta).Add(1 * time.Second) endTime := startTime.Add(MinimumStakingDuration) key, _ := vm.factory.NewPrivateKey() ID := key.PublicKey().Address() // create valid tx tx, err := vm.newAddDefaultSubnetValidatorTx( defaultNonce+1, defaultStakeAmount, uint64(startTime.Unix()), uint64(endTime.Unix()), ID, ID, NumberOfShares, testNetworkID, defaultKey, ) if err != nil { t.Fatal(err) } // trigger block creation vm.unissuedEvents.Add(tx) blk, err := vm.BuildBlock() if err != nil { t.Fatal(err) } // Assert preferences are correct block := blk.(*ProposalBlock) options := block.Options() commit, ok := blk.(*ProposalBlock).Options()[0].(*Commit) if !ok { t.Fatal(errShouldPrefCommit) } _, ok = options[1].(*Abort) if !ok { t.Fatal(errShouldPrefAbort) } if err := block.Verify(); err != nil { t.Fatal(err) } block.Accept() if err := commit.Verify(); err != nil { t.Fatal(err) } commit.Accept() // commit the proposal // Verify that new validator now in pending validator set pendingValidators, err := vm.getPendingValidators(vm.DB, DefaultSubnetID) if err != nil { t.Fatal(err) } pendingSampler := validators.NewSet() pendingSampler.Set(vm.getValidators(pendingValidators)) if !pendingSampler.Contains(ID) { t.Fatalf("pending validator should have validator with ID %s", ID) } } // Reject proposal to add validator to default subnet func TestAddDefaultSubnetValidatorReject(t *testing.T) { vm := defaultVM() vm.Ctx.Lock.Lock() defer func() { vm.Shutdown() vm.Ctx.Lock.Unlock() }() startTime := defaultGenesisTime.Add(Delta).Add(1 * time.Second) endTime := startTime.Add(MinimumStakingDuration) key, _ := vm.factory.NewPrivateKey() ID := key.PublicKey().Address() // create valid tx tx, err := vm.newAddDefaultSubnetValidatorTx( defaultNonce+1, defaultStakeAmount, uint64(startTime.Unix()), uint64(endTime.Unix()), ID, ID, NumberOfShares, testNetworkID, defaultKey, ) if err != nil { t.Fatal(err) } // trigger block creation vm.unissuedEvents.Add(tx) blk, err := vm.BuildBlock() if err != nil { t.Fatal(err) } // Assert preferences are correct block := blk.(*ProposalBlock) options := block.Options() commit, ok := blk.(*ProposalBlock).Options()[0].(*Commit) if !ok { t.Fatal(errShouldPrefCommit) } abort, ok := options[1].(*Abort) if !ok { t.Fatal(errShouldPrefAbort) } if err := block.Verify(); err != nil { t.Fatal(err) } block.Accept() if err := commit.Verify(); err != nil { // should pass verification t.Fatal(err) } if err := abort.Verify(); err != nil { // should pass verification t.Fatal(err) } abort.Accept() // reject the proposal // Verify that new validator NOT in pending validator set pendingValidators, err := vm.getPendingValidators(vm.DB, DefaultSubnetID) if err != nil { t.Fatal(err) } pendingSampler := validators.NewSet() pendingSampler.Set(vm.getValidators(pendingValidators)) if pendingSampler.Contains(ID) { t.Fatalf("should not have added validator to pending validator set") } } // Accept proposal to add validator to non-default subnet func TestAddNonDefaultSubnetValidatorAccept(t *testing.T) { vm := defaultVM() vm.Ctx.Lock.Lock() defer func() { vm.Shutdown() vm.Ctx.Lock.Unlock() }() startTime := defaultValidateStartTime.Add(Delta).Add(1 * time.Second) endTime := startTime.Add(MinimumStakingDuration) // create valid tx // note that [startTime, endTime] is a subset of time that keys[0] // validates default subnet ([defaultValidateStartTime, defaultValidateEndTime]) tx, err := vm.newAddNonDefaultSubnetValidatorTx( defaultNonce+1, defaultWeight, uint64(startTime.Unix()), uint64(endTime.Unix()), keys[0].PublicKey().Address(), testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, keys[0], ) if err != nil { t.Fatal(err) } // trigger block creation vm.unissuedEvents.Add(tx) blk, err := vm.BuildBlock() if err != nil { t.Fatal(err) } // Assert preferences are correct block := blk.(*ProposalBlock) options := block.Options() commit, ok := blk.(*ProposalBlock).Options()[0].(*Commit) if !ok { t.Fatal(errShouldPrefCommit) } abort, ok := options[1].(*Abort) if !ok { t.Fatal(errShouldPrefAbort) } if err := block.Verify(); err != nil { t.Fatal(err) } block.Accept() if err := commit.Verify(); err != nil { t.Fatal(err) } if err := abort.Verify(); err != nil { t.Fatal(err) } commit.Accept() // accept the proposal // Verify that new validator is in pending validator set pendingValidators, err := vm.getPendingValidators(vm.DB, testSubnet1.id) if err != nil { t.Fatal(err) } pendingSampler := validators.NewSet() pendingSampler.Set(vm.getValidators(pendingValidators)) if !pendingSampler.Contains(keys[0].PublicKey().Address()) { t.Fatalf("should have added validator to pending validator set") } } // Reject proposal to add validator to non-default subnet func TestAddNonDefaultSubnetValidatorReject(t *testing.T) { vm := defaultVM() vm.Ctx.Lock.Lock() defer func() { vm.Shutdown() vm.Ctx.Lock.Unlock() }() startTime := defaultValidateStartTime.Add(Delta).Add(1 * time.Second) endTime := startTime.Add(MinimumStakingDuration) key, _ := vm.factory.NewPrivateKey() ID := key.PublicKey().Address() // create valid tx // note that [startTime, endTime] is a subset of time that keys[0] // validates default subnet ([defaultValidateStartTime, defaultValidateEndTime]) tx, err := vm.newAddNonDefaultSubnetValidatorTx( defaultNonce+1, defaultWeight, uint64(startTime.Unix()), uint64(endTime.Unix()), keys[0].PublicKey().Address(), testSubnet1.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[1], testSubnet1ControlKeys[2]}, keys[0], ) if err != nil { t.Fatal(err) } // trigger block creation vm.unissuedEvents.Add(tx) blk, err := vm.BuildBlock() if err != nil { t.Fatal(err) } // Assert preferences are correct block := blk.(*ProposalBlock) options := block.Options() commit, ok := blk.(*ProposalBlock).Options()[0].(*Commit) if !ok { t.Fatal(errShouldPrefCommit) } abort, ok := options[1].(*Abort) if !ok { t.Fatal(errShouldPrefAbort) } if err := block.Verify(); err != nil { t.Fatal(err) } block.Accept() if err := commit.Verify(); err != nil { t.Fatal(err) } if err := abort.Verify(); err != nil { t.Fatal(err) } abort.Accept() // reject the proposal // Verify that new validator NOT in pending validator set pendingValidators, err := vm.getPendingValidators(vm.DB, testSubnet1.id) if err != nil { t.Fatal(err) } pendingSampler := validators.NewSet() pendingSampler.Set(vm.getValidators(pendingValidators)) if pendingSampler.Contains(ID) { t.Fatalf("should not have added validator to pending validator set") } } // Test case where default subnet validator rewarded func TestRewardValidatorAccept(t *testing.T) { vm := defaultVM() vm.Ctx.Lock.Lock() defer func() { vm.Shutdown() vm.Ctx.Lock.Unlock() }() // Fast forward clock to time for genesis validators to leave vm.clock.Set(defaultValidateEndTime) blk, err := vm.BuildBlock() // should contain proposal to advance time if err != nil { t.Fatal(err) } // Assert preferences are correct block := blk.(*ProposalBlock) options := block.Options() commit, ok := blk.(*ProposalBlock).Options()[0].(*Commit) if !ok { t.Fatal(errShouldPrefCommit) } abort, ok := options[1].(*Abort) if !ok { t.Fatal(errShouldPrefAbort) } if err := block.Verify(); err != nil { t.Fatal(err) } block.Accept() if err := commit.Verify(); err != nil { t.Fatal(err) } if err := abort.Verify(); err != nil { t.Fatal(err) } commit.Accept() // advance the timestamp // Verify that chain's timestamp has advanced timestamp, err := vm.getTimestamp(vm.DB) if err != nil { t.Fatal(err) } if !timestamp.Equal(defaultValidateEndTime) { t.Fatal("expected timestamp to have advanced") } blk, err = vm.BuildBlock() // should contain proposal to reward genesis validator if err != nil { t.Fatal(err) } // Assert preferences are correct block = blk.(*ProposalBlock) options = block.Options() commit, ok = blk.(*ProposalBlock).Options()[0].(*Commit) if !ok { t.Fatal(errShouldPrefCommit) } abort, ok = options[1].(*Abort) if !ok { t.Fatal(errShouldPrefAbort) } if err := block.Verify(); err != nil { t.Fatal(err) } block.Accept() if err := commit.Verify(); err != nil { t.Fatal(err) } if err := abort.Verify(); err != nil { t.Fatal(err) } commit.Accept() // reward the genesis validator // Verify that genesis validator was rewarded and removed from current validator set currentValidators, err := vm.getCurrentValidators(vm.DB, DefaultSubnetID) if err != nil { t.Fatal(err) } if currentValidators.Len() != len(keys)-1 { t.Fatal("should have removed a genesis validator") } } // Test case where default subnet validator not rewarded func TestRewardValidatorReject(t *testing.T) { vm := defaultVM() vm.Ctx.Lock.Lock() defer func() { vm.Shutdown() vm.Ctx.Lock.Unlock() }() // Fast forward clock to time for genesis validators to leave vm.clock.Set(defaultValidateEndTime) blk, err := vm.BuildBlock() // should contain proposal to advance time if err != nil { t.Fatal(err) } // Assert preferences are correct block := blk.(*ProposalBlock) options := block.Options() commit, ok := blk.(*ProposalBlock).Options()[0].(*Commit) if !ok { t.Fatal(errShouldPrefCommit) } abort, ok := options[1].(*Abort) if !ok { t.Fatal(errShouldPrefAbort) } if err := block.Verify(); err != nil { t.Fatal(err) } block.Accept() if err := commit.Verify(); err != nil { t.Fatal(err) } if err := abort.Verify(); err != nil { t.Fatal(err) } commit.Accept() // advance the timestamp // Verify that chain's timestamp has advanced timestamp, err := vm.getTimestamp(vm.DB) if err != nil { t.Fatal(err) } if !timestamp.Equal(defaultValidateEndTime) { t.Fatal("expected timestamp to have advanced") } blk, err = vm.BuildBlock() // should contain proposal to reward genesis validator if err != nil { t.Fatal(err) } // Assert preferences are correct block = blk.(*ProposalBlock) options = block.Options() commit, ok = blk.(*ProposalBlock).Options()[0].(*Commit) if !ok { t.Fatal(errShouldPrefCommit) } abort, ok = options[1].(*Abort) if !ok { t.Fatal(errShouldPrefAbort) } if err := block.Verify(); err != nil { t.Fatal(err) } block.Accept() if err := commit.Verify(); err != nil { t.Fatal(err) } if err := abort.Verify(); err != nil { t.Fatal(err) } abort.Accept() // do not reward the genesis validator // Verify that genesis validator was removed from current validator set currentValidators, err := vm.getCurrentValidators(vm.DB, DefaultSubnetID) if err != nil { t.Fatal(err) } if currentValidators.Len() != len(keys)-1 { t.Fatal("should have removed a genesis validator") } } // Ensure BuildBlock errors when there is no block to build func TestUnneededBuildBlock(t *testing.T) { vm := defaultVM() vm.Ctx.Lock.Lock() defer func() { vm.Shutdown() vm.Ctx.Lock.Unlock() }() if _, err := vm.BuildBlock(); err == nil { t.Fatalf("Should have errored on BuildBlock") } } // test acceptance of proposal to create a new chain func TestCreateChain(t *testing.T) { vm := defaultVM() vm.Ctx.Lock.Lock() defer func() { vm.Shutdown() vm.Ctx.Lock.Unlock() }() tx, err := vm.newCreateChainTx( defaultNonce+1, testSubnet1.id, nil, timestampvm.ID, nil, "name", testNetworkID, []*crypto.PrivateKeySECP256K1R{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, keys[0], ) if err != nil { t.Fatal(err) } vm.unissuedDecisionTxs = append(vm.unissuedDecisionTxs, tx) blk, err := vm.BuildBlock() // should contain proposal to create chain if err != nil { t.Fatal(err) } if err := blk.Verify(); err != nil { t.Fatal(err) } blk.Accept() // Verify chain was created chains, err := vm.getChains(vm.DB) if err != nil { t.Fatal(err) } foundNewChain := false for _, chain := range chains { if bytes.Equal(chain.Bytes(), tx.Bytes()) { foundNewChain = true } } if !foundNewChain { t.Fatal("should've created new chain but didn't") } // Verify tx fee was deducted account, err := vm.getAccount(vm.DB, tx.PayerAddress) if err != nil { t.Fatal(err) } if account.Balance != defaultBalance-txFee { t.Fatal("should have deducted txFee from balance") } } // test where we: // 1) Create a subnet // 2) Add a validator to the subnet's pending validator set // 3) Advance timestamp to validator's start time (moving the validator from pending to current) // 4) Advance timestamp to validator's end time (removing validator from current) func TestCreateSubnet(t *testing.T) { vm := defaultVM() vm.Ctx.Lock.Lock() defer func() { vm.Shutdown() vm.Ctx.Lock.Unlock() }() createSubnetTx, err := vm.newCreateSubnetTx( testNetworkID, defaultNonce+1, []ids.ShortID{ keys[0].PublicKey().Address(), keys[1].PublicKey().Address(), }, 1, // threshold keys[0], // payer ) if err != nil { t.Fatal(err) } vm.unissuedDecisionTxs = append(vm.unissuedDecisionTxs, createSubnetTx) blk, err := vm.BuildBlock() // should contain proposal to create subnet if err != nil { t.Fatal(err) } if err := blk.Verify(); err != nil { t.Fatal(err) } blk.Accept() // Verify new subnet was created subnets, err := vm.getSubnets(vm.DB) if err != nil { t.Fatal(err) } foundNewSubnet := false for _, subnet := range subnets { if bytes.Equal(subnet.Bytes(), createSubnetTx.Bytes()) { foundNewSubnet = true } } if !foundNewSubnet { t.Fatal("should've created new subnet but didn't") } // Verify tx fee was deducted account, err := vm.getAccount(vm.DB, createSubnetTx.key.Address()) if err != nil { t.Fatal(err) } if account.Balance != defaultBalance-txFee { t.Fatal("should have deducted txFee from balance") } // Now that we've created a new subnet, add a validator to that subnet startTime := defaultValidateStartTime.Add(Delta).Add(1 * time.Second) endTime := startTime.Add(MinimumStakingDuration) // [startTime, endTime] is subset of time keys[0] validates default subent so tx is valid addValidatorTx, err := vm.newAddNonDefaultSubnetValidatorTx( defaultNonce+2, defaultWeight, uint64(startTime.Unix()), uint64(endTime.Unix()), keys[0].PublicKey().Address(), createSubnetTx.id, testNetworkID, []*crypto.PrivateKeySECP256K1R{keys[0]}, keys[0], ) if err != nil { t.Fatal(err) } // Verify tx is valid _, _, _, _, err = addValidatorTx.SemanticVerify(vm.DB) if err != nil { t.Fatal(err) } vm.unissuedEvents.Push(addValidatorTx) blk, err = vm.BuildBlock() // should add validator to the new subnet if err != nil { t.Fatal(err) } // Assert preferences are correct // and accept the proposal/commit block := blk.(*ProposalBlock) options := block.Options() commit, ok := blk.(*ProposalBlock).Options()[0].(*Commit) if !ok { t.Fatal(errShouldPrefCommit) } abort, ok := options[1].(*Abort) if !ok { t.Fatal(errShouldPrefAbort) } // Accept the block if err := block.Verify(); err != nil { t.Fatal(err) } block.Accept() if err := commit.Verify(); err != nil { t.Fatal(err) } if err := abort.Verify(); err != nil { t.Fatal(err) } commit.Accept() // add the validator to pending validator set // Verify validator is in pending validator set pendingValidators, err := vm.getPendingValidators(vm.DB, createSubnetTx.id) if err != nil { t.Fatal(err) } foundNewValidator := false for _, tx := range pendingValidators.Txs { if tx.ID().Equals(addValidatorTx.ID()) { foundNewValidator = true } } if !foundNewValidator { t.Fatal("didn't add validator to new subnet's pending validator set") } // Advance time to when new validator should start validating // Create a block with an advance time tx that moves validator // from pending to current validator set vm.clock.Set(startTime) blk, err = vm.BuildBlock() // should be advance time tx if err != nil { t.Fatal(err) } // Assert preferences are correct // and accept the proposal/commit block = blk.(*ProposalBlock) options = block.Options() commit, ok = blk.(*ProposalBlock).Options()[0].(*Commit) if !ok { t.Fatal(errShouldPrefCommit) } abort, ok = options[1].(*Abort) if !ok { t.Fatal(errShouldPrefAbort) } // Accept the block if err := block.Verify(); err != nil { t.Fatal(err) } block.Accept() if err := commit.Verify(); err != nil { t.Fatal(err) } if err := abort.Verify(); err != nil { t.Fatal(err) } commit.Accept() // move validator addValidatorTx from pending to current // Verify validator no longer in pending validator set // Verify validator is in pending validator set pendingValidators, err = vm.getPendingValidators(vm.DB, createSubnetTx.id) if err != nil { t.Fatal(err) } if pendingValidators.Len() != 0 { t.Fatal("pending validator set should be empty") } // Verify validator is in current validator set currentValidators, err := vm.getCurrentValidators(vm.DB, createSubnetTx.id) if err != nil { t.Fatal(err) } foundNewValidator = false for _, tx := range currentValidators.Txs { if tx.ID().Equals(addValidatorTx.ID()) { foundNewValidator = true } } if !foundNewValidator { t.Fatal("didn't add validator to new subnet's current validator set") } // fast forward clock to time validator should stop validating vm.clock.Set(endTime) blk, err = vm.BuildBlock() // should be advance time tx if err != nil { t.Fatal(err) } // Assert preferences are correct // and accept the proposal/commit block = blk.(*ProposalBlock) options = block.Options() commit, ok = blk.(*ProposalBlock).Options()[0].(*Commit) if !ok { t.Fatal(errShouldPrefCommit) } abort, ok = options[1].(*Abort) if !ok { t.Fatal(errShouldPrefAbort) } // Accept the block if err := block.Verify(); err != nil { t.Fatal(err) } block.Accept() if err := commit.Verify(); err != nil { t.Fatal(err) } if err := abort.Verify(); err != nil { t.Fatal(err) } commit.Accept() // remove validator from current validator set // pending validators and current validator should be empty pendingValidators, err = vm.getPendingValidators(vm.DB, createSubnetTx.id) if err != nil { t.Fatal(err) } if pendingValidators.Len() != 0 { t.Fatal("pending validator set should be empty") } currentValidators, err = vm.getCurrentValidators(vm.DB, createSubnetTx.id) if err != nil { t.Fatal(err) } if currentValidators.Len() != 0 { t.Fatal("pending validator set should be empty") } } // test asset import func TestAtomicImport(t *testing.T) { vm := defaultVM() vm.Ctx.Lock.Lock() defer func() { vm.Shutdown() vm.Ctx.Lock.Unlock() }() avmID := ids.Empty.Prefix(0) utxoID := ava.UTXOID{ TxID: ids.Empty.Prefix(1), OutputIndex: 1, } assetID := ids.Empty.Prefix(2) amount := uint64(50000) key := keys[0] sm := &atomic.SharedMemory{} sm.Initialize(logging.NoLog{}, memdb.New()) vm.Ctx.SharedMemory = sm.NewBlockchainSharedMemory(vm.Ctx.ChainID) tx, err := vm.newImportTx( defaultNonce+1, testNetworkID, []*ava.TransferableInput{&ava.TransferableInput{ UTXOID: utxoID, Asset: ava.Asset{ID: assetID}, In: &secp256k1fx.TransferInput{ Amt: amount, Input: secp256k1fx.Input{SigIndices: []uint32{0}}, }, }}, [][]*crypto.PrivateKeySECP256K1R{[]*crypto.PrivateKeySECP256K1R{key}}, key, ) if err != nil { t.Fatal(err) } vm.ava = assetID vm.avm = avmID vm.unissuedAtomicTxs = append(vm.unissuedAtomicTxs, tx) if _, err := vm.BuildBlock(); err == nil { t.Fatalf("should have errored due to missing utxos") } // Provide the avm UTXO: smDB := vm.Ctx.SharedMemory.GetDatabase(avmID) utxo := &ava.UTXO{ UTXOID: utxoID, Asset: ava.Asset{ID: assetID}, Out: &secp256k1fx.TransferOutput{ Amt: amount, OutputOwners: secp256k1fx.OutputOwners{ Threshold: 1, Addrs: []ids.ShortID{key.PublicKey().Address()}, }, }, } state := ava.NewPrefixedState(smDB, Codec) if err := state.FundAVMUTXO(utxo); err != nil { t.Fatal(err) } vm.Ctx.SharedMemory.ReleaseDatabase(avmID) vm.unissuedAtomicTxs = append(vm.unissuedAtomicTxs, tx) blk, err := vm.BuildBlock() if err != nil { t.Fatal(err) } if err := blk.Verify(); err != nil { t.Fatal(err) } blk.Accept() smDB = vm.Ctx.SharedMemory.GetDatabase(avmID) defer vm.Ctx.SharedMemory.ReleaseDatabase(avmID) state = ava.NewPrefixedState(smDB, vm.codec) if _, err := state.AVMUTXO(utxoID.InputID()); err == nil { t.Fatalf("shouldn't have been able to read the utxo") } } // test optimistic asset import func TestOptimisticAtomicImport(t *testing.T) { vm := defaultVM() vm.Ctx.Lock.Lock() defer func() { vm.Shutdown() vm.Ctx.Lock.Unlock() }() avmID := ids.Empty.Prefix(0) utxoID := ava.UTXOID{ TxID: ids.Empty.Prefix(1), OutputIndex: 1, } assetID := ids.Empty.Prefix(2) amount := uint64(50000) key := keys[0] sm := &atomic.SharedMemory{} sm.Initialize(logging.NoLog{}, memdb.New()) vm.Ctx.SharedMemory = sm.NewBlockchainSharedMemory(vm.Ctx.ChainID) tx, err := vm.newImportTx( defaultNonce+1, testNetworkID, []*ava.TransferableInput{&ava.TransferableInput{ UTXOID: utxoID, Asset: ava.Asset{ID: assetID}, In: &secp256k1fx.TransferInput{ Amt: amount, Input: secp256k1fx.Input{SigIndices: []uint32{0}}, }, }}, [][]*crypto.PrivateKeySECP256K1R{[]*crypto.PrivateKeySECP256K1R{key}}, key, ) if err != nil { t.Fatal(err) } vm.ava = assetID vm.avm = avmID blk, err := vm.newAtomicBlock(vm.Preferred(), tx) if err != nil { t.Fatal(err) } if err := blk.Verify(); err == nil { t.Fatalf("should have errored due to an invalid atomic utxo") } previousAccount, err := vm.getAccount(vm.DB, key.PublicKey().Address()) if err != nil { t.Fatal(err) } blk.Accept() newAccount, err := vm.getAccount(vm.DB, key.PublicKey().Address()) if err != nil { t.Fatal(err) } if newAccount.Balance != previousAccount.Balance+amount { t.Fatalf("failed to provide funds") } } // test restarting the node func TestRestartPartiallyAccepted(t *testing.T) { genesisAccounts := GenesisAccounts() genesisValidators := GenesisCurrentValidators() genesisChains := make([]*CreateChainTx, 0) genesisState := Genesis{ Accounts: genesisAccounts, Validators: genesisValidators, Chains: genesisChains, Timestamp: uint64(defaultGenesisTime.Unix()), } genesisBytes, err := Codec.Marshal(genesisState) if err != nil { t.Fatal(err) } db := memdb.New() firstVM := &VM{ SnowmanVM: &core.SnowmanVM{}, chainManager: chains.MockManager{}, } firstDefaultSubnet := validators.NewSet() firstVM.validators = validators.NewManager() firstVM.validators.PutValidatorSet(DefaultSubnetID, firstDefaultSubnet) firstVM.clock.Set(defaultGenesisTime) firstCtx := defaultContext() firstCtx.Lock.Lock() firstMsgChan := make(chan common.Message, 1) if err := firstVM.Initialize(firstCtx, db, genesisBytes, firstMsgChan, nil); err != nil { t.Fatal(err) } genesisID := firstVM.LastAccepted() firstAdvanceTimeTx, err := firstVM.newAdvanceTimeTx(defaultGenesisTime.Add(time.Second)) if err != nil { t.Fatal(err) } firstAdvanceTimeBlk, err := firstVM.newProposalBlock(firstVM.Preferred(), firstAdvanceTimeTx) if err != nil { t.Fatal(err) } firstVM.clock.Set(defaultGenesisTime.Add(2 * time.Second)) if err := firstAdvanceTimeBlk.Verify(); err != nil { t.Fatal(err) } options := firstAdvanceTimeBlk.Options() firstOption := options[0] secondOption := options[1] if err := firstOption.Verify(); err != nil { t.Fatal(err) } if err := secondOption.Verify(); err != nil { t.Fatal(err) } firstAdvanceTimeBlk.Accept() secondAdvanceTimeBlkBytes := []byte{ 0x00, 0x00, 0x00, 0x00, 0xad, 0x64, 0x34, 0x49, 0xa5, 0x05, 0xd8, 0xda, 0xc6, 0xd1, 0xb8, 0x2c, 0x5c, 0xe6, 0x06, 0x81, 0xf3, 0x54, 0xbf, 0x0f, 0xf7, 0xc4, 0xb1, 0xc2, 0xa9, 0x6e, 0x92, 0xc1, 0xd8, 0xd8, 0xf0, 0xce, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x5e, 0xa7, 0xbc, 0x7c, } if _, err := firstVM.ParseBlock(secondAdvanceTimeBlkBytes); err != nil { t.Fatal(err) } firstVM.Shutdown() firstCtx.Lock.Unlock() secondVM := &VM{ SnowmanVM: &core.SnowmanVM{}, chainManager: chains.MockManager{}, } secondDefaultSubnet := validators.NewSet() secondVM.validators = validators.NewManager() secondVM.validators.PutValidatorSet(DefaultSubnetID, secondDefaultSubnet) secondVM.clock.Set(defaultGenesisTime) secondCtx := defaultContext() secondCtx.Lock.Lock() defer func() { secondVM.Shutdown() secondCtx.Lock.Unlock() }() secondMsgChan := make(chan common.Message, 1) if err := secondVM.Initialize(secondCtx, db, genesisBytes, secondMsgChan, nil); err != nil { t.Fatal(err) } if lastAccepted := secondVM.LastAccepted(); !genesisID.Equals(lastAccepted) { t.Fatalf("Shouldn't have changed the genesis") } } // test restarting the node func TestRestartFullyAccepted(t *testing.T) { genesisAccounts := GenesisAccounts() genesisValidators := GenesisCurrentValidators() genesisChains := make([]*CreateChainTx, 0) genesisState := Genesis{ Accounts: genesisAccounts, Validators: genesisValidators, Chains: genesisChains, Timestamp: uint64(defaultGenesisTime.Unix()), } genesisBytes, err := Codec.Marshal(genesisState) if err != nil { t.Fatal(err) } db := memdb.New() firstVM := &VM{ SnowmanVM: &core.SnowmanVM{}, chainManager: chains.MockManager{}, } firstDefaultSubnet := validators.NewSet() firstVM.validators = validators.NewManager() firstVM.validators.PutValidatorSet(DefaultSubnetID, firstDefaultSubnet) firstVM.clock.Set(defaultGenesisTime) firstCtx := defaultContext() firstCtx.Lock.Lock() firstMsgChan := make(chan common.Message, 1) if err := firstVM.Initialize(firstCtx, db, genesisBytes, firstMsgChan, nil); err != nil { t.Fatal(err) } firstAdvanceTimeTx, err := firstVM.newAdvanceTimeTx(defaultGenesisTime.Add(time.Second)) if err != nil { t.Fatal(err) } firstAdvanceTimeBlk, err := firstVM.newProposalBlock(firstVM.Preferred(), firstAdvanceTimeTx) if err != nil { t.Fatal(err) } firstVM.clock.Set(defaultGenesisTime.Add(2 * time.Second)) if err := firstAdvanceTimeBlk.Verify(); err != nil { t.Fatal(err) } options := firstAdvanceTimeBlk.Options() firstOption := options[0] secondOption := options[1] if err := firstOption.Verify(); err != nil { t.Fatal(err) } if err := secondOption.Verify(); err != nil { t.Fatal(err) } firstAdvanceTimeBlk.Accept() firstOption.Accept() secondOption.Reject() secondAdvanceTimeBlkBytes := []byte{ 0x00, 0x00, 0x00, 0x00, 0xad, 0x64, 0x34, 0x49, 0xa5, 0x05, 0xd8, 0xda, 0xc6, 0xd1, 0xb8, 0x2c, 0x5c, 0xe6, 0x06, 0x81, 0xf3, 0x54, 0xbf, 0x0f, 0xf7, 0xc4, 0xb1, 0xc2, 0xa9, 0x6e, 0x92, 0xc1, 0xd8, 0xd8, 0xf0, 0xce, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x5e, 0xa7, 0xbc, 0x7c, } if _, err := firstVM.ParseBlock(secondAdvanceTimeBlkBytes); err != nil { t.Fatal(err) } firstVM.Shutdown() firstCtx.Lock.Unlock() secondVM := &VM{ SnowmanVM: &core.SnowmanVM{}, chainManager: chains.MockManager{}, } secondDefaultSubnet := validators.NewSet() secondVM.validators = validators.NewManager() secondVM.validators.PutValidatorSet(DefaultSubnetID, secondDefaultSubnet) secondVM.clock.Set(defaultGenesisTime) secondCtx := defaultContext() secondCtx.Lock.Lock() defer func() { secondVM.Shutdown() secondCtx.Lock.Unlock() }() secondMsgChan := make(chan common.Message, 1) if err := secondVM.Initialize(secondCtx, db, genesisBytes, secondMsgChan, nil); err != nil { t.Fatal(err) } if lastAccepted := secondVM.LastAccepted(); !firstOption.ID().Equals(lastAccepted) { t.Fatalf("Should have changed the genesis") } } // test bootstrapping the node func TestBootstrapPartiallyAccepted(t *testing.T) { genesisAccounts := GenesisAccounts() genesisValidators := GenesisCurrentValidators() genesisChains := make([]*CreateChainTx, 0) genesisState := Genesis{ Accounts: genesisAccounts, Validators: genesisValidators, Chains: genesisChains, Timestamp: uint64(defaultGenesisTime.Unix()), } genesisBytes, err := Codec.Marshal(genesisState) if err != nil { t.Fatal(err) } db := memdb.New() vmDB := prefixdb.New([]byte("vm"), db) bootstrappingDB := prefixdb.New([]byte("bootstrapping"), db) blocked, err := queue.New(bootstrappingDB) if err != nil { t.Fatal(err) } vm := &VM{ SnowmanVM: &core.SnowmanVM{}, chainManager: chains.MockManager{}, } defaultSubnet := validators.NewSet() vm.validators = validators.NewManager() vm.validators.PutValidatorSet(DefaultSubnetID, defaultSubnet) vm.clock.Set(defaultGenesisTime) ctx := defaultContext() ctx.Lock.Lock() msgChan := make(chan common.Message, 1) if err := vm.Initialize(ctx, vmDB, genesisBytes, msgChan, nil); err != nil { t.Fatal(err) } genesisID := vm.Preferred() advanceTimeTx, err := vm.newAdvanceTimeTx(defaultGenesisTime.Add(time.Second)) if err != nil { t.Fatal(err) } advanceTimeBlk, err := vm.newProposalBlock(vm.Preferred(), advanceTimeTx) if err != nil { t.Fatal(err) } advanceTimeBlkID := advanceTimeBlk.ID() advanceTimeBlkBytes := advanceTimeBlk.Bytes() advanceTimePreference := advanceTimeBlk.Options()[0] vdrs := validators.NewSet() vdrs.Add(validators.NewValidator(ctx.NodeID, 1)) beacons := vdrs timeoutManager := timeout.Manager{} timeoutManager.Initialize(2 * time.Second) go timeoutManager.Dispatch() chainRouter := &router.ChainRouter{} chainRouter.Initialize(logging.NoLog{}, &timeoutManager, time.Hour, time.Second) externalSender := &sender.ExternalSenderTest{T: t} externalSender.Default(true) // Passes messages from the consensus engine to the network sender := sender.Sender{} sender.Initialize(ctx, externalSender, chainRouter, &timeoutManager) // The engine handles consensus engine := smeng.Transitive{} engine.Initialize(smeng.Config{ BootstrapConfig: smeng.BootstrapConfig{ Config: common.Config{ Context: ctx, Validators: vdrs, Beacons: beacons, Alpha: uint64(beacons.Len()/2 + 1), Sender: &sender, }, Blocked: blocked, VM: vm, }, Params: snowball.Parameters{ Metrics: prometheus.NewRegistry(), K: 1, Alpha: 1, BetaVirtuous: 20, BetaRogue: 20, ConcurrentRepolls: 1, }, Consensus: &smcon.Topological{}, }) // Asynchronously passes messages from the network to the consensus engine handler := &router.Handler{} handler.Initialize(&engine, msgChan, 1000) // Allow incoming messages to be routed to the new chain chainRouter.AddChain(handler) go ctx.Log.RecoverAndPanic(handler.Dispatch) reqID := new(uint32) externalSender.GetAcceptedFrontierF = func(_ ids.ShortSet, _ ids.ID, requestID uint32) { *reqID = requestID } engine.Startup() externalSender.GetAcceptedFrontierF = nil externalSender.GetAcceptedF = func(_ ids.ShortSet, _ ids.ID, requestID uint32, _ ids.Set) { *reqID = requestID } frontier := ids.Set{} frontier.Add(advanceTimeBlkID) engine.AcceptedFrontier(ctx.NodeID, *reqID, frontier) externalSender.GetAcceptedF = nil externalSender.GetF = func(_ ids.ShortID, _ ids.ID, requestID uint32, containerID ids.ID) { *reqID = requestID if !containerID.Equals(advanceTimeBlkID) { t.Fatalf("wrong block requested") } } engine.Accepted(ctx.NodeID, *reqID, frontier) externalSender.GetF = nil externalSender.CantPushQuery = false externalSender.CantPullQuery = false engine.Put(ctx.NodeID, *reqID, advanceTimeBlkID, advanceTimeBlkBytes) externalSender.CantPushQuery = true if pref := vm.Preferred(); !pref.Equals(advanceTimePreference.ID()) { t.Fatalf("wrong preference reported after bootstrapping to proposal block\nPreferred: %s\nExpected: %s\nGenesis: %s", pref, advanceTimePreference.ID(), genesisID) } ctx.Lock.Unlock() chainRouter.Shutdown() } func TestUnverifiedParent(t *testing.T) { genesisAccounts := GenesisAccounts() genesisValidators := GenesisCurrentValidators() genesisChains := make([]*CreateChainTx, 0) genesisState := Genesis{ Accounts: genesisAccounts, Validators: genesisValidators, Chains: genesisChains, Timestamp: uint64(defaultGenesisTime.Unix()), } genesisBytes, err := Codec.Marshal(genesisState) if err != nil { t.Fatal(err) } db := memdb.New() vm := &VM{ SnowmanVM: &core.SnowmanVM{}, chainManager: chains.MockManager{}, } defaultSubnet := validators.NewSet() vm.validators = validators.NewManager() vm.validators.PutValidatorSet(DefaultSubnetID, defaultSubnet) vm.clock.Set(defaultGenesisTime) ctx := defaultContext() ctx.Lock.Lock() defer func() { vm.Shutdown() ctx.Lock.Unlock() }() msgChan := make(chan common.Message, 1) if err := vm.Initialize(ctx, db, genesisBytes, msgChan, nil); err != nil { t.Fatal(err) } firstAdvanceTimeTx, err := vm.newAdvanceTimeTx(defaultGenesisTime.Add(time.Second)) if err != nil { t.Fatal(err) } firstAdvanceTimeBlk, err := vm.newProposalBlock(vm.Preferred(), firstAdvanceTimeTx) if err != nil { t.Fatal(err) } vm.clock.Set(defaultGenesisTime.Add(2 * time.Second)) if err := firstAdvanceTimeBlk.Verify(); err != nil { t.Fatal(err) } options := firstAdvanceTimeBlk.Options() firstOption := options[0] secondOption := options[1] secondAdvanceTimeTx, err := vm.newAdvanceTimeTx(defaultGenesisTime.Add(2 * time.Second)) if err != nil { t.Fatal(err) } secondAdvanceTimeBlk, err := vm.newProposalBlock(firstOption.ID(), secondAdvanceTimeTx) if err != nil { t.Fatal(err) } parentBlk := secondAdvanceTimeBlk.Parent() if parentBlkID := parentBlk.ID(); !parentBlkID.Equals(firstOption.ID()) { t.Fatalf("Wrong parent block ID returned") } if err := firstOption.Verify(); err != nil { t.Fatal(err) } if err := secondOption.Verify(); err != nil { t.Fatal(err) } if err := secondAdvanceTimeBlk.Verify(); err != nil { t.Fatal(err) } }