mirror of https://github.com/poanetwork/gecko.git
1800 lines
44 KiB
Go
1800 lines
44 KiB
Go
// (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)
|
|
}
|
|
}
|
|
|
|
// verify invalid proposal to add validator to default subnet
|
|
func TestInvalidAddDefaultSubnetValidatorCommit(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 invalid 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)
|
|
}
|
|
|
|
blk, err := vm.newProposalBlock(vm.LastAccepted(), tx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := vm.State.PutBlock(vm.DB, blk); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := vm.DB.Commit(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := blk.Verify(); err == nil {
|
|
t.Fatalf("Should have errored during verification")
|
|
}
|
|
|
|
if status := blk.Status(); status != choices.Rejected {
|
|
t.Fatalf("Should have marked the block as rejected")
|
|
}
|
|
|
|
parsedBlk, err := vm.GetBlock(blk.ID())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if status := parsedBlk.Status(); status != choices.Rejected {
|
|
t.Fatalf("Should have marked the block as rejected")
|
|
}
|
|
}
|
|
|
|
// 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]
|
|
|
|
peerID := ids.NewShortID([20]byte{1, 2, 3, 4, 5, 4, 3, 2, 1})
|
|
vdrs := validators.NewSet()
|
|
vdrs.Add(validators.NewValidator(peerID, 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,
|
|
"",
|
|
prometheus.NewRegistry(),
|
|
)
|
|
|
|
// 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(peerID, *reqID, frontier)
|
|
|
|
externalSender.GetAcceptedF = nil
|
|
externalSender.GetAncestorsF = func(_ ids.ShortID, _ ids.ID, requestID uint32, containerID ids.ID) {
|
|
*reqID = requestID
|
|
if !containerID.Equals(advanceTimeBlkID) {
|
|
t.Fatalf("wrong block requested")
|
|
}
|
|
}
|
|
|
|
engine.Accepted(peerID, *reqID, frontier)
|
|
|
|
externalSender.GetF = nil
|
|
externalSender.CantPushQuery = false
|
|
externalSender.CantPullQuery = false
|
|
|
|
engine.MultiPut(peerID, *reqID, [][]byte{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)
|
|
}
|
|
}
|