diff --git a/core/block_validator.go b/core/block_validator.go index f05a623d5..dd5b5821c 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -36,7 +36,7 @@ import ( // BlockValidator is responsible for validating block headers, uncles and // processed state. // -// BlockValidator implements Validator. +// BlockValidator implements validator. type BlockValidator struct { config *params.ChainConfig // Chain configuration options bc *BlockChain // Canonical block chain diff --git a/core/blockchain.go b/core/blockchain.go index 000a99ab4..2e099182d 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -102,9 +102,9 @@ type CacheConfig struct { // block. The Blockchain manages chain imports, reverts, chain reorganisations. // // Importing blocks in to the block chain happens according to the set of rules -// defined by the two stage Validator. Processing of blocks is done using the +// defined by the two stage validator. Processing of blocks is done using the // Processor which processes the included transaction. The validation of the state -// is done in the second part of the Validator. Failing results in aborting of +// is done in the second part of the validator. Failing results in aborting of // the import. // // The BlockChain also helps in returning blocks from **any** chain included @@ -170,7 +170,7 @@ type BlockChain struct { } // NewBlockChain returns a fully initialised block chain using information -// available in the database. It initialises the default Ethereum Validator and +// available in the database. It initialises the default Ethereum validator and // Processor. func NewBlockChain( db ethdb.Database, cacheConfig *CacheConfig, chainConfig *params.ChainConfig, diff --git a/core/staking_verifier.go b/core/staking_verifier.go index 77b6e6198..9634c4f83 100644 --- a/core/staking_verifier.go +++ b/core/staking_verifier.go @@ -111,9 +111,8 @@ func VerifyAndCreateValidatorFromMsg( wrapper.Delegations = []staking.Delegation{ staking.NewDelegation(v.Address, msg.Amount), } - zero := big.NewInt(0) - wrapper.Counters.NumBlocksSigned = zero - wrapper.Counters.NumBlocksToSign = zero + wrapper.Counters.NumBlocksSigned = big.NewInt(0) + wrapper.Counters.NumBlocksToSign = big.NewInt(0) wrapper.BlockReward = big.NewInt(0) maxBLSKeyAllowed := shard.ExternalSlotsAvailableForEpoch(epoch) / 3 if err := wrapper.SanityCheck(maxBLSKeyAllowed); err != nil { @@ -167,7 +166,7 @@ func VerifyAndEditValidatorFromMsg( snapshotValidator, err := chainContext.ReadValidatorSnapshot(wrapper.Address) if err != nil { - return nil, errors.WithMessage(err, "Validator snapshot not found.") + return nil, errors.WithMessage(err, "validator snapshot not found.") } rateAtBeginningOfEpoch := snapshotValidator.Validator.Rate diff --git a/core/staking_verifier_test.go b/core/staking_verifier_test.go index 5fb7bbf86..908f4cac0 100644 --- a/core/staking_verifier_test.go +++ b/core/staking_verifier_test.go @@ -1,609 +1,1502 @@ package core import ( + "errors" + "fmt" "math/big" "strings" "testing" "github.com/ethereum/go-ethereum/common" - - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" - "github.com/harmony-one/bls/ffi/go/bls" - blockfactory "github.com/harmony-one/harmony/block/factory" + "github.com/harmony-one/harmony/block" + consensus_engine "github.com/harmony-one/harmony/consensus/engine" "github.com/harmony-one/harmony/core/state" "github.com/harmony-one/harmony/core/vm" + "github.com/harmony-one/harmony/crypto/bls" "github.com/harmony-one/harmony/crypto/hash" - chain2 "github.com/harmony-one/harmony/internal/chain" - common2 "github.com/harmony-one/harmony/internal/common" - "github.com/harmony-one/harmony/internal/params" "github.com/harmony-one/harmony/numeric" "github.com/harmony-one/harmony/shard" + "github.com/harmony-one/harmony/staking/effective" staking "github.com/harmony-one/harmony/staking/types" + staketest "github.com/harmony-one/harmony/staking/types/test" +) + +const ( + defNumWrappersInState = 5 + defNumPubPerAddr = 2 + + validatorIndex = 0 + validator2Index = 1 + delegatorIndex = 6 ) var ( - validatorAddress = common.Address(common2.MustBech32ToAddress("one1pdv9lrdwl0rg5vglh4xtyrv3wjk3wsqket7zxy")) - postStakingEpoch = big.NewInt(200) - tenK = new(big.Int).Mul(big.NewInt(10000), big.NewInt(1e18)) - twelveK = new(big.Int).Mul(big.NewInt(12000), big.NewInt(1e18)) + blsKeys = makeKeyPairs(20) + + createValidatorAddr = makeTestAddr("validator") + validatorAddr = makeTestAddr(validatorIndex) + validatorAddr2 = makeTestAddr(validator2Index) + delegatorAddr = makeTestAddr(delegatorIndex) +) + +var ( + oneBig = big.NewInt(1e18) + fiveKOnes = new(big.Int).Mul(big.NewInt(5000), oneBig) + tenKOnes = new(big.Int).Mul(big.NewInt(10000), oneBig) + twelveKOnes = new(big.Int).Mul(big.NewInt(12000), oneBig) + fifteenKOnes = new(big.Int).Mul(big.NewInt(15000), oneBig) + twentyKOnes = new(big.Int).Mul(big.NewInt(20000), oneBig) + twentyFiveKOnes = new(big.Int).Mul(big.NewInt(25000), oneBig) + thirtyKOnes = new(big.Int).Mul(big.NewInt(30000), oneBig) + hundredKOnes = new(big.Int).Mul(big.NewInt(100000), oneBig) + + negRate = numeric.NewDecWithPrec(-1, 10) + pointOneDec = numeric.NewDecWithPrec(1, 1) + pointTwoDec = numeric.NewDecWithPrec(2, 1) + pointFiveDec = numeric.NewDecWithPrec(5, 1) + pointSevenDec = numeric.NewDecWithPrec(7, 1) + pointEightFiveDec = numeric.NewDecWithPrec(85, 2) + pointNineDec = numeric.NewDecWithPrec(9, 1) + oneDec = numeric.OneDec() +) + +const ( + defaultEpoch = 5 + defaultNextEpoch = 6 + defaultSnapBlockNumber = 90 + defaultBlockNumber = 100 ) -func createChain(database *ethdb.MemDatabase) *BlockChain { - key, _ := crypto.GenerateKey() - gspec := Genesis{ - Config: params.TestChainConfig, - Factory: blockfactory.ForTest, - Alloc: GenesisAlloc{ - crypto.PubkeyToAddress(key.PublicKey): { - Balance: big.NewInt(8e18), - }, - }, - GasLimit: 1e18, - ShardID: 0, - } - genesis := gspec.MustCommit(database) - _ = genesis - chain, _ := NewBlockChain(database, nil, gspec.Config, chain2.Engine, vm.Config{}, nil) - return chain -} - -func generateBLSKeySigPair() (shard.BLSPublicKey, shard.BLSSignature) { - p := &bls.PublicKey{} - p.DeserializeHexStr(testBLSPubKey) - pub := shard.BLSPublicKey{} - pub.FromLibBLSPublicKey(p) - messageBytes := []byte(staking.BLSVerificationStr) - privateKey := &bls.SecretKey{} - privateKey.DeserializeHexStr(testBLSPrvKey) - msgHash := hash.Keccak256(messageBytes) - signature := privateKey.SignHash(msgHash[:]) - var sig shard.BLSSignature - copy(sig[:], signature.Serialize()) - return pub, sig -} - -func createValidator() *staking.CreateValidator { - desc := staking.Description{ +var ( + defaultDesc = staking.Description{ Name: "SuperHero", Identity: "YouWouldNotKnow", Website: "Secret Website", SecurityContact: "LicenseToKill", Details: "blah blah blah", } - rate, _ := numeric.NewDecFromStr("0.1") - maxRate, _ := numeric.NewDecFromStr("0.5") - maxChangeRate, _ := numeric.NewDecFromStr("0.05") - commission := staking.CommissionRates{ - Rate: rate, - MaxRate: maxRate, - MaxChangeRate: maxChangeRate, - } - minSelfDel := tenK - maxTotalDel := twelveK - pubKey, pubSig := generateBLSKeySigPair() - slotPubKeys := []shard.BLSPublicKey{pubKey} - slotKeySigs := []shard.BLSSignature{pubSig} - amount := tenK - v := staking.CreateValidator{ - ValidatorAddress: validatorAddress, - Description: desc, - CommissionRates: commission, - MinSelfDelegation: minSelfDel, - MaxTotalDelegation: maxTotalDel, - SlotPubKeys: slotPubKeys, - SlotKeySigs: slotKeySigs, - Amount: amount, - } - return &v -} - -// Test CV1: create validator -func TestCV1(t *testing.T) { - database := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(database)) - chain := createChain(database) - msg := createValidator() - statedb.AddBalance(msg.ValidatorAddress, tenK) - if _, err := VerifyAndCreateValidatorFromMsg( - statedb, chain, postStakingEpoch, big.NewInt(0), msg, - ); err != nil { - t.Error("expected", nil, "got", err) - } -} - -// Test CV3: validator already exists -func TestCV3(t *testing.T) { - database := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(database)) - chain := createChain(database) - msg := createValidator() - statedb.AddBalance(msg.ValidatorAddress, tenK) - if _, err := VerifyAndCreateValidatorFromMsg( - statedb, chain, postStakingEpoch, big.NewInt(0), msg, - ); err != nil { - t.Error("expected", nil, "got", err) - } - statedb.SetValidatorFlag(msg.ValidatorAddress) - - if _, err := VerifyAndCreateValidatorFromMsg( - statedb, chain, postStakingEpoch, big.NewInt(0), msg, - ); !strings.Contains(err.Error(), errValidatorExist.Error()) { - t.Error("expected", errValidatorExist, "got", err) - } -} - -// Test CV9: name == 140 characters -func TestCV9(t *testing.T) { - database := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(database)) - chain := createChain(database) - msg := createValidator() - // name length: 140 characters - msg.Name = "Helloiwfhwifbwfbcerghveugbviuscbhwiefbcusidbcifwefhgciwefherhbfiwuehfciwiuebfcuyiewfhwieufwiweifhcwefhwefhwidsffevjnononwondqmeofniowfndjowe" - statedb.AddBalance(msg.ValidatorAddress, tenK) - if _, err := VerifyAndCreateValidatorFromMsg( - statedb, chain, postStakingEpoch, big.NewInt(0), msg, - ); err != nil { - t.Error("expected", nil, "got", err) - } -} - -// Test CV10: identity == 140 characters -func TestCV10(t *testing.T) { - database := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(database)) - chain := createChain(database) - msg := createValidator() - // identity length: 140 characters - msg.Identity = "Helloiwfhwifbwfbcerghveugbviuscbhwiefbcusidbcifwefhgciwefherhbfiwuehfciwiuebfcuyiewfhwieufwiweifhcwefhwefhwidsffevjnononwondqmeofniowfndjowe" - statedb.AddBalance(msg.ValidatorAddress, tenK) - if _, err := VerifyAndCreateValidatorFromMsg( - statedb, chain, postStakingEpoch, big.NewInt(0), msg, - ); err != nil { - t.Error("expected", nil, "got", err) - } -} - -// Test CV11: website == 140 characters -func TestCV11(t *testing.T) { - database := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(database)) - chain := createChain(database) - msg := createValidator() - // website length: 140 characters - msg.Website = "Helloiwfhwifbwfbcerghveugbviuscbhwiefbcusidbcifwefhgciwefherhbfiwuehfciwiuebfcuyiewfhwieufwiweifhcwefhwefhwidsffevjnononwondqmeofniowfndjowe" - statedb.AddBalance(msg.ValidatorAddress, tenK) - if _, err := VerifyAndCreateValidatorFromMsg( - statedb, chain, postStakingEpoch, big.NewInt(0), msg, - ); err != nil { - t.Error("expected", nil, "got", err) - } -} - -// Test CV12: security == 140 characters -func TestCV12(t *testing.T) { - database := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(database)) - chain := createChain(database) - msg := createValidator() - // security contact length: 140 characters - msg.SecurityContact = "Helloiwfhwifbwfbcerghveugbviuscbhwiefbcusidbcifwefhgciwefherhbfiwuehfciwiuebfcuyiewfhwieufwiweifhcwefhwefhwidsffevjnononwondqmeofniowfndjowe" - statedb.AddBalance(msg.ValidatorAddress, tenK) - if _, err := VerifyAndCreateValidatorFromMsg( - statedb, chain, postStakingEpoch, big.NewInt(0), msg, - ); err != nil { - t.Error("expected", nil, "got", err) - } -} - -// Test CV13: details == 280 characters -func TestCV13(t *testing.T) { - database := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(database)) - chain := createChain(database) - msg := createValidator() - // details length: 280 characters - msg.Details = "HelloiwfhwifbwfbcerghveugbviuscbhwiefbcusidbcifwefhgciwefherhbfiwuehfciwiuebfcuyiewfhwieufwiweifhcwefhwefhwidsffevjnononwondqmeofniowfndjoweHlloiwfhwifbwfbcerghveugbviuscbhwiefbcusidbcifwefhgciwefherhbfiwuehfciwiuedbfcuyiewfhwieufwiweifhcwefhwefhwidsffevjnononwondqmeofniowfndjowe" - statedb.AddBalance(msg.ValidatorAddress, tenK) - if _, err := VerifyAndCreateValidatorFromMsg( - statedb, chain, postStakingEpoch, big.NewInt(0), msg, - ); err != nil { - t.Error("expected", nil, "got", err) - } -} - -// Test CV14: commission rate <= max rate & max change rate <= max rate -func TestCV14(t *testing.T) { - database := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(database)) - chain := createChain(database) - msg := createValidator() - statedb.AddBalance(msg.ValidatorAddress, tenK) - // commission rate == max rate && max change rate == max rate - msg.CommissionRates.Rate, _ = numeric.NewDecFromStr("0.5") - msg.CommissionRates.MaxChangeRate, _ = numeric.NewDecFromStr("0.5") - if _, err := VerifyAndCreateValidatorFromMsg( - statedb, chain, postStakingEpoch, big.NewInt(0), msg, - ); err != nil { - t.Error("expected", nil, "got", err) - } -} - -// Test CV15: commission rate > max rate -func TestCV15(t *testing.T) { - database := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(database)) - chain := createChain(database) - msg := createValidator() - statedb.AddBalance(msg.ValidatorAddress, tenK) - // commission rate: 0.6 > max rate: 0.5 - msg.CommissionRates.Rate, _ = numeric.NewDecFromStr("0.6") - if _, err := VerifyAndCreateValidatorFromMsg( - statedb, chain, postStakingEpoch, big.NewInt(0), msg, - ); err == nil { - t.Error("expected", "commission rate and change rate can not be larger than max commission rate", "got", nil) - } -} - -// Test CV16: max change rate > max rate -func TestCV16(t *testing.T) { - database := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(database)) - chain := createChain(database) - msg := createValidator() - statedb.AddBalance(msg.ValidatorAddress, tenK) - // max change rate: 0.6 > max rate: 0.5 - msg.CommissionRates.MaxChangeRate, _ = numeric.NewDecFromStr("0.6") - if _, err := VerifyAndCreateValidatorFromMsg( - statedb, chain, postStakingEpoch, big.NewInt(0), msg, - ); err == nil { - t.Error("expected", "commission rate and change rate can not be larger than max commission rate", "got", nil) - } -} - -// Test CV17: max rate == 1 -func TestCV17(t *testing.T) { - database := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(database)) - chain := createChain(database) - msg := createValidator() - statedb.AddBalance(msg.ValidatorAddress, tenK) - // max rate == 1 - msg.CommissionRates.MaxRate, _ = numeric.NewDecFromStr("1") - if _, err := VerifyAndCreateValidatorFromMsg( - statedb, chain, postStakingEpoch, big.NewInt(0), msg, - ); err != nil { - t.Error("expected", nil, "got", err) - } -} - -// Test CV18: max rate == 1 && max change rate == 1 && commission rate == 0 -func TestCV18(t *testing.T) { - database := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(database)) - chain := createChain(database) - msg := createValidator() - statedb.AddBalance(msg.ValidatorAddress, tenK) - // max rate == 1 && max change rate == 1 && commission rate == 0 - msg.CommissionRates.MaxRate, _ = numeric.NewDecFromStr("1") - msg.CommissionRates.MaxChangeRate, _ = numeric.NewDecFromStr("1") - msg.CommissionRates.Rate, _ = numeric.NewDecFromStr("1") - if _, err := VerifyAndCreateValidatorFromMsg( - statedb, chain, postStakingEpoch, big.NewInt(0), msg, - ); err != nil { - t.Error("expected", nil, "got", err) - } -} - -// Test CV19: commission rate == 0 -func TestCV19(t *testing.T) { - database := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(database)) - chain := createChain(database) - msg := createValidator() - statedb.AddBalance(msg.ValidatorAddress, tenK) - // commission rate == 0 - msg.CommissionRates.Rate, _ = numeric.NewDecFromStr("0") - if _, err := VerifyAndCreateValidatorFromMsg( - statedb, chain, postStakingEpoch, big.NewInt(0), msg, - ); err != nil { - t.Error("expected", nil, "got", err) - } -} - -// Test CV20: max change rate == 0 -func TestCV20(t *testing.T) { - database := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(database)) - chain := createChain(database) - msg := createValidator() - statedb.AddBalance(msg.ValidatorAddress, tenK) - // commission rate == 0 - msg.CommissionRates.MaxChangeRate, _ = numeric.NewDecFromStr("0") - if _, err := VerifyAndCreateValidatorFromMsg( - statedb, chain, postStakingEpoch, big.NewInt(0), msg, - ); err != nil { - t.Error("expected", nil, "got", err) - } -} - -// Test CV21: max change rate == 0 & max rate == 0 & commission rate == 0 -func TestCV21(t *testing.T) { - database := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(database)) - chain := createChain(database) - msg := createValidator() - statedb.AddBalance(msg.ValidatorAddress, tenK) - // max change rate == 0 & max rate == 0 & commission rate == 0 - msg.CommissionRates.MaxRate, _ = numeric.NewDecFromStr("0") - msg.CommissionRates.MaxChangeRate, _ = numeric.NewDecFromStr("0") - msg.CommissionRates.Rate, _ = numeric.NewDecFromStr("0") - if _, err := VerifyAndCreateValidatorFromMsg( - statedb, chain, postStakingEpoch, big.NewInt(0), msg, - ); err != nil { - t.Error("expected", nil, "got", err) - } -} - -// Test CV22: max change rate == 1 & max rate == 1 -func TestCV22(t *testing.T) { - database := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(database)) - chain := createChain(database) - msg := createValidator() - statedb.AddBalance(msg.ValidatorAddress, tenK) - // max change rate == 1 & max rate == 1 - msg.CommissionRates.MaxRate, _ = numeric.NewDecFromStr("1") - msg.CommissionRates.MaxChangeRate, _ = numeric.NewDecFromStr("1") - if _, err := VerifyAndCreateValidatorFromMsg( - statedb, chain, postStakingEpoch, big.NewInt(0), msg, - ); err != nil { - t.Error("expected", nil, "got", err) - } -} - -// Test CV23: commission rate < 0 -func TestCV23(t *testing.T) { - database := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(database)) - chain := createChain(database) - msg := createValidator() - statedb.AddBalance(msg.ValidatorAddress, tenK) - // commission rate < 0 - msg.CommissionRates.Rate, _ = numeric.NewDecFromStr("-0.1") - if _, err := VerifyAndCreateValidatorFromMsg( - statedb, chain, postStakingEpoch, big.NewInt(0), msg, - ); err == nil { - t.Error("expected", "rate:-0.100000000000000000: commission rate, change rate and max rate should be within 0-100 percent", "got", nil) - } -} - -// Test CV24: max rate < 0 -func TestCV24(t *testing.T) { - database := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(database)) - chain := createChain(database) - msg := createValidator() - statedb.AddBalance(msg.ValidatorAddress, tenK) - // max rate < 0 - msg.CommissionRates.MaxRate, _ = numeric.NewDecFromStr("-0.001") - if _, err := VerifyAndCreateValidatorFromMsg( - statedb, chain, postStakingEpoch, big.NewInt(0), msg, - ); err == nil { - t.Error("expected", "rate:-0.001000000000000000: commission rate, change rate and max rate should be within 0-100 percent", "got", nil) - } -} - -// Test CV25: max change rate < 0 -func TestCV25(t *testing.T) { - database := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(database)) - chain := createChain(database) - msg := createValidator() - statedb.AddBalance(msg.ValidatorAddress, tenK) - // max rate < 0 - msg.CommissionRates.MaxChangeRate, _ = numeric.NewDecFromStr("-0.001") - if _, err := VerifyAndCreateValidatorFromMsg( - statedb, chain, postStakingEpoch, big.NewInt(0), msg, - ); err == nil { - t.Error("expected", "rate:-0.001000000000000000: commission rate, change rate and max rate should be within 0-100 percent", "got", nil) - } -} - -// Test CV26: commission rate > 1 -func TestCV26(t *testing.T) { - database := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(database)) - chain := createChain(database) - msg := createValidator() - statedb.AddBalance(msg.ValidatorAddress, tenK) - // commission rate > 1 - msg.CommissionRates.Rate, _ = numeric.NewDecFromStr("1.01") - if _, err := VerifyAndCreateValidatorFromMsg( - statedb, chain, postStakingEpoch, big.NewInt(0), msg, - ); err == nil { - t.Error("expected", "rate:1.01000000000000000: commission rate, change rate and max rate should be within 0-100 percent", "got", nil) - } -} - -// Test CV27: max rate > 1 -func TestCV27(t *testing.T) { - database := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(database)) - chain := createChain(database) - msg := createValidator() - statedb.AddBalance(msg.ValidatorAddress, tenK) - // max rate > 1 - msg.CommissionRates.MaxRate, _ = numeric.NewDecFromStr("1.01") - if _, err := VerifyAndCreateValidatorFromMsg( - statedb, chain, postStakingEpoch, big.NewInt(0), msg, - ); err == nil { - t.Error("expected", "rate:1.01000000000000000: commission rate, change rate and max rate should be within 0-100 percent", "got", nil) - } -} - -// Test CV28: max change rate > 1 -func TestCV28(t *testing.T) { - database := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(database)) - chain := createChain(database) - msg := createValidator() - statedb.AddBalance(msg.ValidatorAddress, tenK) - // max change rate > 1 - msg.CommissionRates.MaxChangeRate, _ = numeric.NewDecFromStr("1.01") - if _, err := VerifyAndCreateValidatorFromMsg( - statedb, chain, postStakingEpoch, big.NewInt(0), msg, - ); err == nil { - t.Error("expected", "rate:1.01000000000000000: commission rate, change rate and max rate should be within 0-100 percent", "got", nil) - } -} - -// Test CV29: amount > MinSelfDelegation -func TestCV29(t *testing.T) { - database := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(database)) - chain := createChain(database) - msg := createValidator() - statedb.AddBalance(msg.ValidatorAddress, twelveK) - // amount > MinSelfDelegation - msg.Amount = twelveK - msg.MinSelfDelegation = tenK - if _, err := VerifyAndCreateValidatorFromMsg( - statedb, chain, postStakingEpoch, big.NewInt(0), msg, - ); err != nil { - t.Error("expected", nil, "got", err) - } -} - -// Test CV30: amount == MinSelfDelegation -func TestCV30(t *testing.T) { - database := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(database)) - chain := createChain(database) - msg := createValidator() - statedb.AddBalance(msg.ValidatorAddress, tenK) - // amount > MinSelfDelegation - msg.Amount = tenK - msg.MinSelfDelegation = tenK - if _, err := VerifyAndCreateValidatorFromMsg( - statedb, chain, postStakingEpoch, big.NewInt(0), msg, - ); err != nil { - t.Error("expected", nil, "got", err) - } -} - -// Test CV31: amount < MinSelfDelegation -func TestCV31(t *testing.T) { - database := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(database)) - chain := createChain(database) - msg := createValidator() - statedb.AddBalance(msg.ValidatorAddress, tenK) - // amount > MinSelfDelegation - msg.Amount = twelveK - msg.MinSelfDelegation = tenK - if _, err := VerifyAndCreateValidatorFromMsg( - statedb, chain, postStakingEpoch, big.NewInt(0), msg, - ); err == nil { - t.Error("expected", "min_self_delegation 5000000000000000000, after delegation amount 4000000000000000000: self delegation can not be less than min_self_delegation", "got", nil) - } -} - -// Test CV32: MaxTotalDelegation < MinSelfDelegation -func TestCV32(t *testing.T) { - database := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(database)) - chain := createChain(database) - msg := createValidator() - statedb.AddBalance(msg.ValidatorAddress, tenK) - // MaxTotalDelegation < MinSelfDelegation - msg.MaxTotalDelegation = tenK - msg.MinSelfDelegation = twelveK - if _, err := VerifyAndCreateValidatorFromMsg( - statedb, chain, postStakingEpoch, big.NewInt(0), msg, - ); err == nil { - t.Error("expected", "max_total_delegation can not be less than min_self_delegation", "got", nil) - } -} - -// Test CV33: MinSelfDelegation < 1 ONE -func TestCV33(t *testing.T) { - database := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(database)) - chain := createChain(database) - msg := createValidator() - statedb.AddBalance(msg.ValidatorAddress, tenK) - // MinSelfDelegation < 10,000 ONE - msg.MinSelfDelegation = big.NewInt(1e18) - if _, err := VerifyAndCreateValidatorFromMsg( - statedb, chain, postStakingEpoch, big.NewInt(0), msg, - ); err == nil { - t.Error("expected", "delegation-given 1000000000000000000: min_self_delegation has to be greater than 10,000 ONE", "got", nil) - } -} - -// Test CV34: MinSelfDelegation not specified -func TestCV34(t *testing.T) { - database := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(database)) - chain := createChain(database) - msg := createValidator() - statedb.AddBalance(msg.ValidatorAddress, tenK) - // MinSelfDelegation not specified - msg.MinSelfDelegation = nil - if _, err := VerifyAndCreateValidatorFromMsg( - statedb, chain, postStakingEpoch, big.NewInt(0), msg, - ); err == nil { - t.Error("expected", "MinSelfDelegation can not be nil", "got", nil) - } -} - -// Test CV35: MinSelfDelegation < 0 -func TestCV35(t *testing.T) { - database := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(database)) - chain := createChain(database) - msg := createValidator() - statedb.AddBalance(msg.ValidatorAddress, tenK) - // MinSelfDelegation < 0 - msg.MinSelfDelegation = big.NewInt(-1) - if _, err := VerifyAndCreateValidatorFromMsg( - statedb, chain, postStakingEpoch, big.NewInt(0), msg, - ); err == nil { - t.Error("expected", "delegation-given -1: min_self_delegation has to be greater than 1 ONE", "got", nil) - } -} - -// Test CV36: amount > MaxTotalDelegation -func TestCV36(t *testing.T) { - database := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(database)) - chain := createChain(database) - msg := createValidator() - statedb.AddBalance(msg.ValidatorAddress, tenK) - // amount > MaxTotalDelegation - msg.Amount = big.NewInt(4e18) - msg.MaxTotalDelegation = big.NewInt(3e18) - if _, err := VerifyAndCreateValidatorFromMsg( - statedb, chain, postStakingEpoch, big.NewInt(0), msg, - ); err == nil { - t.Error("expected", "total delegation can not be bigger than max_total_delegation", "got", nil) - } -} - -// Test CV39: MaxTotalDelegation < 0 -func TestCV39(t *testing.T) { - database := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(database)) - chain := createChain(database) - msg := createValidator() - statedb.AddBalance(msg.ValidatorAddress, tenK) - // MaxTotalDelegation < 0 - msg.MaxTotalDelegation = big.NewInt(-1) - if _, err := VerifyAndCreateValidatorFromMsg( - statedb, chain, postStakingEpoch, big.NewInt(0), msg, - ); err == nil { - t.Error("expected", "max_total_delegation can not be less than min_self_delegation", "got", nil) + + defaultCommissionRates = staking.CommissionRates{ + Rate: pointOneDec, + MaxRate: pointNineDec, + MaxChangeRate: pointFiveDec, + } +) + +func TestCheckDuplicateFields(t *testing.T) { + tests := []struct { + bc ChainContext + sdb *state.DB + validator common.Address + identity string + pubs []shard.BLSPublicKey + + expErr error + }{ + { + // new validator + bc: makeFakeChainContextForStake(), + sdb: makeStateDBForStake(t), + validator: createValidatorAddr, + identity: makeIdentityStr("new validator"), + pubs: []shard.BLSPublicKey{blsKeys[11].pub}, + + expErr: nil, + }, + { + // validator skip self check + bc: makeFakeChainContextForStake(), + sdb: makeStateDBForStake(t), + validator: makeTestAddr(0), + identity: makeIdentityStr(0), + pubs: []shard.BLSPublicKey{blsKeys[0].pub, blsKeys[1].pub}, + + expErr: nil, + }, + { + // empty bls keys + bc: makeFakeChainContextForStake(), + sdb: makeStateDBForStake(t), + validator: createValidatorAddr, + identity: makeIdentityStr("new validator"), + pubs: []shard.BLSPublicKey{}, + + expErr: nil, + }, + { + // empty identity will not collide + bc: makeFakeChainContextForStake(), + sdb: func(t *testing.T) *state.DB { + sdb := makeStateDBForStake(t) + vw, err := sdb.ValidatorWrapper(makeTestAddr(0)) + if err != nil { + t.Fatal(err) + } + vw.Identity = "" + + err = sdb.UpdateValidatorWrapper(makeTestAddr(0), vw) + if err != nil { + t.Fatal(err) + } + return sdb + }(t), + validator: createValidatorAddr, + identity: makeIdentityStr("new validator"), + pubs: []shard.BLSPublicKey{blsKeys[11].pub}, + + expErr: nil, + }, + { + // chain error + bc: &fakeErrChainContext{}, + sdb: makeStateDBForStake(t), + validator: createValidatorAddr, + identity: makeIdentityStr("new validator"), + pubs: []shard.BLSPublicKey{blsKeys[11].pub}, + + expErr: errors.New("error intended"), + }, + { + // validators read from chain not in state + bc: func() *fakeChainContext { + chain := makeFakeChainContextForStake() + addr := makeTestAddr("not exist in state") + w := staketest.GetDefaultValidatorWrapper() + chain.vWrappers[addr] = &w + return chain + }(), + sdb: makeStateDBForStake(t), + validator: createValidatorAddr, + identity: makeIdentityStr("new validator"), + pubs: []shard.BLSPublicKey{blsKeys[11].pub}, + + expErr: errors.New("address not present in state"), + }, + { + // duplicate identity + bc: makeFakeChainContextForStake(), + sdb: makeStateDBForStake(t), + validator: createValidatorAddr, + identity: makeIdentityStr(0), + pubs: []shard.BLSPublicKey{blsKeys[11].pub}, + + expErr: errDupIdentity, + }, + { + // bls key duplication + bc: makeFakeChainContextForStake(), + sdb: makeStateDBForStake(t), + validator: createValidatorAddr, + identity: makeIdentityStr("new validator"), + pubs: []shard.BLSPublicKey{blsKeys[0].pub}, + + expErr: errDupBlsKey, + }, + } + for i, test := range tests { + err := checkDuplicateFields(test.bc, test.sdb, test.validator, test.identity, test.pubs) + + if assErr := assertError(err, test.expErr); assErr != nil { + t.Errorf("Test %v: %v", i, assErr) + } + } +} + +func TestVerifyAndCreateValidatorFromMsg(t *testing.T) { + tests := []struct { + sdb vm.StateDB + chain ChainContext + epoch *big.Int + blockNum *big.Int + msg staking.CreateValidator + + expWrapper staking.ValidatorWrapper + expErr error + }{ + { + // 0: valid request + sdb: makeStateDBForStake(t), + chain: makeFakeChainContextForStake(), + epoch: big.NewInt(defaultEpoch), + blockNum: big.NewInt(defaultBlockNumber), + msg: defaultMsgCreateValidator(), + + expWrapper: defaultExpWrapperCreateValidator(), + }, + { + // 1: nil state db + sdb: nil, + chain: makeFakeChainContextForStake(), + epoch: big.NewInt(defaultEpoch), + blockNum: big.NewInt(defaultBlockNumber), + msg: defaultMsgCreateValidator(), + + expErr: errStateDBIsMissing, + }, + { + // 2: nil chain context + sdb: makeStateDBForStake(t), + chain: nil, + epoch: big.NewInt(defaultEpoch), + blockNum: big.NewInt(defaultBlockNumber), + msg: defaultMsgCreateValidator(), + + expErr: errChainContextMissing, + }, + { + // 3: nil epoch + sdb: makeStateDBForStake(t), + chain: makeFakeChainContextForStake(), + epoch: nil, + blockNum: big.NewInt(defaultBlockNumber), + msg: defaultMsgCreateValidator(), + + expErr: errEpochMissing, + }, + { + // 4: nil block number + sdb: makeStateDBForStake(t), + chain: makeFakeChainContextForStake(), + epoch: big.NewInt(defaultEpoch), + blockNum: nil, + msg: defaultMsgCreateValidator(), + + expErr: errBlockNumMissing, + }, + { + // 5: negative amount + sdb: makeStateDBForStake(t), + chain: makeFakeChainContextForStake(), + epoch: big.NewInt(defaultEpoch), + blockNum: big.NewInt(defaultBlockNumber), + msg: func() staking.CreateValidator { + m := defaultMsgCreateValidator() + m.Amount = big.NewInt(-1) + return m + }(), + expErr: errNegativeAmount, + }, + { + // 6: the address isValidatorFlag is true + sdb: func() *state.DB { + sdb := makeStateDBForStake(t) + sdb.SetValidatorFlag(createValidatorAddr) + return sdb + }(), + chain: makeFakeChainContextForStake(), + epoch: big.NewInt(defaultEpoch), + blockNum: big.NewInt(defaultBlockNumber), + msg: defaultMsgCreateValidator(), + + expErr: errValidatorExist, + }, + { + // 7: bls collision (checkDuplicateFields) + sdb: makeStateDBForStake(t), + chain: makeFakeChainContextForStake(), + epoch: big.NewInt(defaultEpoch), + blockNum: big.NewInt(defaultBlockNumber), + msg: func() staking.CreateValidator { + m := defaultMsgCreateValidator() + m.SlotPubKeys = []shard.BLSPublicKey{blsKeys[0].pub} + return m + }(), + + expErr: errors.New("BLS key exists"), + }, + { + // 8: insufficient balance + sdb: func() *state.DB { + sdb := makeStateDBForStake(t) + bal := new(big.Int).Sub(staketest.DefaultDelAmount, common.Big1) + sdb.SetBalance(createValidatorAddr, bal) + return sdb + }(), + chain: makeFakeChainContextForStake(), + epoch: big.NewInt(defaultEpoch), + blockNum: big.NewInt(defaultBlockNumber), + msg: defaultMsgCreateValidator(), + + expErr: errInsufficientBalanceForStake, + }, + { + // 9: incorrect signature + sdb: makeStateDBForStake(t), + chain: makeFakeChainContextForStake(), + epoch: big.NewInt(defaultEpoch), + blockNum: big.NewInt(defaultBlockNumber), + msg: func() staking.CreateValidator { + m := defaultMsgCreateValidator() + m.SlotKeySigs = []shard.BLSSignature{blsKeys[12].sig} + return m + }(), + + expErr: errors.New("bls keys and corresponding signatures"), + }, + { + // 10: small self delegation amount (fail sanity check) + sdb: makeStateDBForStake(t), + chain: makeFakeChainContextForStake(), + epoch: big.NewInt(defaultEpoch), + blockNum: big.NewInt(defaultBlockNumber), + msg: func() staking.CreateValidator { + m := defaultMsgCreateValidator() + m.Amount = new(big.Int).Sub(m.MinSelfDelegation, common.Big1) + return m + }(), + + expErr: errors.New("self delegation can not be less than min_self_delegation"), + }, + { + // 11: amount exactly minSelfDelegation. Should not return error + sdb: makeStateDBForStake(t), + chain: makeFakeChainContextForStake(), + epoch: big.NewInt(defaultEpoch), + blockNum: big.NewInt(defaultBlockNumber), + msg: func() staking.CreateValidator { + m := defaultMsgCreateValidator() + m.Amount = new(big.Int).Set(m.MinSelfDelegation) + return m + }(), + + expWrapper: func() staking.ValidatorWrapper { + w := defaultExpWrapperCreateValidator() + w.Delegations[0].Amount = new(big.Int).Set(w.MinSelfDelegation) + return w + }(), + }, + } + for i, test := range tests { + w, err := VerifyAndCreateValidatorFromMsg(test.sdb, test.chain, test.epoch, + test.blockNum, &test.msg) + + if assErr := assertError(err, test.expErr); assErr != nil { + t.Errorf("Test %v: %v", i, err) + } + if err != nil || test.expErr != nil { + continue + } + + if err := staketest.CheckValidatorWrapperEqual(*w, test.expWrapper); err != nil { + t.Errorf("Test %v: %v", i, err) + } + } +} + +func defaultMsgCreateValidator() staking.CreateValidator { + pub, sig := blsKeys[11].pub, blsKeys[11].sig + cv := staking.CreateValidator{ + ValidatorAddress: createValidatorAddr, + Description: defaultDesc, + CommissionRates: defaultCommissionRates, + MinSelfDelegation: staketest.DefaultMinSelfDel, + MaxTotalDelegation: staketest.DefaultMaxTotalDel, + SlotPubKeys: []shard.BLSPublicKey{pub}, + SlotKeySigs: []shard.BLSSignature{sig}, + Amount: staketest.DefaultDelAmount, + } + return cv +} + +func defaultExpWrapperCreateValidator() staking.ValidatorWrapper { + pub := blsKeys[11].pub + v := staking.Validator{ + Address: createValidatorAddr, + SlotPubKeys: []shard.BLSPublicKey{pub}, + LastEpochInCommittee: new(big.Int), + MinSelfDelegation: staketest.DefaultMinSelfDel, + MaxTotalDelegation: staketest.DefaultMaxTotalDel, + Status: effective.Active, + Commission: staking.Commission{ + CommissionRates: defaultCommissionRates, + UpdateHeight: big.NewInt(defaultBlockNumber), + }, + Description: defaultDesc, + CreationHeight: big.NewInt(defaultBlockNumber), + } + ds := staking.Delegations{ + staking.NewDelegation(createValidatorAddr, staketest.DefaultDelAmount), + } + w := staking.ValidatorWrapper{ + Validator: v, + Delegations: ds, + BlockReward: big.NewInt(0), + } + w.Counters.NumBlocksSigned = common.Big0 + w.Counters.NumBlocksToSign = common.Big0 + return w +} + +func TestVerifyAndEditValidatorFromMsg(t *testing.T) { + tests := []struct { + sdb vm.StateDB + bc ChainContext + epoch, blockNum *big.Int + msg staking.EditValidator + expWrapper staking.ValidatorWrapper + expErr error + }{ + { + // 0: positive case + sdb: makeStateDBForStake(t), + bc: makeFakeChainContextForStake(), + epoch: big.NewInt(defaultEpoch), + blockNum: big.NewInt(defaultBlockNumber), + msg: defaultMsgEditValidator(), + + expWrapper: defaultExpWrapperEditValidator(), + }, + { + // 1: If the rate is not changed, UpdateHeight is not update + sdb: makeStateDBForStake(t), + bc: makeFakeChainContextForStake(), + epoch: big.NewInt(defaultEpoch), + blockNum: big.NewInt(defaultBlockNumber), + msg: func() staking.EditValidator { + msg := defaultMsgEditValidator() + msg.CommissionRate = nil + return msg + }(), + + expWrapper: func() staking.ValidatorWrapper { + vw := defaultExpWrapperEditValidator() + vw.UpdateHeight = big.NewInt(defaultSnapBlockNumber) + vw.Rate = pointFiveDec + return vw + }(), + }, + { + // 2: nil state db + sdb: nil, + bc: makeFakeChainContextForStake(), + epoch: big.NewInt(defaultEpoch), + blockNum: big.NewInt(defaultBlockNumber), + msg: defaultMsgEditValidator(), + + expErr: errStateDBIsMissing, + }, + { + // 3: nil chain + sdb: makeStateDBForStake(t), + bc: nil, + epoch: big.NewInt(defaultEpoch), + blockNum: big.NewInt(defaultBlockNumber), + msg: defaultMsgEditValidator(), + + expErr: errChainContextMissing, + }, + { + // 4: nil block number + sdb: makeStateDBForStake(t), + bc: makeFakeChainContextForStake(), + epoch: big.NewInt(defaultEpoch), + blockNum: nil, + msg: defaultMsgEditValidator(), + + expErr: errBlockNumMissing, + }, + { + // 5: edited validator flag not set in state + sdb: makeStateDBForStake(t), + bc: makeFakeChainContextForStake(), + epoch: big.NewInt(defaultEpoch), + blockNum: big.NewInt(defaultBlockNumber), + msg: func() staking.EditValidator { + msg := defaultMsgEditValidator() + msg.ValidatorAddress = makeTestAddr("addr not in chain") + return msg + }(), + + expErr: errValidatorNotExist, + }, + { + // 6: bls key collision + sdb: makeStateDBForStake(t), + bc: makeFakeChainContextForStake(), + epoch: big.NewInt(defaultEpoch), + blockNum: big.NewInt(defaultBlockNumber), + msg: func() staking.EditValidator { + msg := defaultMsgEditValidator() + msg.SlotKeyToAdd = &blsKeys[3].pub + msg.SlotKeyToAddSig = &blsKeys[3].sig + return msg + }(), + + expErr: errDupBlsKey, + }, + { + // 7: validatorWrapper not in state + sdb: func() *state.DB { + sdb := makeStateDBForStake(t) + sdb.SetValidatorFlag(makeTestAddr("someone")) + return sdb + }(), + bc: func() *fakeChainContext { + chain := makeFakeChainContextForStake() + addr := makeTestAddr("someone") + w := staketest.GetDefaultValidatorWrapperWithAddr(addr, nil) + chain.vWrappers[addr] = &w + return chain + }(), + epoch: big.NewInt(defaultEpoch), + blockNum: big.NewInt(defaultBlockNumber), + msg: func() staking.EditValidator { + msg := defaultMsgEditValidator() + msg.ValidatorAddress = makeTestAddr("someone") + return msg + }(), + + expErr: errors.New("address not present in state"), + }, + { + // 8: signature cannot be verified + sdb: makeStateDBForStake(t), + bc: makeFakeChainContextForStake(), + epoch: big.NewInt(defaultEpoch), + blockNum: big.NewInt(defaultBlockNumber), + msg: func() staking.EditValidator { + msg := defaultMsgEditValidator() + msg.SlotKeyToAddSig = &blsKeys[13].sig + return msg + }(), + + expErr: errors.New("bls keys and corresponding signatures could not be verified"), + }, + { + // 9: Rate is greater the maxRate + sdb: makeStateDBForStake(t), + bc: func() *fakeChainContext { + chain := makeFakeChainContextForStake() + vw := chain.vWrappers[validatorAddr] + vw.Rate = pointSevenDec + chain.vWrappers[validatorAddr] = vw + return chain + }(), + epoch: big.NewInt(defaultEpoch), + blockNum: big.NewInt(defaultBlockNumber), + msg: func() staking.EditValidator { + msg := defaultMsgEditValidator() + msg.CommissionRate = &oneDec + return msg + }(), + + expErr: errCommissionRateChangeTooHigh, + }, + { + // 10: validator not in snapshot + sdb: makeStateDBForStake(t), + bc: func() *fakeChainContext { + chain := makeFakeChainContextForStake() + delete(chain.vWrappers, validatorAddr) + return chain + }(), + epoch: big.NewInt(defaultEpoch), + blockNum: big.NewInt(defaultBlockNumber), + msg: defaultMsgEditValidator(), + + expErr: errors.New("validator snapshot not found"), + }, + { + // 11: rate is greater than maxChangeRate + sdb: makeStateDBForStake(t), + bc: makeFakeChainContextForStake(), + epoch: big.NewInt(defaultEpoch), + blockNum: big.NewInt(defaultBlockNumber), + msg: func() staking.EditValidator { + msg := defaultMsgEditValidator() + msg.CommissionRate = &pointEightFiveDec + return msg + }(), + + expErr: errCommissionRateChangeTooFast, + }, + { + // 12: fails in sanity check (rate below zero) + sdb: makeStateDBForStake(t), + bc: func() *fakeChainContext { + chain := makeFakeChainContextForStake() + vw := chain.vWrappers[validatorAddr] + vw.Rate = pointOneDec + chain.vWrappers[validatorAddr] = vw + return chain + }(), + epoch: big.NewInt(defaultEpoch), + blockNum: big.NewInt(defaultBlockNumber), + msg: func() staking.EditValidator { + msg := defaultMsgEditValidator() + msg.CommissionRate = &negRate + return msg + }(), + + expErr: errors.New("rate should be a value ranging from 0.0 to 1.0"), + }, + { + // 13: cannot update a banned validator + sdb: func(t *testing.T) *state.DB { + sdb := makeStateDBForStake(t) + vw, err := sdb.ValidatorWrapper(validatorAddr) + if err != nil { + t.Fatal(err) + } + vw.Status = effective.Banned + if err := sdb.UpdateValidatorWrapper(validatorAddr, vw); err != nil { + t.Fatal(err) + } + return sdb + }(t), + bc: makeFakeChainContextForStake(), + epoch: big.NewInt(defaultEpoch), + blockNum: big.NewInt(defaultBlockNumber), + msg: defaultMsgEditValidator(), + + expErr: errors.New("banned status"), + }, + } + for i, test := range tests { + w, err := VerifyAndEditValidatorFromMsg(test.sdb, test.bc, test.epoch, test.blockNum, + &test.msg) + + if assErr := assertError(err, test.expErr); assErr != nil { + t.Errorf("Test %v: unexpected Error: %v", i, assErr) + } + if err != nil || test.expErr != nil { + continue + } + + if err := staketest.CheckValidatorWrapperEqual(*w, test.expWrapper); err != nil { + t.Errorf("Test %v: %v", i, err) + } + } +} + +var ( + editDesc = staking.Description{ + Name: "batman", + Identity: "batman", + Website: "", + SecurityContact: "", + Details: "", + } + + editExpDesc = staking.Description{ + Name: "batman", + Identity: "batman", + Website: "Secret Website", + SecurityContact: "LicenseToKill", + Details: "blah blah blah", + } +) + +func defaultMsgEditValidator() staking.EditValidator { + var ( + pub0Copy shard.BLSPublicKey + pub12Copy shard.BLSPublicKey + sig12Copy shard.BLSSignature + ) + copy(pub0Copy[:], blsKeys[0].pub[:]) + copy(pub12Copy[:], blsKeys[12].pub[:]) + copy(sig12Copy[:], blsKeys[12].sig[:]) + + return staking.EditValidator{ + ValidatorAddress: validatorAddr, + Description: editDesc, + CommissionRate: &pointTwoDec, + SlotKeyToRemove: &pub0Copy, + SlotKeyToAdd: &pub12Copy, + SlotKeyToAddSig: &sig12Copy, + EPOSStatus: effective.Inactive, + } +} + +func defaultExpWrapperEditValidator() staking.ValidatorWrapper { + w := makeVWrapperByIndex(validatorIndex) + w.SlotPubKeys = append(w.SlotPubKeys[1:], blsKeys[12].pub) + w.Description = editExpDesc + w.Rate = pointTwoDec + w.UpdateHeight = big.NewInt(defaultBlockNumber) + w.Status = effective.Inactive + return w +} + +func TestVerifyAndDelegateFromMsg(t *testing.T) { + tests := []struct { + sdb vm.StateDB + msg staking.Delegate + + expVWrapper staking.ValidatorWrapper + expAmt *big.Int + expErr error + }{ + { + // 0: new delegate + sdb: makeStateDBForStake(t), + msg: defaultMsgDelegate(), + + expVWrapper: defaultExpVWrapperDelegate(), + expAmt: tenKOnes, + }, + { + // 1: add amount to current delegate + sdb: makeStateDBForStake(t), + msg: defaultMsgSelfDelegate(), + + expVWrapper: defaultExpVWrapperSelfDelegate(), + expAmt: tenKOnes, + }, + { + // 2: nil state db + sdb: nil, + msg: defaultMsgDelegate(), + + expErr: errStateDBIsMissing, + }, + { + // 3: validatorFlag not set + sdb: makeStateDBForStake(t), + msg: func() staking.Delegate { + msg := defaultMsgDelegate() + msg.ValidatorAddress = makeTestAddr("not in state") + return msg + }(), + + expErr: errValidatorNotExist, + }, + { + // 4: negative amount + sdb: makeStateDBForStake(t), + msg: func() staking.Delegate { + msg := defaultMsgDelegate() + msg.Amount = big.NewInt(-1) + return msg + }(), + + expErr: errNegativeAmount, + }, + { + // 5: small amount + sdb: makeStateDBForStake(t), + msg: func() staking.Delegate { + msg := defaultMsgDelegate() + msg.Amount = big.NewInt(100) + return msg + }(), + + expErr: errDelegationTooSmall, + }, + { + // 6: missing validator wrapper + sdb: func() *state.DB { + sdb := makeStateDBForStake(t) + sdb.SetValidatorFlag(createValidatorAddr) + return sdb + }(), + msg: func() staking.Delegate { + d := defaultMsgDelegate() + d.ValidatorAddress = createValidatorAddr + return d + }(), + + expErr: errors.New("address not present in state"), + }, + { + // 7: cannot transfer since not enough amount + sdb: func() *state.DB { + sdb := makeStateDBForStake(t) + sdb.SetBalance(delegatorAddr, big.NewInt(100)) + return sdb + }(), + msg: defaultMsgDelegate(), + + expErr: errInsufficientBalanceForStake, + }, + { + // 8: self delegation not pass sanity check + sdb: makeStateDBForStake(t), + msg: func() staking.Delegate { + d := defaultMsgSelfDelegate() + d.Amount = hundredKOnes + return d + }(), + + expErr: errors.New(" total delegation can not be bigger than max_total_delegation"), + }, + { + // 9: Delegation does not pass sanity check + sdb: makeStateDBForStake(t), + msg: func() staking.Delegate { + d := defaultMsgDelegate() + d.Amount = hundredKOnes + return d + }(), + + expErr: errors.New(" total delegation can not be bigger than max_total_delegation"), + }, + } + for i, test := range tests { + w, amt, err := VerifyAndDelegateFromMsg(test.sdb, &test.msg) + + if assErr := assertError(err, test.expErr); assErr != nil { + t.Errorf("Test %v: %v", i, assErr) + } + if err != nil || test.expErr != nil { + continue + } + + if amt.Cmp(test.expAmt) != 0 { + t.Errorf("Test %v: unexpected amount %v / %v", i, amt, test.expAmt) + } + if err := staketest.CheckValidatorWrapperEqual(*w, test.expVWrapper); err != nil { + t.Errorf("Test %v: %v", i, err) + } + } +} + +func defaultMsgDelegate() staking.Delegate { + return staking.Delegate{ + DelegatorAddress: delegatorAddr, + ValidatorAddress: validatorAddr, + Amount: new(big.Int).Set(tenKOnes), + } +} + +func defaultExpVWrapperDelegate() staking.ValidatorWrapper { + w := makeVWrapperByIndex(validatorIndex) + w.Delegations = append(w.Delegations, staking.NewDelegation(delegatorAddr, tenKOnes)) + return w +} + +func defaultMsgSelfDelegate() staking.Delegate { + return staking.Delegate{ + DelegatorAddress: validatorAddr, + ValidatorAddress: validatorAddr, + Amount: new(big.Int).Set(tenKOnes), + } +} + +func defaultExpVWrapperSelfDelegate() staking.ValidatorWrapper { + w := makeVWrapperByIndex(validatorIndex) + w.Delegations[0].Amount = new(big.Int).Add(tenKOnes, staketest.DefaultDelAmount) + return w +} + +func TestVerifyAndUndelegateFromMsg(t *testing.T) { + tests := []struct { + sdb vm.StateDB + epoch *big.Int + msg staking.Undelegate + + expVWrapper staking.ValidatorWrapper + expErr error + }{ + { + // 0: Undelegate at delegation with an entry already exist at the same epoch. + // Will increase the amount in undelegate entry + sdb: makeDefaultStateForUndelegate(t), + epoch: big.NewInt(defaultEpoch), + msg: defaultMsgUndelegate(), + + expVWrapper: defaultExpVWrapperUndelegateSameEpoch(t), + }, + { + // 1: Undelegate with undelegation entry exist but not in same epoch. + // Will create a new undelegate entry + sdb: makeDefaultStateForUndelegate(t), + epoch: big.NewInt(defaultNextEpoch), + msg: defaultMsgUndelegate(), + + expVWrapper: defaultExpVWrapperUndelegateNextEpoch(t), + }, + { + // 2: Undelegate from a delegation record with no undelegation entry. + // Will create a new undelegate entry + sdb: makeDefaultStateForUndelegate(t), + epoch: big.NewInt(defaultEpoch), + msg: defaultMsgSelfUndelegate(), + + expVWrapper: defaultVWrapperSelfUndelegate(t), + }, + { + // 3: Self delegation below min self delegation, change status to Inactive + sdb: makeDefaultStateForUndelegate(t), + epoch: big.NewInt(defaultEpoch), + msg: func() staking.Undelegate { + msg := defaultMsgSelfUndelegate() + msg.Amount = new(big.Int).Set(fifteenKOnes) + return msg + }(), + + expVWrapper: func(t *testing.T) staking.ValidatorWrapper { + w := defaultVWrapperSelfUndelegate(t) + + w.Delegations[0].Amount = new(big.Int).Set(fiveKOnes) + w.Delegations[0].Undelegations[0].Amount = new(big.Int).Set(fifteenKOnes) + w.Status = effective.Inactive + + return w + }(t), + }, + { + // 4: Extract tokens from banned validator + sdb: func(t *testing.T) *state.DB { + sdb := makeDefaultStateForUndelegate(t) + w, err := sdb.ValidatorWrapper(validatorAddr) + if err != nil { + t.Fatal(err) + } + w.Status = effective.Banned + if err := sdb.UpdateValidatorWrapper(validatorAddr, w); err != nil { + t.Fatal(err) + } + return sdb + }(t), + epoch: big.NewInt(defaultEpoch), + msg: func() staking.Undelegate { + msg := defaultMsgSelfUndelegate() + msg.Amount = new(big.Int).Set(fifteenKOnes) + return msg + }(), + + expVWrapper: func(t *testing.T) staking.ValidatorWrapper { + w := defaultVWrapperSelfUndelegate(t) + + w.Delegations[0].Amount = new(big.Int).Set(fiveKOnes) + w.Delegations[0].Undelegations[0].Amount = new(big.Int).Set(fifteenKOnes) + w.Status = effective.Banned + + return w + }(t), + }, + { + // 5: nil state db + sdb: nil, + epoch: big.NewInt(defaultEpoch), + msg: defaultMsgUndelegate(), + + expErr: errStateDBIsMissing, + }, + { + // 6: nil epoch + sdb: makeDefaultStateForUndelegate(t), + epoch: nil, + msg: defaultMsgUndelegate(), + + expErr: errEpochMissing, + }, + { + // 7: negative amount + sdb: makeDefaultStateForUndelegate(t), + epoch: big.NewInt(defaultEpoch), + msg: func() staking.Undelegate { + msg := defaultMsgUndelegate() + msg.Amount = big.NewInt(-1) + return msg + }(), + + expErr: errNegativeAmount, + }, + { + // 8: validator flag not set + sdb: func() *state.DB { + sdb := makeStateDBForStake(t) + w := makeVWrapperByIndex(6) + if err := sdb.UpdateValidatorWrapper(makeTestAddr(6), &w); err != nil { + t.Fatal(err) + } + return sdb + }(), + epoch: big.NewInt(defaultEpoch), + msg: func() staking.Undelegate { + msg := defaultMsgUndelegate() + msg.ValidatorAddress = makeTestAddr(6) + return msg + }(), + + expErr: errValidatorNotExist, + }, + { + // 9: vWrapper not in state + sdb: func() *state.DB { + sdb := makeStateDBForStake(t) + sdb.SetValidatorFlag(makeTestAddr(6)) + return sdb + }(), + epoch: big.NewInt(defaultEpoch), + msg: func() staking.Undelegate { + msg := defaultMsgUndelegate() + msg.ValidatorAddress = makeTestAddr(6) + return msg + }(), + + expErr: errors.New("address not present in state"), + }, + { + // 10: Insufficient balance to undelegate + sdb: makeDefaultStateForUndelegate(t), + epoch: big.NewInt(defaultEpoch), + msg: func() staking.Undelegate { + msg := defaultMsgUndelegate() + msg.Amount = new(big.Int).Set(hundredKOnes) + return msg + }(), + + expErr: errors.New("insufficient balance to undelegate"), + }, + { + // 11: No delegation record + sdb: makeDefaultStateForUndelegate(t), + epoch: big.NewInt(defaultEpoch), + msg: func() staking.Undelegate { + msg := defaultMsgUndelegate() + msg.DelegatorAddress = makeTestAddr("not exist") + return msg + }(), + + expErr: errNoDelegationToUndelegate, + }, + } + for i, test := range tests { + w, err := VerifyAndUndelegateFromMsg(test.sdb, test.epoch, &test.msg) + + if assErr := assertError(err, test.expErr); assErr != nil { + t.Errorf("Test %v: %v", i, assErr) + } + if err != nil || test.expErr != nil { + continue + } + + if err := staketest.CheckValidatorWrapperEqual(*w, test.expVWrapper); err != nil { + t.Errorf("Test %v: %v", i, err) + } + } +} + +func makeDefaultSnapVWrapperForUndelegate(t *testing.T) staking.ValidatorWrapper { + w := makeVWrapperByIndex(validatorIndex) + + newDelegation := staking.NewDelegation(delegatorAddr, new(big.Int).Set(twentyKOnes)) + if err := newDelegation.Undelegate(big.NewInt(defaultEpoch), fiveKOnes); err != nil { + t.Fatal(err) + } + w.Delegations = append(w.Delegations, newDelegation) + + return w +} + +func makeDefaultStateForUndelegate(t *testing.T) *state.DB { + sdb := makeStateDBForStake(t) + w := makeDefaultSnapVWrapperForUndelegate(t) + + if err := sdb.UpdateValidatorWrapper(validatorAddr, &w); err != nil { + t.Fatal(err) + } + sdb.IntermediateRoot(true) + return sdb +} + +// undelegate from delegator which has already go one entry for undelegation +func defaultMsgUndelegate() staking.Undelegate { + return staking.Undelegate{ + DelegatorAddress: delegatorAddr, + ValidatorAddress: validatorAddr, + Amount: fiveKOnes, + } +} + +func defaultExpVWrapperUndelegateSameEpoch(t *testing.T) staking.ValidatorWrapper { + w := makeDefaultSnapVWrapperForUndelegate(t) + + amt := w.Delegations[1].Undelegations[0].Amount + w.Delegations[1].Undelegations[0].Amount = new(big.Int). + Add(w.Delegations[1].Undelegations[0].Amount, amt) + w.Delegations[1].Amount = new(big.Int).Sub(w.Delegations[1].Amount, fiveKOnes) + + return w +} + +func defaultExpVWrapperUndelegateNextEpoch(t *testing.T) staking.ValidatorWrapper { + w := makeDefaultSnapVWrapperForUndelegate(t) + + w.Delegations[1].Undelegations = append(w.Delegations[1].Undelegations, + staking.Undelegation{Amount: fiveKOnes, Epoch: big.NewInt(defaultNextEpoch)}) + w.Delegations[1].Amount = new(big.Int).Sub(w.Delegations[1].Amount, fiveKOnes) + + return w +} + +// undelegate from self undelegation (new undelegates) +func defaultMsgSelfUndelegate() staking.Undelegate { + return staking.Undelegate{ + DelegatorAddress: validatorAddr, + ValidatorAddress: validatorAddr, + Amount: fiveKOnes, + } +} + +func defaultVWrapperSelfUndelegate(t *testing.T) staking.ValidatorWrapper { + w := makeDefaultSnapVWrapperForUndelegate(t) + + w.Delegations[0].Undelegations = staking.Undelegations{ + staking.Undelegation{Amount: fiveKOnes, Epoch: big.NewInt(defaultEpoch)}, + } + w.Delegations[0].Amount = new(big.Int).Sub(w.Delegations[0].Amount, fiveKOnes) + + return w +} + +var ( + reward00 = twentyKOnes + reward01 = tenKOnes + reward10 = thirtyKOnes + reward11 = twentyFiveKOnes +) + +func TestVerifyAndCollectRewardsFromDelegation(t *testing.T) { + tests := []struct { + sdb vm.StateDB + ds []staking.DelegationIndex + + expVWrappers []*staking.ValidatorWrapper + expTotalRewards *big.Int + expErr error + }{ + { + // 0: Positive test case + sdb: makeStateForReward(t), + ds: makeMsgCollectRewards(), + + expVWrappers: expVWrappersForReward(), + expTotalRewards: new(big.Int).Add(reward01, reward11), + }, + { + // 1: No rewards to collect + sdb: makeStateDBForStake(t), + ds: []staking.DelegationIndex{{ValidatorAddress: validatorAddr2, Index: 0}}, + + expErr: errNoRewardsToCollect, + }, + { + // 2: nil state db + sdb: nil, + ds: makeMsgCollectRewards(), + + expErr: errStateDBIsMissing, + }, + { + // 3: ValidatorWrapper not in state + sdb: makeStateForReward(t), + ds: func() []staking.DelegationIndex { + msg := makeMsgCollectRewards() + msg[1].ValidatorAddress = makeTestAddr("addr not exist") + return msg + }(), + + expErr: errors.New("address not present in state"), + }, + { + // 4: Wrong input message - index out of range + sdb: makeStateForReward(t), + ds: func() []staking.DelegationIndex { + dis := makeMsgCollectRewards() + dis[1].Index = 2 + return dis + }(), + + expErr: errors.New("index out of bound"), + }, + } + for i, test := range tests { + ws, tReward, err := VerifyAndCollectRewardsFromDelegation(test.sdb, test.ds) + + if assErr := assertError(err, test.expErr); assErr != nil { + t.Fatalf("Test %v: %v", i, err) + } + if err != nil || test.expErr != nil { + continue + } + + if len(ws) != len(test.expVWrappers) { + t.Fatalf("vwrapper size unexpected: %v / %v", len(ws), len(test.expVWrappers)) + } + for wi := range ws { + if err := staketest.CheckValidatorWrapperEqual(*ws[wi], *test.expVWrappers[wi]); err != nil { + t.Errorf("%v wrapper: %v", wi, err) + } + } + if tReward.Cmp(test.expTotalRewards) != 0 { + t.Errorf("Test %v: total Rewards unexpected: %v / %v", i, tReward, test.expTotalRewards) + } + } +} + +func makeMsgCollectRewards() []staking.DelegationIndex { + dis := []staking.DelegationIndex{ + { + ValidatorAddress: validatorAddr, + Index: 1, + BlockNum: big.NewInt(defaultBlockNumber), + }, { + ValidatorAddress: validatorAddr2, + Index: 1, + BlockNum: big.NewInt(defaultBlockNumber), + }, + } + return dis +} + +func makeStateForReward(t *testing.T) *state.DB { + sdb := makeStateDBForStake(t) + + rewards0 := []*big.Int{reward00, reward01} + if err := addStateRewardForAddr(sdb, validatorAddr, rewards0); err != nil { + t.Fatal(err) + } + rewards1 := []*big.Int{reward10, reward11} + if err := addStateRewardForAddr(sdb, validatorAddr2, rewards1); err != nil { + t.Fatal(err) + } + + sdb.IntermediateRoot(true) + return sdb +} + +func addStateRewardForAddr(sdb *state.DB, addr common.Address, rewards []*big.Int) error { + w, err := sdb.ValidatorWrapper(addr) + if err != nil { + return err + } + w.Delegations = append(w.Delegations, + staking.NewDelegation(delegatorAddr, new(big.Int).Set(twentyKOnes)), + ) + w.Delegations[1].Undelegations = staking.Undelegations{} + w.Delegations[0].Reward = new(big.Int).Set(rewards[0]) + w.Delegations[1].Reward = new(big.Int).Set(rewards[1]) + + return sdb.UpdateValidatorWrapper(addr, w) +} + +func expVWrappersForReward() []*staking.ValidatorWrapper { + w1 := makeVWrapperByIndex(validatorIndex) + w1.Delegations = append(w1.Delegations, + staking.NewDelegation(delegatorAddr, new(big.Int).Set(twentyKOnes)), + ) + w1.Delegations[1].Undelegations = staking.Undelegations{} + w1.Delegations[0].Reward = new(big.Int).Set(reward00) + w1.Delegations[1].Reward = new(big.Int).SetUint64(0) + + w2 := makeVWrapperByIndex(validator2Index) + w2.Delegations = append(w2.Delegations, + staking.NewDelegation(delegatorAddr, new(big.Int).Set(twentyKOnes)), + ) + w2.Delegations[1].Undelegations = staking.Undelegations{} + w2.Delegations[0].Reward = new(big.Int).Set(reward10) + w2.Delegations[1].Reward = new(big.Int).SetUint64(0) + return []*staking.ValidatorWrapper{&w1, &w2} +} + +// makeFakeChainContextForStake makes the default fakeChainContext for staking test +func makeFakeChainContextForStake() *fakeChainContext { + ws := makeVWrappersForStake(defNumWrappersInState, defNumPubPerAddr) + return makeFakeChainContext(ws) +} + +// makeStateDBForStake make the default state db for staking test +func makeStateDBForStake(t *testing.T) *state.DB { + sdb, err := newTestStateDB() + if err != nil { + t.Fatal(err) + } + ws := makeVWrappersForStake(defNumWrappersInState, defNumPubPerAddr) + if err := updateStateValidators(sdb, ws); err != nil { + t.Fatalf("make default state: %v", err) + } + sdb.AddBalance(createValidatorAddr, hundredKOnes) + sdb.AddBalance(delegatorAddr, hundredKOnes) + + sdb.IntermediateRoot(true) + + return sdb +} + +func updateStateValidators(sdb *state.DB, ws []*staking.ValidatorWrapper) error { + for i, w := range ws { + sdb.SetValidatorFlag(w.Address) + sdb.AddBalance(w.Address, hundredKOnes) + if err := sdb.UpdateValidatorWrapper(w.Address, w); err != nil { + return fmt.Errorf("update %v vw error: %v", i, err) + } + } + return nil +} + +func makeVWrapperByIndex(index int) staking.ValidatorWrapper { + pubGetter := newBLSPubGetter(blsKeys[index*defNumPubPerAddr:]) + + return makeStateVWrapperFromGetter(index, defNumPubPerAddr, pubGetter) +} + +func newTestStateDB() (*state.DB, error) { + return state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase())) +} + +// makeVWrappersForStake makes the default staking.ValidatorWrappers for +// initialization of default state db for staking test +func makeVWrappersForStake(num, numPubsPerVal int) []*staking.ValidatorWrapper { + ws := make([]*staking.ValidatorWrapper, 0, num) + pubGetter := newBLSPubGetter(blsKeys) + for i := 0; i != num; i++ { + w := makeStateVWrapperFromGetter(i, numPubsPerVal, pubGetter) + ws = append(ws, &w) + } + return ws +} + +func makeStateVWrapperFromGetter(index int, numPubs int, pubGetter *BLSPubGetter) staking.ValidatorWrapper { + addr := makeTestAddr(index) + pubs := make([]shard.BLSPublicKey, 0, numPubs) + for i := 0; i != numPubs; i++ { + pubs = append(pubs, pubGetter.getPub()) + } + w := staketest.GetDefaultValidatorWrapperWithAddr(addr, pubs) + w.Identity = makeIdentityStr(index) + w.UpdateHeight = big.NewInt(defaultSnapBlockNumber) + return w +} + +type BLSPubGetter struct { + keys []blsPubSigPair + index int +} + +func newBLSPubGetter(keys []blsPubSigPair) *BLSPubGetter { + return &BLSPubGetter{ + keys: keys, + index: 0, + } +} + +func (g *BLSPubGetter) getPub() shard.BLSPublicKey { + key := g.keys[g.index] + g.index++ + return key.pub +} + +// fakeChainContext is the fake structure of ChainContext for testing +type fakeChainContext struct { + vWrappers map[common.Address]*staking.ValidatorWrapper +} + +func makeFakeChainContext(ws []*staking.ValidatorWrapper) *fakeChainContext { + m := make(map[common.Address]*staking.ValidatorWrapper) + for _, w := range ws { + wCpy := staketest.CopyValidatorWrapper(*w) + m[w.Address] = &wCpy + } + return &fakeChainContext{ + vWrappers: m, + } +} + +func (chain *fakeChainContext) ReadValidatorList() ([]common.Address, error) { + vs := make([]common.Address, 0, len(chain.vWrappers)) + for addr := range chain.vWrappers { + vs = append(vs, addr) + } + return vs, nil +} + +func (chain *fakeChainContext) Engine() consensus_engine.Engine { + return nil +} + +func (chain *fakeChainContext) GetHeader(common.Hash, uint64) *block.Header { + return nil +} + +func (chain *fakeChainContext) ReadDelegationsByDelegator(common.Address) (staking.DelegationIndexes, error) { + return nil, nil +} + +func (chain *fakeChainContext) ReadValidatorSnapshot(addr common.Address) (*staking.ValidatorSnapshot, error) { + w, ok := chain.vWrappers[addr] + if !ok { + return nil, fmt.Errorf("addr not exist in snapshot") + } + cp := staketest.CopyValidatorWrapper(*w) + return &staking.ValidatorSnapshot{ + Validator: &cp, + Epoch: big.NewInt(defaultEpoch), + }, nil +} + +type fakeErrChainContext struct{} + +func (chain *fakeErrChainContext) ReadValidatorList() ([]common.Address, error) { + return nil, errors.New("error intended from chain") +} + +func (chain *fakeErrChainContext) Engine() consensus_engine.Engine { + return nil +} + +func (chain *fakeErrChainContext) GetHeader(common.Hash, uint64) *block.Header { + return nil +} + +func (chain *fakeErrChainContext) ReadDelegationsByDelegator(common.Address) (staking.DelegationIndexes, error) { + return nil, nil +} + +func (chain *fakeErrChainContext) ReadValidatorSnapshot(common.Address) (*staking.ValidatorSnapshot, error) { + return nil, errors.New("error intended") +} + +func makeIdentityStr(item interface{}) string { + return fmt.Sprintf("harmony-one-%v", item) +} + +func makeTestAddr(item interface{}) common.Address { + s := fmt.Sprintf("harmony-one-%v", item) + return common.BytesToAddress([]byte(s)) +} + +func makeKeyPairs(size int) []blsPubSigPair { + pairs := make([]blsPubSigPair, 0, size) + for i := 0; i != size; i++ { + pairs = append(pairs, makeBLSKeyPair()) + } + return pairs +} + +type blsPubSigPair struct { + pub shard.BLSPublicKey + sig shard.BLSSignature +} + +func makeBLSKeyPair() blsPubSigPair { + blsPriv := bls.RandPrivateKey() + blsPub := blsPriv.GetPublicKey() + msgHash := hash.Keccak256([]byte(staking.BLSVerificationStr)) + sig := blsPriv.SignHash(msgHash) + + var shardPub shard.BLSPublicKey + copy(shardPub[:], blsPub.Serialize()) + + var shardSig shard.BLSSignature + copy(shardSig[:], sig.Serialize()) + + return blsPubSigPair{shardPub, shardSig} +} + +func assertError(got, expect error) error { + if (got == nil) != (expect == nil) { + return fmt.Errorf("unexpected error [%v] / [%v]", got, expect) + } + if (got == nil) || (expect == nil) { + return nil + } + if !strings.Contains(got.Error(), expect.Error()) { + return fmt.Errorf("unexpected error [%v] / [%v]", got, expect) } + return nil } diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index 55a999a1d..d41b546f5 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -116,12 +116,12 @@ func stakingCreateValidatorTransaction(key *ecdsa.PrivateKey) (*staking.StakingT MaxRate: maxRate, MaxChangeRate: maxChangeRate, }, - MinSelfDelegation: tenK, - MaxTotalDelegation: twelveK, + MinSelfDelegation: tenKOnes, + MaxTotalDelegation: twelveKOnes, ValidatorAddress: crypto.PubkeyToAddress(key.PublicKey), SlotPubKeys: []shard.BLSPublicKey{pub}, SlotKeySigs: []shard.BLSSignature{sig}, - Amount: tenK, + Amount: tenKOnes, } } @@ -374,8 +374,8 @@ func TestErrorSink(t *testing.T) { t.Error("expected errored transaction in tx pool") } - pool.currentState.SetBalance(from, twelveK) - pool.currentState.SetBalance(fromStx, twelveK) + pool.currentState.SetBalance(from, twelveKOnes) + pool.currentState.SetBalance(fromStx, twelveKOnes) if err := pool.AddRemote(tx); err != nil { t.Error("expected successful transaction got", err) } @@ -403,7 +403,7 @@ func TestCreateValidatorTransaction(t *testing.T) { t.Errorf("cannot create new staking transaction, %v\n", err) } senderAddr, _ := stx.SenderAddress() - pool.currentState.AddBalance(senderAddr, tenK) + pool.currentState.AddBalance(senderAddr, tenKOnes) // Add additional create validator tx cost pool.currentState.AddBalance(senderAddr, cost) @@ -429,7 +429,7 @@ func TestMixedTransactions(t *testing.T) { t.Errorf("cannot create new staking transaction, %v\n", err) } stxAddr, _ := stx.SenderAddress() - pool.currentState.AddBalance(stxAddr, tenK) + pool.currentState.AddBalance(stxAddr, tenKOnes) // Add additional create validator tx cost pool.currentState.AddBalance(stxAddr, cost) diff --git a/staking/slash/double-sign_test.go b/staking/slash/double-sign_test.go index 64a1f6093..3883234a4 100644 --- a/staking/slash/double-sign_test.go +++ b/staking/slash/double-sign_test.go @@ -780,7 +780,7 @@ func makeVoteData(kp blsKeyPair, block *types.Block) Vote { } func makeTestAddress(item interface{}) common.Address { - s := fmt.Sprintf("harmony.one.%s", item) + s := fmt.Sprintf("harmony.one.%v", item) return common.BytesToAddress([]byte(s)) } diff --git a/staking/types/test/copy.go b/staking/types/test/copy.go index 66483cd24..fcb827d9a 100644 --- a/staking/types/test/copy.go +++ b/staking/types/test/copy.go @@ -8,8 +8,8 @@ import ( ) // CopyValidatorWrapper deep copies staking.ValidatorWrapper -func CopyValidatorWrapper(w *staking.ValidatorWrapper) *staking.ValidatorWrapper { - cp := &staking.ValidatorWrapper{ +func CopyValidatorWrapper(w staking.ValidatorWrapper) staking.ValidatorWrapper { + cp := staking.ValidatorWrapper{ Validator: CopyValidator(w.Validator), Delegations: CopyDelegations(w.Delegations), } diff --git a/staking/types/test/copy_test.go b/staking/types/test/copy_test.go index 162cf29e8..fc200e961 100644 --- a/staking/types/test/copy_test.go +++ b/staking/types/test/copy_test.go @@ -27,9 +27,9 @@ func TestCopyValidatorWrapper(t *testing.T) { {staking.ValidatorWrapper{}}, } for i, test := range tests { - cp := CopyValidatorWrapper(&test.w) + cp := CopyValidatorWrapper(test.w) - if err := assertValidatorWrapperDeepCopy(*cp, test.w); err != nil { + if err := assertValidatorWrapperDeepCopy(cp, test.w); err != nil { t.Errorf("Test %v: %v", i, err) } } diff --git a/staking/types/test/equal.go b/staking/types/test/equal.go new file mode 100644 index 000000000..a9eab1ff0 --- /dev/null +++ b/staking/types/test/equal.go @@ -0,0 +1,209 @@ +package staketest + +import ( + "fmt" + "math/big" + + "github.com/harmony-one/harmony/numeric" + "github.com/harmony-one/harmony/shard" + staking "github.com/harmony-one/harmony/staking/types" +) + +// CheckValidatorWrapperEqual checks the equality of staking.ValidatorWrapper. If not equal, an +// error is returned. Note nil pointer is treated as zero in this compare function. +func CheckValidatorWrapperEqual(w1, w2 staking.ValidatorWrapper) error { + if err := checkValidatorWrapperEqual(w1, w2); err != nil { + return fmt.Errorf("wrapper%v", err) + } + return nil +} + +// CheckValidatorEqual checks the equality of validator. If not equal, an +// error is returned. Note nil pointer is treated as zero in this compare function. +func CheckValidatorEqual(v1, v2 staking.Validator) error { + if err := checkValidatorEqual(v1, v2); err != nil { + return fmt.Errorf("validator%v", err) + } + return nil +} + +func checkValidatorWrapperEqual(w1, w2 staking.ValidatorWrapper) error { + if err := checkValidatorEqual(w1.Validator, w2.Validator); err != nil { + return fmt.Errorf(".Validator%v", err) + } + if err := checkDelegationsEqual(w1.Delegations, w2.Delegations); err != nil { + return fmt.Errorf(".Delegations%v", err) + } + if err := checkBigIntEqual(w1.Counters.NumBlocksToSign, w2.Counters.NumBlocksToSign); err != nil { + return fmt.Errorf("..Counters.NumBlocksToSign %v", err) + } + if err := checkBigIntEqual(w1.Counters.NumBlocksSigned, w2.Counters.NumBlocksSigned); err != nil { + return fmt.Errorf("..Counters.NumBlocksSigned %v", err) + } + if err := checkBigIntEqual(w1.BlockReward, w2.BlockReward); err != nil { + return fmt.Errorf(".BlockReward %v", err) + } + return nil +} + +func checkValidatorEqual(v1, v2 staking.Validator) error { + if v1.Address != v2.Address { + return fmt.Errorf(".Address not equal: %x / %x", v1.Address, v2.Address) + } + if err := checkPubKeysEqual(v1.SlotPubKeys, v2.SlotPubKeys); err != nil { + return fmt.Errorf(".SlotPubKeys%v", err) + } + if err := checkBigIntEqual(v1.LastEpochInCommittee, v2.LastEpochInCommittee); err != nil { + return fmt.Errorf(".LastEpochInCommittee %v", err) + } + if err := checkBigIntEqual(v1.MinSelfDelegation, v2.MinSelfDelegation); err != nil { + return fmt.Errorf(".MinSelfDelegation %v", err) + } + if err := checkBigIntEqual(v1.MaxTotalDelegation, v2.MaxTotalDelegation); err != nil { + return fmt.Errorf(".MaxTotalDelegation %v", err) + } + if v1.Status != v2.Status { + return fmt.Errorf(".Status not equal: %v / %v", v1.Status, v2.Status) + } + if err := checkCommissionEqual(v1.Commission, v2.Commission); err != nil { + return fmt.Errorf(".Commission%v", err) + } + if err := checkDescriptionEqual(v1.Description, v2.Description); err != nil { + return fmt.Errorf(".Description%v", err) + } + if err := checkBigIntEqual(v1.CreationHeight, v2.CreationHeight); err != nil { + return fmt.Errorf(".CreationHeight %v", err) + } + return nil +} + +func checkDelegationsEqual(ds1, ds2 staking.Delegations) error { + if len(ds1) != len(ds2) { + return fmt.Errorf(".len not equal: %v / %v", len(ds1), len(ds2)) + } + for i := range ds1 { + if err := checkDelegationEqual(ds1[i], ds2[i]); err != nil { + return fmt.Errorf("[%v]%v", i, err) + } + } + return nil +} + +func checkDelegationEqual(d1, d2 staking.Delegation) error { + if d1.DelegatorAddress != d2.DelegatorAddress { + return fmt.Errorf(".DelegatorAddress not equal: %x / %x", + d1.DelegatorAddress, d2.DelegatorAddress) + } + if err := checkBigIntEqual(d1.Amount, d2.Amount); err != nil { + return fmt.Errorf(".Amount %v", err) + } + if err := checkBigIntEqual(d1.Reward, d2.Reward); err != nil { + return fmt.Errorf(".Reward %v", err) + } + if err := checkUndelegationsEqual(d1.Undelegations, d2.Undelegations); err != nil { + return fmt.Errorf(".Undelegations%v", err) + } + return nil +} + +func checkUndelegationsEqual(uds1, uds2 staking.Undelegations) error { + if len(uds1) != len(uds2) { + return fmt.Errorf(".len not equal: %v / %v", len(uds1), len(uds2)) + } + for i := range uds1 { + if err := checkUndelegationEqual(uds1[i], uds2[i]); err != nil { + return fmt.Errorf("[%v]%v", i, err) + } + } + return nil +} + +func checkUndelegationEqual(ud1, ud2 staking.Undelegation) error { + if err := checkBigIntEqual(ud1.Amount, ud2.Amount); err != nil { + return fmt.Errorf(".Amount %v", err) + } + if err := checkBigIntEqual(ud1.Epoch, ud2.Epoch); err != nil { + return fmt.Errorf(".Epoch %v", err) + } + return nil +} + +func checkPubKeysEqual(pubs1, pubs2 []shard.BLSPublicKey) error { + if len(pubs1) != len(pubs2) { + return fmt.Errorf(".len not equal: %v / %v", len(pubs1), len(pubs2)) + } + for i := range pubs1 { + if pubs1[i] != pubs2[i] { + return fmt.Errorf("[%v] not equal: %x / %x", i, pubs1[i], pubs2[i]) + } + } + return nil +} + +func checkDescriptionEqual(d1, d2 staking.Description) error { + if d1.Name != d2.Name { + return fmt.Errorf(".Name not equal: %v / %v", d1.Name, d2.Name) + } + if d1.Identity != d2.Identity { + return fmt.Errorf(".Identity not equal: %v / %v", d1.Identity, d2.Identity) + } + if d1.Website != d2.Website { + return fmt.Errorf(".Website not equal: %v / %v", d1.Website, d2.Website) + } + if d1.Details != d2.Details { + return fmt.Errorf(".Details not equal: %v / %v", d1.Details, d2.Details) + } + if d1.SecurityContact != d2.SecurityContact { + return fmt.Errorf(".SecurityContact not equal: %v / %v", d1.SecurityContact, d2.SecurityContact) + } + return nil +} + +func checkCommissionEqual(c1, c2 staking.Commission) error { + if err := checkCommissionRateEqual(c1.CommissionRates, c2.CommissionRates); err != nil { + return fmt.Errorf(".CommissionRate%v", err) + } + if err := checkBigIntEqual(c1.UpdateHeight, c2.UpdateHeight); err != nil { + return fmt.Errorf(".UpdateHeight %v", err) + } + return nil +} + +func checkCommissionRateEqual(cr1, cr2 staking.CommissionRates) error { + if err := checkDecEqual(cr1.Rate, cr2.Rate); err != nil { + return fmt.Errorf(".Rate %v", err) + } + if err := checkDecEqual(cr1.MaxChangeRate, cr2.MaxChangeRate); err != nil { + return fmt.Errorf(".MaxChangeRate %v", err) + } + if err := checkDecEqual(cr1.MaxRate, cr2.MaxRate); err != nil { + return fmt.Errorf(".MaxRate %v", err) + } + return nil +} + +func checkDecEqual(d1, d2 numeric.Dec) error { + if d1.IsNil() { + d1 = numeric.ZeroDec() + } + if d2.IsNil() { + d2 = numeric.ZeroDec() + } + if !d1.Equal(d2) { + return fmt.Errorf("not equal: %v / %v", d1, d2) + } + return nil +} + +func checkBigIntEqual(i1, i2 *big.Int) error { + if i1 == nil { + i1 = big.NewInt(0) + } + if i2 == nil { + i2 = big.NewInt(0) + } + if i1.Cmp(i2) != 0 { + return fmt.Errorf("not equal: %v / %v", i1, i2) + } + return nil +} diff --git a/staking/types/test/equal_test.go b/staking/types/test/equal_test.go new file mode 100644 index 000000000..ffca401ac --- /dev/null +++ b/staking/types/test/equal_test.go @@ -0,0 +1,37 @@ +package staketest + +import ( + "testing" + + staking "github.com/harmony-one/harmony/staking/types" +) + +func TestCheckValidatorWrapperEqual(t *testing.T) { + tests := []struct { + w1, w2 staking.ValidatorWrapper + }{ + {vWrapperPrototype, vWrapperPrototype}, + {makeZeroValidatorWrapper(), makeZeroValidatorWrapper()}, + {staking.ValidatorWrapper{}, staking.ValidatorWrapper{}}, + } + for i, test := range tests { + if err := CheckValidatorWrapperEqual(test.w1, test.w2); err != nil { + t.Errorf("Test %v: %v", i, err) + } + } +} + +func TestCheckValidatorEqual(t *testing.T) { + tests := []struct { + v1, v2 staking.Validator + }{ + {validatorPrototype, validatorPrototype}, + {makeZeroValidator(), makeZeroValidator()}, + {staking.Validator{}, staking.Validator{}}, + } + for i, test := range tests { + if err := CheckValidatorEqual(test.v1, test.v2); err != nil { + t.Errorf("Test %v: %v", i, err) + } + } +} diff --git a/staking/types/test/prototype.go b/staking/types/test/prototype.go new file mode 100644 index 000000000..3b0ab46c5 --- /dev/null +++ b/staking/types/test/prototype.go @@ -0,0 +1,118 @@ +package staketest + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/harmony-one/harmony/numeric" + "github.com/harmony-one/harmony/shard" + "github.com/harmony-one/harmony/staking/effective" + staking "github.com/harmony-one/harmony/staking/types" +) + +var ( + oneBig = big.NewInt(1e18) + tenKOnes = new(big.Int).Mul(big.NewInt(10000), oneBig) + twentyKOnes = new(big.Int).Mul(big.NewInt(20000), oneBig) + hundredKOnes = new(big.Int).Mul(big.NewInt(100000), oneBig) + + // DefaultDelAmount is the default delegation amount + DefaultDelAmount = new(big.Int).Set(twentyKOnes) + + // DefaultMinSelfDel is the default value of MinSelfDelegation + DefaultMinSelfDel = new(big.Int).Set(tenKOnes) + + // DefaultMaxTotalDel is the default value of MaxTotalDelegation + DefaultMaxTotalDel = new(big.Int).Set(hundredKOnes) +) + +var ( + vWrapperPrototype = func() staking.ValidatorWrapper { + w := staking.ValidatorWrapper{ + Validator: validatorPrototype, + Delegations: staking.Delegations{ + staking.Delegation{ + DelegatorAddress: validatorPrototype.Address, + Amount: DefaultDelAmount, + Reward: common.Big0, + Undelegations: staking.Undelegations{}, + }, + }, + BlockReward: common.Big0, + } + w.Counters.NumBlocksToSign = common.Big0 + w.Counters.NumBlocksSigned = common.Big0 + return w + }() + + validatorPrototype = staking.Validator{ + Address: common.Address{}, + SlotPubKeys: []shard.BLSPublicKey{shard.BLSPublicKey{}}, + LastEpochInCommittee: common.Big0, + MinSelfDelegation: DefaultMinSelfDel, + MaxTotalDelegation: DefaultMaxTotalDel, + Status: effective.Active, + Commission: commission, + Description: description, + CreationHeight: common.Big0, + } + + commissionRates = staking.CommissionRates{ + Rate: numeric.NewDecWithPrec(5, 1), + MaxRate: numeric.NewDecWithPrec(9, 1), + MaxChangeRate: numeric.NewDecWithPrec(3, 1), + } + + commission = staking.Commission{ + CommissionRates: commissionRates, + UpdateHeight: common.Big0, + } + + description = staking.Description{ + Name: "SuperHero", + Identity: "YouWouldNotKnow", + Website: "Secret Website", + SecurityContact: "LicenseToKill", + Details: "blah blah blah", + } +) + +// GetDefaultValidator return the default staking.Validator for testing +func GetDefaultValidator() staking.Validator { + return CopyValidator(validatorPrototype) +} + +// GetDefaultValidatorWithAddr return the default staking.Validator with the +// given validator address and bls keys +func GetDefaultValidatorWithAddr(addr common.Address, pubs []shard.BLSPublicKey) staking.Validator { + v := CopyValidator(validatorPrototype) + v.Address = addr + if pubs != nil { + v.SlotPubKeys = make([]shard.BLSPublicKey, len(pubs)) + copy(v.SlotPubKeys, pubs) + } else { + v.SlotPubKeys = nil + } + return v +} + +// GetDefaultValidatorWrapper return the default staking.ValidatorWrapper for testing +func GetDefaultValidatorWrapper() staking.ValidatorWrapper { + return CopyValidatorWrapper(vWrapperPrototype) +} + +// GetDefaultValidatorWrapperWithAddr return the default staking.ValidatorWrapper +// with the given validator address and bls keys. +func GetDefaultValidatorWrapperWithAddr(addr common.Address, pubs []shard.BLSPublicKey) staking.ValidatorWrapper { + w := CopyValidatorWrapper(vWrapperPrototype) + w.Address = addr + if pubs != nil { + w.SlotPubKeys = make([]shard.BLSPublicKey, len(pubs)) + copy(w.SlotPubKeys, pubs) + } else { + w.SlotPubKeys = nil + } + w.Delegations[0].DelegatorAddress = addr + + return w +} diff --git a/staking/types/test/prototype_test.go b/staking/types/test/prototype_test.go new file mode 100644 index 000000000..3cc4d7661 --- /dev/null +++ b/staking/types/test/prototype_test.go @@ -0,0 +1,79 @@ +package staketest + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/harmony-one/harmony/shard" +) + +func TestGetDefaultValidator(t *testing.T) { + v := GetDefaultValidator() + if err := assertValidatorDeepCopy(v, validatorPrototype); err != nil { + t.Error(err) + } +} + +func TestGetDefaultValidatorWrapper(t *testing.T) { + w := GetDefaultValidatorWrapper() + if err := assertValidatorWrapperDeepCopy(w, vWrapperPrototype); err != nil { + t.Error(err) + } +} + +func TestGetDefaultValidatorWithAddr(t *testing.T) { + tests := []struct { + addr common.Address + keys []shard.BLSPublicKey + }{ + { + addr: common.BigToAddress(common.Big1), + keys: []shard.BLSPublicKey{{1}, {}}, + }, + { + addr: common.Address{}, + keys: make([]shard.BLSPublicKey, 0), + }, + {}, + } + for i, test := range tests { + v := GetDefaultValidatorWithAddr(test.addr, test.keys) + + exp := CopyValidator(validatorPrototype) + exp.Address = test.addr + exp.SlotPubKeys = test.keys + + if err := assertValidatorDeepCopy(v, exp); err != nil { + t.Errorf("Test %v: %v", i, err) + } + } +} + +func TestGetDefaultValidatorWrapperWithAddr(t *testing.T) { + tests := []struct { + addr common.Address + keys []shard.BLSPublicKey + }{ + { + addr: common.BigToAddress(common.Big1), + keys: []shard.BLSPublicKey{{1}, {}}, + }, + { + addr: common.Address{}, + keys: make([]shard.BLSPublicKey, 0), + }, + {}, + } + for i, test := range tests { + v := GetDefaultValidatorWrapperWithAddr(test.addr, test.keys) + + exp := CopyValidatorWrapper(vWrapperPrototype) + exp.Address = test.addr + exp.SlotPubKeys = test.keys + exp.Delegations[0].DelegatorAddress = test.addr + + if err := assertValidatorWrapperDeepCopy(v, exp); err != nil { + t.Errorf("Test %v: %v", i, err) + } + } +}