diff --git a/Dockerfile b/Dockerfile index c6abaf2d5..f84d401d9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,6 +16,7 @@ ENV LD_LIBRARY_PATH=${BLS_DIR}/lib:${MCL_DIR}/lib ENV GIMME_GO_VERSION="1.14.1" ENV PATH="/root/bin:${PATH}" +RUN apt-get update -y RUN apt install libgmp-dev libssl-dev curl git \ psmisc dnsutils jq make gcc g++ bash tig tree sudo vim \ silversearcher-ag unzip emacs-nox nano bash-completion -y diff --git a/Makefile b/Makefile index 0bac63457..450911a03 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ libs: make -C $(TOP)/bls BLS_SWAP_G=1 -j8 exe: - ./scripts/go_executable_build.sh + ./scripts/go_executable_build.sh -S race: ./scripts/go_executable_build.sh -r diff --git a/api/service/explorer/service.go b/api/service/explorer/service.go index 342240474..ed896de66 100644 --- a/api/service/explorer/service.go +++ b/api/service/explorer/service.go @@ -102,7 +102,13 @@ func (s *Service) Run() *http.Server { // Do serving now. utils.Logger().Info().Str("port", GetExplorerPort(s.Port)).Msg("Listening") - server := &http.Server{Addr: addr, Handler: s.router} + server := &http.Server{ + Addr: addr, + Handler: s.router, + ReadTimeout: 5 * time.Second, + WriteTimeout: 10 * time.Second, + IdleTimeout: 120 * time.Second, + } go func() { if err := server.ListenAndServe(); err != nil { utils.Logger().Warn().Err(err).Msg("server.ListenAndServe()") diff --git a/api/service/syncing/syncing.go b/api/service/syncing/syncing.go index 588b6bb54..4f127c35e 100644 --- a/api/service/syncing/syncing.go +++ b/api/service/syncing/syncing.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/hex" "fmt" + "math/rand" "reflect" "sort" "strconv" @@ -36,6 +37,11 @@ const ( verifyHeaderBatchSize uint64 = 100 // block chain header verification batch size SyncLoopFrequency = 1 // unit in second LastMileBlocksSize = 50 + + // after cutting off a number of connected peers, the result number of peers + // shall be between numPeersLowBound and numPeersHighBound + numPeersLowBound = 3 + numPeersHighBound = 5 ) // SyncPeerConfig is peer config to sync. @@ -225,6 +231,10 @@ func (peerConfig *SyncPeerConfig) GetBlocks(hashes [][]byte) ([][]byte, error) { // CreateSyncConfig creates SyncConfig for StateSync object. func (ss *StateSync) CreateSyncConfig(peers []p2p.Peer, isBeacon bool) error { + // limit the number of dns peers to connect + randSeed := time.Now().UnixNano() + peers = limitNumPeers(peers, randSeed) + utils.Logger().Debug(). Int("len", len(peers)). Bool("isBeacon", isBeacon). @@ -237,6 +247,7 @@ func (ss *StateSync) CreateSyncConfig(peers []p2p.Peer, isBeacon bool) error { ss.syncConfig.CloseConnections() } ss.syncConfig = &SyncConfig{} + var wg sync.WaitGroup for _, peer := range peers { wg.Add(1) @@ -263,6 +274,34 @@ func (ss *StateSync) CreateSyncConfig(peers []p2p.Peer, isBeacon bool) error { return nil } +// limitNumPeers limits number of peers to release some server end sources. +func limitNumPeers(ps []p2p.Peer, randSeed int64) []p2p.Peer { + targetSize := calcNumPeersWithBound(len(ps), numPeersLowBound, numPeersHighBound) + if len(ps) <= targetSize { + return ps + } + + r := rand.New(rand.NewSource(randSeed)) + r.Shuffle(len(ps), func(i, j int) { ps[i], ps[j] = ps[j], ps[i] }) + + return ps[:targetSize] +} + +// Peers are expected to limited at half of the size, capped between lowBound and highBound. +func calcNumPeersWithBound(size int, lowBound, highBound int) int { + if size < lowBound { + return size + } + expLen := size / 2 + if expLen < lowBound { + expLen = lowBound + } + if expLen > highBound { + expLen = highBound + } + return expLen +} + // GetActivePeerNumber returns the number of active peers func (ss *StateSync) GetActivePeerNumber() int { if ss.syncConfig == nil { diff --git a/api/service/syncing/syncing_test.go b/api/service/syncing/syncing_test.go index 902c90336..bd98ec238 100644 --- a/api/service/syncing/syncing_test.go +++ b/api/service/syncing/syncing_test.go @@ -1,9 +1,12 @@ package syncing import ( + "fmt" + "reflect" "testing" "github.com/harmony-one/harmony/api/service/syncing/downloader" + "github.com/harmony-one/harmony/p2p" "github.com/stretchr/testify/assert" ) @@ -49,3 +52,69 @@ func TestCreateStateSync(t *testing.T) { t.Error("Unable to create stateSync") } } + +func TestLimitPeersWithBound(t *testing.T) { + tests := []struct { + size int + expSize int + }{ + {0, 0}, + {1, 1}, + {3, 3}, + {4, 3}, + {7, 3}, + {8, 4}, + {10, 5}, + {11, 5}, + {100, 5}, + } + for _, test := range tests { + ps := makePeersForTest(test.size) + + res := limitNumPeers(ps, 1) + + if len(res) != test.expSize { + t.Errorf("result size unexpected: %v / %v", len(res), test.expSize) + } + if err := checkTestPeerDuplicity(res); err != nil { + t.Error(err) + } + } +} + +func TestLimitPeersWithBound_random(t *testing.T) { + ps1 := makePeersForTest(100) + ps2 := makePeersForTest(100) + s1, s2 := int64(1), int64(2) + + res1 := limitNumPeers(ps1, s1) + res2 := limitNumPeers(ps2, s2) + if reflect.DeepEqual(res1, res2) { + t.Fatal("not randomized limit peer") + } +} + +func makePeersForTest(size int) []p2p.Peer { + ps := make([]p2p.Peer, 0, size) + for i := 0; i != size; i++ { + ps = append(ps, p2p.Peer{ + IP: makeTestPeerIP(i), + }) + } + return ps +} + +func checkTestPeerDuplicity(ps []p2p.Peer) error { + m := make(map[string]struct{}) + for _, p := range ps { + if _, ok := m[p.IP]; ok { + return fmt.Errorf("duplicate ip") + } + m[p.IP] = struct{}{} + } + return nil +} + +func makeTestPeerIP(i interface{}) string { + return fmt.Sprintf("%v", i) +} diff --git a/cmd/harmony/main.go b/cmd/harmony/main.go index 2889e42c2..0f502de5d 100644 --- a/cmd/harmony/main.go +++ b/cmd/harmony/main.go @@ -72,6 +72,7 @@ var ( onlyLogTps = flag.Bool("only_log_tps", false, "Only log TPS if true") dnsZone = flag.String("dns_zone", "", "if given and not empty, use peers from the zone (default: use libp2p peer discovery instead)") dnsFlag = flag.Bool("dns", true, "[deprecated] equivalent to -dns_zone t.hmny.io") + dnsPort = flag.String("dns_port", "9000", "port of dns node") //Leader needs to have a minimal number of peers to start consensus minPeers = flag.Int("min_peers", 32, "Minimal number of Peers in shard") // Key file to store the private key @@ -110,8 +111,9 @@ var ( revertTo = flag.Int("revert_to", 0, "The revert will rollback all blocks until and including block number revert_to") revertBeacon = flag.Bool("revert_beacon", false, "Whether to revert beacon chain or the chain this node is assigned to") // Blacklist of addresses - blacklistPath = flag.String("blacklist", "./.hmy/blacklist.txt", "Path to newline delimited file of blacklisted wallet addresses") - webHookYamlPath = flag.String( + blacklistPath = flag.String("blacklist", "./.hmy/blacklist.txt", "Path to newline delimited file of blacklisted wallet addresses") + broadcastInvalidTx = flag.Bool("broadcast_invalid_tx", false, "Broadcast invalid transactions to sync pool state (default: false)") + webHookYamlPath = flag.String( "webhook_yaml", "", "path for yaml config reporting double signing", ) // aws credentials @@ -290,7 +292,7 @@ func readMultiBLSKeys(consensusMultiBLSPriKey *multibls.PrivateKey, consensusMul os.Exit(100) } - keyFiles := []os.FileInfo{} + var keyFiles []os.FileInfo legacyBLSFile := true if len(awsEncryptedBLSKeyFiles) > 0 { @@ -460,6 +462,7 @@ func setupConsensusAndNode(nodeConfig *nodeconfig.ConfigType) *node.Node { chainDBFactory := &shardchain.LDBFactory{RootDir: nodeConfig.DBDir} currentNode := node.New(myHost, currentConsensus, chainDBFactory, blacklist, *isArchival) + currentNode.BroadcastInvalidTx = *broadcastInvalidTx switch { case *networkType == nodeconfig.Localnet: @@ -474,9 +477,9 @@ func setupConsensusAndNode(nodeConfig *nodeconfig.ConfigType) *node.Node { currentNode.SyncingPeerProvider = node.NewLocalSyncingPeerProvider( 6000, uint16(selfPort), epochConfig.NumShards(), uint32(epochConfig.NumNodesPerShard())) case *dnsZone != "": - currentNode.SyncingPeerProvider = node.NewDNSSyncingPeerProvider(*dnsZone, syncing.GetSyncingPort(*port)) + currentNode.SyncingPeerProvider = node.NewDNSSyncingPeerProvider(*dnsZone, syncing.GetSyncingPort(*dnsPort)) case *dnsFlag: - currentNode.SyncingPeerProvider = node.NewDNSSyncingPeerProvider("t.hmny.io", syncing.GetSyncingPort(*port)) + currentNode.SyncingPeerProvider = node.NewDNSSyncingPeerProvider("t.hmny.io", syncing.GetSyncingPort(*dnsPort)) default: currentNode.SyncingPeerProvider = node.NewLegacySyncingPeerProvider(currentNode) diff --git a/consensus/consensus_service.go b/consensus/consensus_service.go index b90851933..348619ecd 100644 --- a/consensus/consensus_service.go +++ b/consensus/consensus_service.go @@ -418,7 +418,7 @@ func (consensus *Consensus) UpdateConsensusInformation() Mode { consensus.Decider = decider } - committeeToSet := &shard.Committee{} + var committeeToSet *shard.Committee epochToSet := curEpoch hasError := false curShardState, err := committee.WithStakingEnabled.ReadFromDB( diff --git a/consensus/leader.go b/consensus/leader.go index cba41c2ca..77024c4e0 100644 --- a/consensus/leader.go +++ b/consensus/leader.go @@ -262,20 +262,19 @@ func (consensus *Consensus) onCommit(msg *msg_pb.Message) { return } + viewID := consensus.viewID + quorumIsMet := consensus.Decider.IsQuorumAchieved(quorum.Commit) if !quorumWasMet && quorumIsMet { logger.Info().Msg("[OnCommit] 2/3 Enough commits received") - go func(viewID uint64) { - consensus.getLogger().Info().Msg("[OnCommit] Starting Grace Period") - // Always wait for 2 seconds as minimum grace period - time.Sleep(2 * time.Second) - if n := time.Now(); n.Before(consensus.NextBlockDue) { - // Sleep to wait for the full block time - time.Sleep(consensus.NextBlockDue.Sub(n)) - } + + next := consensus.NextBlockDue + consensus.getLogger().Info().Msg("[OnCommit] Starting Grace Period") + time.AfterFunc(2*time.Second, func() { + <-time.After(time.Until(next)) logger.Info().Msg("[OnCommit] Commit Grace Period Ended") consensus.commitFinishChan <- viewID - }(consensus.viewID) + }) consensus.msgSender.StopRetry(msg_pb.MessageType_PREPARED) } diff --git a/consensus/votepower/roster.go b/consensus/votepower/roster.go index 86d3b5539..897542818 100644 --- a/consensus/votepower/roster.go +++ b/consensus/votepower/roster.go @@ -143,13 +143,9 @@ func AggregateRosters( AccommodateHarmonyVote: *voteCard, ShardID: roster.ShardID, } - if _, ok := result[voteCard.EarningAccount]; ok { - result[voteCard.EarningAccount] = append( - result[voteCard.EarningAccount], voterID, - ) - } else { - result[voteCard.EarningAccount] = []VoteOnSubcomittee{voterID} - } + result[voteCard.EarningAccount] = append( + result[voteCard.EarningAccount], voterID, + ) } } } diff --git a/core/block_validator.go b/core/block_validator.go index 6d3fdd3b9..da5114d7b 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/hmy/api_backend.go b/hmy/api_backend.go index 0d53debc6..363c4d310 100644 --- a/hmy/api_backend.go +++ b/hmy/api_backend.go @@ -16,6 +16,7 @@ import ( "github.com/harmony-one/harmony/block" "github.com/harmony-one/harmony/consensus/quorum" "github.com/harmony-one/harmony/core" + "github.com/harmony-one/harmony/core/rawdb" "github.com/harmony-one/harmony/core/state" "github.com/harmony-one/harmony/core/types" "github.com/harmony-one/harmony/core/vm" @@ -34,6 +35,11 @@ import ( "golang.org/x/sync/singleflight" ) +var ( + // ErrFinalizedTransaction is returned if the transaction to be submitted is already on-chain + ErrFinalizedTransaction = errors.New("transaction already finalized") +) + // APIBackend An implementation of internal/hmyapi/Backend. Full client. type APIBackend struct { hmy *Harmony @@ -122,8 +128,11 @@ func (b *APIBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uin // SendTx ... func (b *APIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { - b.hmy.nodeAPI.AddPendingTransaction(signedTx) - return nil + tx, _, _, _ := rawdb.ReadTransaction(b.ChainDb(), signedTx.Hash()) + if tx == nil { + return b.hmy.nodeAPI.AddPendingTransaction(signedTx) + } + return ErrFinalizedTransaction } // ChainConfig ... @@ -362,11 +371,12 @@ func (b *APIBackend) IsLeader() bool { } // SendStakingTx adds a staking transaction -func (b *APIBackend) SendStakingTx( - ctx context.Context, - newStakingTx *staking.StakingTransaction) error { - b.hmy.nodeAPI.AddPendingStakingTransaction(newStakingTx) - return nil +func (b *APIBackend) SendStakingTx(ctx context.Context, signedStakingTx *staking.StakingTransaction) error { + stx, _, _, _ := rawdb.ReadStakingTransaction(b.ChainDb(), signedStakingTx.Hash()) + if stx == nil { + return b.hmy.nodeAPI.AddPendingStakingTransaction(signedStakingTx) + } + return ErrFinalizedTransaction } // GetElectedValidatorAddresses returns the address of elected validators for current epoch @@ -387,7 +397,7 @@ var ( // GetValidatorInformation returns the information of validator func (b *APIBackend) GetValidatorInformation( addr common.Address, block *types.Block, -) (*staking.ValidatorRPCEnchanced, error) { +) (*staking.ValidatorRPCEnhanced, error) { bc := b.hmy.BlockChain() wrapper, err := bc.ReadValidatorInformationAt(addr, block.Root()) if err != nil { @@ -399,7 +409,7 @@ func (b *APIBackend) GetValidatorInformation( // At the last block of epoch, block epoch is e while val.LastEpochInCommittee // is already updated to e+1. So need the >= check rather than == inCommittee := wrapper.LastEpochInCommittee.Cmp(now) >= 0 - defaultReply := &staking.ValidatorRPCEnchanced{ + defaultReply := &staking.ValidatorRPCEnhanced{ CurrentlyInCommittee: inCommittee, Wrapper: *wrapper, Performance: nil, @@ -410,6 +420,7 @@ func (b *APIBackend) GetValidatorInformation( ).String(), EPoSWinningStake: nil, BootedStatus: nil, + ActiveStatus: wrapper.Validator.Status.String(), Lifetime: &staking.AccumulatedOverLifetime{ wrapper.BlockReward, wrapper.Counters, diff --git a/internal/chain/engine.go b/internal/chain/engine.go index 3c44d17e6..69283e12a 100644 --- a/internal/chain/engine.go +++ b/internal/chain/engine.go @@ -399,12 +399,7 @@ func applySlashes( shardID: doubleSigners[i].Evidence.Moment.ShardID, epoch: doubleSigners[i].Evidence.Moment.Epoch.Uint64(), } - - if _, ok := groupedRecords[thisKey]; ok { - groupedRecords[thisKey] = append(groupedRecords[thisKey], doubleSigners[i]) - } else { - groupedRecords[thisKey] = slash.Records{doubleSigners[i]} - } + groupedRecords[thisKey] = append(groupedRecords[thisKey], doubleSigners[i]) } sortedKeys := []keyStruct{} diff --git a/internal/configs/sharding/testnet.go b/internal/configs/sharding/testnet.go index 79d329baf..2fd7a4368 100644 --- a/internal/configs/sharding/testnet.go +++ b/internal/configs/sharding/testnet.go @@ -21,9 +21,9 @@ const ( testnetVdfDifficulty = 10000 // This takes about 20s to finish the vdf // TestNetHTTPPattern is the http pattern for testnet. - TestNetHTTPPattern = "https://api.s%d.tn.hmny.io" + TestNetHTTPPattern = "https://api.s%d.b.hmny.io" // TestNetWSPattern is the websocket pattern for testnet. - TestNetWSPattern = "wss://ws.s%d.tn.hmny.io" + TestNetWSPattern = "wss://ws.s%d.b.hmny.io" ) func (testnetSchedule) InstanceForEpoch(epoch *big.Int) Instance { @@ -81,5 +81,5 @@ var testnetReshardingEpoch = []*big.Int{ params.TestnetChainConfig.StakingEpoch, } -var testnetV0 = MustNewInstance(4, 30, 25, numeric.OneDec(), genesis.TNHarmonyAccounts, genesis.TNFoundationalAccounts, testnetReshardingEpoch, TestnetSchedule.BlocksPerEpoch()) -var testnetV1 = MustNewInstance(4, 50, 25, numeric.MustNewDecFromStr("0.68"), genesis.TNHarmonyAccounts, genesis.TNFoundationalAccounts, testnetReshardingEpoch, TestnetSchedule.BlocksPerEpoch()) +var testnetV0 = MustNewInstance(4, 16, 15, numeric.OneDec(), genesis.TNHarmonyAccounts, genesis.TNFoundationalAccounts, testnetReshardingEpoch, TestnetSchedule.BlocksPerEpoch()) +var testnetV1 = MustNewInstance(4, 20, 15, numeric.MustNewDecFromStr("0.90"), genesis.TNHarmonyAccounts, genesis.TNFoundationalAccounts, testnetReshardingEpoch, TestnetSchedule.BlocksPerEpoch()) diff --git a/internal/configs/viper/viper_config.go b/internal/configs/viper/viper_config.go index 40ab5991a..f6f3ff7a2 100644 --- a/internal/configs/viper/viper_config.go +++ b/internal/configs/viper/viper_config.go @@ -87,13 +87,13 @@ func ResetConfInt(value *int, envViper *viper.Viper, configFileViper *viper.Vipe // ResetConfBool resets Bool value to value from config files and system environment variable func ResetConfBool(value *bool, envViper *viper.Viper, configFileViper *viper.Viper, sectionName string, flagName string) { var confRet = configFileViper.GetBool(getConfName(sectionName, flagName)) - if confRet != false { + if confRet { *value = confRet return } var envRet = envViper.GetBool(getEnvName(sectionName, flagName)) - if envRet != false { + if envRet { *value = envRet return } diff --git a/internal/hmyapi/apiv1/backend.go b/internal/hmyapi/apiv1/backend.go index 8d87ceb28..cc013a1c3 100644 --- a/internal/hmyapi/apiv1/backend.go +++ b/internal/hmyapi/apiv1/backend.go @@ -74,7 +74,7 @@ type Backend interface { SendStakingTx(ctx context.Context, newStakingTx *staking.StakingTransaction) error GetElectedValidatorAddresses() []common.Address GetAllValidatorAddresses() []common.Address - GetValidatorInformation(addr common.Address, block *types.Block) (*staking.ValidatorRPCEnchanced, error) + GetValidatorInformation(addr common.Address, block *types.Block) (*staking.ValidatorRPCEnhanced, error) GetDelegationsByValidator(validator common.Address) []*staking.Delegation GetDelegationsByDelegator(delegator common.Address) ([]common.Address, []*staking.Delegation) GetDelegationsByDelegatorByBlock(delegator common.Address, block *types.Block) ([]common.Address, []*staking.Delegation) diff --git a/internal/hmyapi/apiv1/blockchain.go b/internal/hmyapi/apiv1/blockchain.go index 56c027b99..15672d536 100644 --- a/internal/hmyapi/apiv1/blockchain.go +++ b/internal/hmyapi/apiv1/blockchain.go @@ -442,7 +442,7 @@ func (s *PublicBlockChainAPI) GetAccountNonce(ctx context.Context, address strin return s.b.GetAccountNonce(ctx, addr, rpc.BlockNumber(blockNr)) } -// GetBalance returns the amount of Nano for the given address in the state of the +// GetBalance returns the amount of Atto for the given address in the state of the // given block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta // block numbers are also allowed. func (s *PublicBlockChainAPI) GetBalance(ctx context.Context, address string, blockNr rpc.BlockNumber) (*hexutil.Big, error) { @@ -632,7 +632,7 @@ func (s *PublicBlockChainAPI) GetElectedValidatorAddresses() ([]string, error) { // GetValidatorInformation returns information about a validator. func (s *PublicBlockChainAPI) GetValidatorInformation( ctx context.Context, address string, -) (*staking.ValidatorRPCEnchanced, error) { +) (*staking.ValidatorRPCEnhanced, error) { if err := s.isBeaconShard(); err != nil { return nil, err } @@ -648,7 +648,7 @@ func (s *PublicBlockChainAPI) GetValidatorInformation( // GetValidatorInformationByBlockNumber returns information about a validator. func (s *PublicBlockChainAPI) GetValidatorInformationByBlockNumber( ctx context.Context, address string, blockNr rpc.BlockNumber, -) (*staking.ValidatorRPCEnchanced, error) { +) (*staking.ValidatorRPCEnhanced, error) { if err := s.isBeaconShard(); err != nil { return nil, err } @@ -666,13 +666,13 @@ func (s *PublicBlockChainAPI) GetValidatorInformationByBlockNumber( func (s *PublicBlockChainAPI) getAllValidatorInformation( ctx context.Context, page int, blockNr rpc.BlockNumber, -) ([]*staking.ValidatorRPCEnchanced, error) { +) ([]*staking.ValidatorRPCEnhanced, error) { if page < -1 { return nil, errors.Errorf("page given %d cannot be less than -1", page) } addresses := s.b.GetAllValidatorAddresses() if page != -1 && len(addresses) <= page*validatorsPageSize { - return make([]*staking.ValidatorRPCEnchanced, 0), nil + return make([]*staking.ValidatorRPCEnhanced, 0), nil } validatorsNum := len(addresses) start := 0 @@ -683,7 +683,7 @@ func (s *PublicBlockChainAPI) getAllValidatorInformation( validatorsNum = len(addresses) - start } } - validators := make([]*staking.ValidatorRPCEnchanced, validatorsNum) + validators := make([]*staking.ValidatorRPCEnhanced, validatorsNum) block, err := s.b.BlockByNumber(ctx, rpc.BlockNumber(blockNr)) if err != nil { return nil, errors.Wrapf(err, "could not retrieve the block information for block number: %d", blockNr) @@ -702,7 +702,7 @@ func (s *PublicBlockChainAPI) getAllValidatorInformation( // If page is -1, return all instead of `validatorsPageSize` elements. func (s *PublicBlockChainAPI) GetAllValidatorInformation( ctx context.Context, page int, -) ([]*staking.ValidatorRPCEnchanced, error) { +) ([]*staking.ValidatorRPCEnhanced, error) { if err := s.isBeaconShard(); err != nil { return nil, err } @@ -722,14 +722,14 @@ func (s *PublicBlockChainAPI) GetAllValidatorInformation( if err != nil { return nil, err } - return res.([]*staking.ValidatorRPCEnchanced), nil + return res.([]*staking.ValidatorRPCEnhanced), nil } // GetAllValidatorInformationByBlockNumber returns information about all validators. // If page is -1, return all instead of `validatorsPageSize` elements. func (s *PublicBlockChainAPI) GetAllValidatorInformationByBlockNumber( ctx context.Context, page int, blockNr rpc.BlockNumber, -) ([]*staking.ValidatorRPCEnchanced, error) { +) ([]*staking.ValidatorRPCEnhanced, error) { if err := s.isBeaconShard(); err != nil { return nil, err } diff --git a/internal/hmyapi/apiv1/transactionpool.go b/internal/hmyapi/apiv1/transactionpool.go index 8ef0a9baa..0f70cf427 100644 --- a/internal/hmyapi/apiv1/transactionpool.go +++ b/internal/hmyapi/apiv1/transactionpool.go @@ -39,8 +39,8 @@ func NewPublicTransactionPoolAPI(b Backend, nonceLock *AddrLocker) *PublicTransa // GetTransactionsHistory returns the list of transactions hashes that involve a particular address. func (s *PublicTransactionPoolAPI) GetTransactionsHistory(ctx context.Context, args TxHistoryArgs) (map[string]interface{}, error) { - address := args.Address - result := []common.Hash{} + var address string + var result []common.Hash var err error if strings.HasPrefix(args.Address, "one1") { address = args.Address diff --git a/internal/hmyapi/apiv1/types.go b/internal/hmyapi/apiv1/types.go index 4de83ccfe..190643ed4 100644 --- a/internal/hmyapi/apiv1/types.go +++ b/internal/hmyapi/apiv1/types.go @@ -353,6 +353,8 @@ func newRPCStakingTransaction(tx *types2.StakingTransaction, blockHash common.Ha // RPCBlock represents a block that will serialize to the RPC representation of a block type RPCBlock struct { Number *hexutil.Big `json:"number"` + ViewID *hexutil.Big `json:"viewID"` + Epoch *hexutil.Big `json:"epoch"` Hash common.Hash `json:"hash"` ParentHash common.Hash `json:"parentHash"` Nonce types.BlockNonce `json:"nonce"` @@ -382,6 +384,8 @@ func RPCMarshalBlock(b *types.Block, blockArgs BlockArgs) (map[string]interface{ head := b.Header() // copies the header once fields := map[string]interface{}{ "number": (*hexutil.Big)(head.Number()), + "viewID": (*hexutil.Big)(head.ViewID()), + "epoch": (*hexutil.Big)(head.Epoch()), "hash": b.Hash(), "parentHash": head.ParentHash(), "nonce": 0, // Remove this because we don't have it in our header diff --git a/internal/hmyapi/apiv1/util.go b/internal/hmyapi/apiv1/util.go index 45f23dc80..d021ea01f 100644 --- a/internal/hmyapi/apiv1/util.go +++ b/internal/hmyapi/apiv1/util.go @@ -36,7 +36,9 @@ func SubmitTransaction( ctx context.Context, b Backend, tx *types.Transaction, ) (common.Hash, error) { if err := b.SendTx(ctx, tx); err != nil { - return common.Hash{}, err + // legacy behavior is to never return error and always return tx hash + utils.Logger().Warn().Err(err).Msg("Could not submit transaction") + return tx.Hash(), nil } if tx.To() == nil { signer := types.MakeSigner(b.ChainConfig(), b.CurrentBlock().Epoch()) @@ -63,7 +65,9 @@ func SubmitStakingTransaction( ctx context.Context, b Backend, tx *staking.StakingTransaction, ) (common.Hash, error) { if err := b.SendStakingTx(ctx, tx); err != nil { - return common.Hash{}, err + // legacy behavior is to never return error and always return tx hash + utils.Logger().Warn().Err(err).Msg("Could not submit staking transaction") + return tx.Hash(), nil } utils.Logger().Info().Str("fullhash", tx.Hash().Hex()).Msg("Submitted Staking transaction") return tx.Hash(), nil diff --git a/internal/hmyapi/apiv2/backend.go b/internal/hmyapi/apiv2/backend.go index 704bb3710..55a53b379 100644 --- a/internal/hmyapi/apiv2/backend.go +++ b/internal/hmyapi/apiv2/backend.go @@ -70,7 +70,7 @@ type Backend interface { SendStakingTx(ctx context.Context, newStakingTx *staking.StakingTransaction) error GetElectedValidatorAddresses() []common.Address GetAllValidatorAddresses() []common.Address - GetValidatorInformation(addr common.Address, block *types.Block) (*staking.ValidatorRPCEnchanced, error) + GetValidatorInformation(addr common.Address, block *types.Block) (*staking.ValidatorRPCEnhanced, error) GetDelegationsByValidator(validator common.Address) []*staking.Delegation GetDelegationsByDelegator(delegator common.Address) ([]common.Address, []*staking.Delegation) GetDelegationsByDelegatorByBlock(delegator common.Address, block *types.Block) ([]common.Address, []*staking.Delegation) diff --git a/internal/hmyapi/apiv2/blockchain.go b/internal/hmyapi/apiv2/blockchain.go index 0b019a058..a3805ddcf 100644 --- a/internal/hmyapi/apiv2/blockchain.go +++ b/internal/hmyapi/apiv2/blockchain.go @@ -395,12 +395,12 @@ func (s *PublicBlockChainAPI) GetAccountNonce(ctx context.Context, address strin return s.b.GetAccountNonce(ctx, addr, rpc.BlockNumber(blockNr)) } -// GetBalance returns the amount of Nano for the given address in the state of the +// GetBalance returns the amount of Atto for the given address in the state of the // given block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta // block numbers are also allowed. -func (s *PublicBlockChainAPI) GetBalance(ctx context.Context, address string, blockNr rpc.BlockNumber) (*big.Int, error) { +func (s *PublicBlockChainAPI) GetBalance(ctx context.Context, address string) (*big.Int, error) { addr := internal_common.ParseAddr(address) - return s.b.GetBalance(ctx, addr, rpc.BlockNumber(blockNr)) + return s.b.GetBalance(ctx, addr, rpc.BlockNumber(-1)) } // BlockNumber returns the block number of the chain head. @@ -578,7 +578,7 @@ func (s *PublicBlockChainAPI) GetElectedValidatorAddresses() ([]string, error) { // GetValidatorInformation .. func (s *PublicBlockChainAPI) GetValidatorInformation( ctx context.Context, address string, -) (*staking.ValidatorRPCEnchanced, error) { +) (*staking.ValidatorRPCEnhanced, error) { if err := s.isBeaconShard(); err != nil { return nil, err } @@ -594,7 +594,7 @@ func (s *PublicBlockChainAPI) GetValidatorInformation( // GetValidatorInformationByBlockNumber .. func (s *PublicBlockChainAPI) GetValidatorInformationByBlockNumber( ctx context.Context, address string, blockNr uint64, -) (*staking.ValidatorRPCEnchanced, error) { +) (*staking.ValidatorRPCEnhanced, error) { if err := s.isBeaconShard(); err != nil { return nil, err } @@ -612,13 +612,13 @@ func (s *PublicBlockChainAPI) GetValidatorInformationByBlockNumber( func (s *PublicBlockChainAPI) getAllValidatorInformation( ctx context.Context, page int, blockNr rpc.BlockNumber, -) ([]*staking.ValidatorRPCEnchanced, error) { +) ([]*staking.ValidatorRPCEnhanced, error) { if page < -1 { return nil, errors.Errorf("page given %d cannot be less than -1", page) } addresses := s.b.GetAllValidatorAddresses() if page != -1 && len(addresses) <= page*validatorsPageSize { - return make([]*staking.ValidatorRPCEnchanced, 0), nil + return make([]*staking.ValidatorRPCEnhanced, 0), nil } validatorsNum := len(addresses) start := 0 @@ -629,7 +629,7 @@ func (s *PublicBlockChainAPI) getAllValidatorInformation( validatorsNum = len(addresses) - start } } - validators := make([]*staking.ValidatorRPCEnchanced, validatorsNum) + validators := make([]*staking.ValidatorRPCEnhanced, validatorsNum) block, err := s.b.BlockByNumber(ctx, rpc.BlockNumber(blockNr)) if err != nil { return nil, errors.Wrapf(err, "could not retrieve the block information for block number: %d", blockNr) @@ -648,7 +648,7 @@ func (s *PublicBlockChainAPI) getAllValidatorInformation( // If page is -1, return all else return the pagination. func (s *PublicBlockChainAPI) GetAllValidatorInformation( ctx context.Context, page int, -) ([]*staking.ValidatorRPCEnchanced, error) { +) ([]*staking.ValidatorRPCEnhanced, error) { if err := s.isBeaconShard(); err != nil { return nil, err } @@ -669,7 +669,7 @@ func (s *PublicBlockChainAPI) GetAllValidatorInformation( if err != nil { return nil, err } - return res.([]*staking.ValidatorRPCEnchanced), nil + return res.([]*staking.ValidatorRPCEnhanced), nil } @@ -677,7 +677,7 @@ func (s *PublicBlockChainAPI) GetAllValidatorInformation( // If page is -1, return all else return the pagination. func (s *PublicBlockChainAPI) GetAllValidatorInformationByBlockNumber( ctx context.Context, page int, blockNr uint64, -) ([]*staking.ValidatorRPCEnchanced, error) { +) ([]*staking.ValidatorRPCEnhanced, error) { if err := s.isBeaconShard(); err != nil { return nil, err } diff --git a/internal/hmyapi/apiv2/sendtxargs.go b/internal/hmyapi/apiv2/sendtxargs.go index 1edb1a07b..d2cd57641 100644 --- a/internal/hmyapi/apiv2/sendtxargs.go +++ b/internal/hmyapi/apiv2/sendtxargs.go @@ -33,14 +33,14 @@ func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error { *(*uint64)(args.Gas) = 90000 } // TODO(ricl): add check for shardID - if args.GasPrice == nil { - // TODO(ricl): port - // price, err := b.SuggestPrice(ctx) - // if err != nil { - // return err - // } - // args.GasPrice = (*hexutil.Big)(price) - } + // if args.GasPrice == nil { + // TODO(ricl): port + // price, err := b.SuggestPrice(ctx) + // if err != nil { + // return err + // } + // args.GasPrice = (*hexutil.Big)(price) + // } if args.Value == nil { args.Value = new(hexutil.Big) } diff --git a/internal/hmyapi/apiv2/transactionpool.go b/internal/hmyapi/apiv2/transactionpool.go index afcbb4148..41584ed39 100644 --- a/internal/hmyapi/apiv2/transactionpool.go +++ b/internal/hmyapi/apiv2/transactionpool.go @@ -39,8 +39,8 @@ func NewPublicTransactionPoolAPI(b Backend, nonceLock *AddrLocker) *PublicTransa // GetTransactionsHistory returns the list of transactions hashes that involve a particular address. func (s *PublicTransactionPoolAPI) GetTransactionsHistory(ctx context.Context, args TxHistoryArgs) (map[string]interface{}, error) { - address := args.Address - result := []common.Hash{} + var address string + var result []common.Hash var err error if strings.HasPrefix(args.Address, "one1") { address = args.Address @@ -118,8 +118,8 @@ func (s *PublicTransactionPoolAPI) GetTransactionByHash(ctx context.Context, has // GetStakingTransactionsHistory returns the list of transactions hashes that involve a particular address. func (s *PublicTransactionPoolAPI) GetStakingTransactionsHistory(ctx context.Context, args TxHistoryArgs) (map[string]interface{}, error) { - address := args.Address - result := []common.Hash{} + var address string + var result []common.Hash var err error if strings.HasPrefix(args.Address, "one1") { address = args.Address diff --git a/internal/hmyapi/apiv2/types.go b/internal/hmyapi/apiv2/types.go index 10fdd6ab0..8206bcf8a 100644 --- a/internal/hmyapi/apiv2/types.go +++ b/internal/hmyapi/apiv2/types.go @@ -221,7 +221,7 @@ func newRPCStakingTransaction( stakingTxType := tx.StakingType() message := tx.StakingMessage() - fields := make(map[string]interface{}, 0) + fields := make(map[string]interface{}) switch stakingTxType { case types2.DirectiveCreateValidator: @@ -355,6 +355,8 @@ func newRPCStakingTransaction( // RPCBlock represents a block that will serialize to the RPC representation of a block type RPCBlock struct { Number *big.Int `json:"number"` + ViewID *big.Int `json:"viewID"` + Epoch *big.Int `json:"epoch"` Hash common.Hash `json:"hash"` ParentHash common.Hash `json:"parentHash"` Nonce types.BlockNonce `json:"nonce"` @@ -384,6 +386,8 @@ func RPCMarshalBlock(b *types.Block, blockArgs BlockArgs) (map[string]interface{ head := b.Header() // copies the header once fields := map[string]interface{}{ "number": (*big.Int)(head.Number()), + "viewID": (*big.Int)(head.ViewID()), + "epoch": (*big.Int)(head.Epoch()), "hash": b.Hash(), "parentHash": head.ParentHash(), "nonce": 0, // Remove this because we don't have it in our header diff --git a/internal/hmyapi/apiv2/util.go b/internal/hmyapi/apiv2/util.go index 07762b159..28bfdd43e 100644 --- a/internal/hmyapi/apiv2/util.go +++ b/internal/hmyapi/apiv2/util.go @@ -36,7 +36,9 @@ func SubmitTransaction( ctx context.Context, b Backend, tx *types.Transaction, ) (common.Hash, error) { if err := b.SendTx(ctx, tx); err != nil { - return common.Hash{}, err + // legacy behavior is to never return error and always return tx hash + utils.Logger().Warn().Err(err).Msg("Could not submit transaction") + return tx.Hash(), nil } if tx.To() == nil { signer := types.MakeSigner(b.ChainConfig(), b.CurrentBlock().Epoch()) @@ -63,7 +65,9 @@ func SubmitStakingTransaction( ctx context.Context, b Backend, tx *staking.StakingTransaction, ) (common.Hash, error) { if err := b.SendStakingTx(ctx, tx); err != nil { - return common.Hash{}, err + // legacy behavior is to never return error and always return tx hash + utils.Logger().Warn().Err(err).Msg("Could not submit staking transaction") + return tx.Hash(), nil } utils.Logger().Info().Str("fullhash", tx.Hash().Hex()).Msg("Submitted Staking transaction") return tx.Hash(), nil diff --git a/internal/hmyapi/backend.go b/internal/hmyapi/backend.go index 688c0a64c..7bf8a3fc9 100644 --- a/internal/hmyapi/backend.go +++ b/internal/hmyapi/backend.go @@ -64,7 +64,7 @@ type Backend interface { SendStakingTx(ctx context.Context, newStakingTx *staking.StakingTransaction) error GetElectedValidatorAddresses() []common.Address GetAllValidatorAddresses() []common.Address - GetValidatorInformation(addr common.Address, block *types.Block) (*staking.ValidatorRPCEnchanced, error) + GetValidatorInformation(addr common.Address, block *types.Block) (*staking.ValidatorRPCEnhanced, error) GetDelegationsByValidator(validator common.Address) []*staking.Delegation GetDelegationsByDelegator(delegator common.Address) ([]common.Address, []*staking.Delegation) GetDelegationsByDelegatorByBlock(delegator common.Address, block *types.Block) ([]common.Address, []*staking.Delegation) diff --git a/internal/params/config.go b/internal/params/config.go index 2898ac3de..fa2da78bf 100644 --- a/internal/params/config.go +++ b/internal/params/config.go @@ -40,9 +40,9 @@ var ( TestnetChainConfig = &ChainConfig{ ChainID: TestnetChainID, CrossTxEpoch: big.NewInt(0), - CrossLinkEpoch: big.NewInt(4), - StakingEpoch: big.NewInt(4), - PreStakingEpoch: big.NewInt(2), + CrossLinkEpoch: big.NewInt(2), + StakingEpoch: big.NewInt(2), + PreStakingEpoch: big.NewInt(1), QuickUnlockEpoch: big.NewInt(0), EIP155Epoch: big.NewInt(0), S3Epoch: big.NewInt(0), diff --git a/node/node.go b/node/node.go index 7660951ac..8d26077ad 100644 --- a/node/node.go +++ b/node/node.go @@ -156,6 +156,8 @@ type Node struct { keysToAddrsMutex sync.Mutex // TransactionErrorSink contains error messages for any failed transaction, in memory only TransactionErrorSink *types.TransactionErrorSink + // BroadcastInvalidTx flag is considered when adding pending tx to tx-pool + BroadcastInvalidTx bool } // Blockchain returns the blockchain for the node's current shard. @@ -259,13 +261,19 @@ func (node *Node) AddPendingStakingTransaction( ) error { if node.NodeConfig.ShardID == shard.BeaconChainShardID { errs := node.addPendingStakingTransactions(staking.StakingTransactions{newStakingTx}) + var err error for i := range errs { if errs[i] != nil { - return errs[i] + utils.Logger().Info().Err(errs[i]).Msg("[AddPendingStakingTransaction] Failed adding new staking transaction") + err = errs[i] + break } } - utils.Logger().Info().Str("Hash", newStakingTx.Hash().Hex()).Msg("Broadcasting Staking Tx") - node.tryBroadcastStaking(newStakingTx) + if err == nil || node.BroadcastInvalidTx { + utils.Logger().Info().Str("Hash", newStakingTx.Hash().Hex()).Msg("Broadcasting Staking Tx") + node.tryBroadcastStaking(newStakingTx) + } + return err } return nil } @@ -275,14 +283,19 @@ func (node *Node) AddPendingStakingTransaction( func (node *Node) AddPendingTransaction(newTx *types.Transaction) error { if newTx.ShardID() == node.NodeConfig.ShardID { errs := node.addPendingTransactions(types.Transactions{newTx}) + var err error for i := range errs { if errs[i] != nil { utils.Logger().Info().Err(errs[i]).Msg("[AddPendingTransaction] Failed adding new transaction") - return errs[i] + err = errs[i] + break } } - utils.Logger().Info().Str("Hash", newTx.Hash().Hex()).Msg("Broadcasting Tx") - node.tryBroadcast(newTx) + if err == nil || node.BroadcastInvalidTx { + utils.Logger().Info().Str("Hash", newTx.Hash().Hex()).Msg("Broadcasting Tx") + node.tryBroadcast(newTx) + } + return err } return nil } diff --git a/node/node_syncing.go b/node/node_syncing.go index f66350cac..76faf14fe 100644 --- a/node/node_syncing.go +++ b/node/node_syncing.go @@ -17,6 +17,7 @@ import ( "github.com/harmony-one/harmony/internal/utils" "github.com/harmony-one/harmony/node/worker" "github.com/harmony-one/harmony/p2p" + lru "github.com/hashicorp/golang-lru" "github.com/pkg/errors" ) @@ -382,11 +383,7 @@ func (node *Node) CalculateResponse(request *downloader_pb.DownloaderRequest, in var hash common.Hash for _, bytes := range request.Hashes { hash.SetBytes(bytes) - blockHeader := node.Blockchain().GetHeaderByHash(hash) - if blockHeader == nil { - continue - } - encodedBlockHeader, err := rlp.EncodeToBytes(blockHeader) + encodedBlockHeader, err := node.getEncodedBlockHeaderByHash(hash) if err == nil { response.Payload = append(response.Payload, encodedBlockHeader) @@ -397,11 +394,7 @@ func (node *Node) CalculateResponse(request *downloader_pb.DownloaderRequest, in var hash common.Hash for _, bytes := range request.Hashes { hash.SetBytes(bytes) - block := node.Blockchain().GetBlockByHash(hash) - if block == nil { - continue - } - encodedBlock, err := rlp.EncodeToBytes(block) + encodedBlock, err := node.getEncodedBlockByHash(hash) if err == nil { response.Payload = append(response.Payload, encodedBlock) @@ -478,3 +471,49 @@ func (node *Node) CalculateResponse(request *downloader_pb.DownloaderRequest, in } return response, nil } + +const ( + headerCacheSize = 10000 + blockCacheSize = 10000 +) + +var ( + // Cached fields for block header and block requests + headerReqCache, _ = lru.New(headerCacheSize) + blockReqCache, _ = lru.New(blockCacheSize) + + errHeaderNotExist = errors.New("header not exist") + errBlockNotExist = errors.New("block not exist") +) + +func (node *Node) getEncodedBlockHeaderByHash(hash common.Hash) ([]byte, error) { + if b, ok := headerReqCache.Get(hash); ok { + return b.([]byte), nil + } + h := node.Blockchain().GetHeaderByHash(hash) + if h == nil { + return nil, errHeaderNotExist + } + b, err := rlp.EncodeToBytes(h) + if err != nil { + return nil, err + } + headerReqCache.Add(hash, b) + return b, nil +} + +func (node *Node) getEncodedBlockByHash(hash common.Hash) ([]byte, error) { + if b, ok := blockReqCache.Get(hash); ok { + return b.([]byte), nil + } + blk := node.Blockchain().GetBlockByHash(hash) + if blk == nil { + return nil, errBlockNotExist + } + b, err := rlp.EncodeToBytes(blk) + if err != nil { + return nil, err + } + blockReqCache.Add(hash, b) + return b, nil +} diff --git a/scripts/go_executable_build.sh b/scripts/go_executable_build.sh index d42e0c513..da07dbfaf 100755 --- a/scripts/go_executable_build.sh +++ b/scripts/go_executable_build.sh @@ -18,7 +18,7 @@ TRACEPTR= VERBOSE= GO_GCFLAGS="all=-c 2" DEBUG=false -STATIC=false +STATIC=true unset -v progdir case "${0}" in @@ -62,6 +62,7 @@ OPTIONS: -t full analysis on {pointer} build option (default: $TRACEPTR) -v verbose build process (default: $VERBOSE) -s build static linux executable (default: $STATIC) + -S build non-static linux executable ACTION: @@ -257,6 +258,7 @@ while getopts "hp:a:o:b:f:rtvsdS" option; do v) VERBOSE='-v -x' ;; d) DEBUG=true ;; s) STATIC=true ;; + S) STATIC=false ;; esac done diff --git a/scripts/node.sh b/scripts/node.sh index e64e6f2e3..1bc559973 100755 --- a/scripts/node.sh +++ b/scripts/node.sh @@ -200,6 +200,7 @@ options: -r address start a pprof profiling server listening on the specified address -I use statically linked Harmony binary (default: true) -R tracefile enable p2p trace using tracefile (default: off) + -l limit broadcasting of invalid transactions (default: off) -L log_level logging verbosity: 0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=detail (default: $log_level) examples: @@ -251,7 +252,7 @@ usage() { BUCKET=pub.harmony.one OS=$(uname -s) -unset start_clean loop run_as_root blspass do_not_download download_only network node_type shard_id db_file_to_dl +unset start_clean loop run_as_root blspass do_not_download download_only network node_type shard_id broadcast_invalid_tx unset upgrade_rel public_rpc staking_mode pub_port multi_key blsfolder blacklist verify TRACEFILE minpeers max_bls_keys_per_node log_level start_clean=false loop=true @@ -272,13 +273,14 @@ static=true verify=false minpeers=6 max_bls_keys_per_node=10 +broadcast_invalid_tx=true log_level=3 ${BLSKEYFILE=} ${TRACEFILE=} unset OPTIND OPTARG opt OPTIND=1 -while getopts :1chk:sSp:dDN:T:i:a:U:PvVyzn:MAIB:r:Y:f:R:m:L: opt +while getopts :1chk:sSp:dDN:T:i:U:PvVyzn:MAIB:r:Y:f:R:m:L:l opt do case "${opt}" in '?') usage "unrecognized option -${OPTARG}";; @@ -300,7 +302,6 @@ do T) node_type="${OPTARG}";; i) shard_id="${OPTARG}";; I) static=true;; - a) db_file_to_dl="${OPTARG}";; U) upgrade_rel="${OPTARG}";; P) public_rpc=true;; B) blacklist="${OPTARG}";; @@ -314,6 +315,7 @@ do y) staking_mode=false;; A) archival=true;; R) TRACEFILE="${OPTARG}";; + l) broadcast_invalid_tx=false;; L) log_level="${OPTARG}";; *) err 70 "unhandled option -${OPTARG}";; # EX_SOFTWARE esac @@ -342,26 +344,16 @@ mainnet) dns_zone=t.hmny.io syncdir=mainnet.min ;; -testnet) # TODO: update Testnet configs once LRTN is upgraded +testnet) bootnodes=( - /ip4/54.218.73.167/tcp/9876/p2p/QmWBVCPXQmc2ULigm3b9ayCZa15gj25kywiQQwPhHCZeXj - /ip4/18.232.171.117/tcp/9876/p2p/QmfJ71Eb7XTDs8hX2vPJ8un4L7b7RiDk6zCzWVxLXGA6MA + /ip4/54.86.126.90/tcp/9850/p2p/Qmdfjtk6hPoyrH1zVD9PEH4zfWLo38dP2mDvvKXfh3tnEv + /ip4/52.40.84.2/tcp/9850/p2p/QmbPVwrqWsTYXq1RxGWcxx9SWaTUCfoo1wA6wmdbduWe29 ) REL=testnet network_type=testnet - dns_zone=p.hmny.io + dns_zone=b.hmny.io syncdir=lrtn ;; -tnet) - bootnodes=( - /ip4/54.86.126.90/tcp/9889/p2p/Qmdfjtk6hPoyrH1zVD9PEH4zfWLo38dP2mDvvKXfh3tnEv - /ip4/52.40.84.2/tcp/9889/p2p/QmbPVwrqWsTYXq1RxGWcxx9SWaTUCfoo1wA6wmdbduWe29 - ) - REL=tnet - network_type=testnet - dns_zone=tn.hmny.io - syncdir=tnet - ;; staking) bootnodes=( /ip4/54.86.126.90/tcp/9867/p2p/Qmdfjtk6hPoyrH1zVD9PEH4zfWLo38dP2mDvvKXfh3tnEv @@ -391,16 +383,6 @@ stn|stress|stressnet) dns_zone=stn.hmny.io syncdir=stn ;; -devnet) - bootnodes=( - /ip4/54.86.126.90/tcp/9870/p2p/Qmdfjtk6hPoyrH1zVD9PEH4zfWLo38dP2mDvvKXfh3tnEv - /ip4/52.40.84.2/tcp/9870/p2p/QmbPVwrqWsTYXq1RxGWcxx9SWaTUCfoo1wA6wmdbduWe29 - ) - REL=devnet - network_type=devnet - dns_zone=pga.hmny.io - syncdir=devnet - ;; *) err 64 "${network}: invalid network" ;; @@ -767,7 +749,8 @@ read_bls_pass() { unset -v passphrase read -rsp "Enter passphrase for the BLS key file $f: " passphrase echo ${passphrase} | tee $passfile - echo "Passphrase is temporarely saved to: $passfile" + chmod og-wr $passfile + echo "Passphrase is temporarily saved to: $passfile" prompt_save=true fi done @@ -853,6 +836,7 @@ do -blacklist="${blacklist}" -min_peers="${minpeers}" -max_bls_keys_per_node="${max_bls_keys_per_node}" + -broadcast_invalid_tx="${broadcast_invalid_tx}" -verbosity="${log_level}" ) args+=( diff --git a/staking/availability/measure_test.go b/staking/availability/measure_test.go index 35b29d661..104056e20 100644 --- a/staking/availability/measure_test.go +++ b/staking/availability/measure_test.go @@ -470,10 +470,7 @@ func checkIncWrapperVerified(snapWrapper, curWrapper *staking.ValidatorWrapper) } snapToSign := snapWrapper.Counters.NumBlocksToSign curToSign := curWrapper.Counters.NumBlocksToSign - if curToSign.Cmp(new(big.Int).Add(snapToSign, common.Big1)) != 0 { - return false - } - return true + return curToSign.Cmp(new(big.Int).Add(snapToSign, common.Big1)) == 0 } // checkIncWrapperMissing is the compare function to check whether validator wrapper @@ -486,10 +483,7 @@ func checkIncWrapperMissing(snapWrapper, curWrapper *staking.ValidatorWrapper) b } snapToSign := snapWrapper.Counters.NumBlocksToSign curToSign := curWrapper.Counters.NumBlocksToSign - if curToSign.Cmp(new(big.Int).Add(snapToSign, common.Big1)) != 0 { - return false - } - return true + return curToSign.Cmp(new(big.Int).Add(snapToSign, common.Big1)) == 0 } type computeEPOSTestCtx struct { diff --git a/staking/effective/eligible.go b/staking/effective/eligible.go index ab3770e61..4fcce379d 100644 --- a/staking/effective/eligible.go +++ b/staking/effective/eligible.go @@ -21,6 +21,19 @@ const ( Banned ) +func (e Eligibility) String() string { + switch e { + case Active: + return "active" + case Inactive: + return "inactive" + case Banned: + return doubleSigningBanned + default: + return "unknown" + } +} + // Candidacy is a more semantically meaningful // value that is derived from core protocol logic but // meant more for the presentation of user, like at RPC diff --git a/staking/slash/double-sign_test.go b/staking/slash/double-sign_test.go index 79f513d07..3883234a4 100644 --- a/staking/slash/double-sign_test.go +++ b/staking/slash/double-sign_test.go @@ -630,7 +630,6 @@ func (tc *applyTestCase) makeData(t *testing.T) { } } tc.stateSnap = tc.state.Copy() - return } func (tc *applyTestCase) apply() { @@ -781,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/slash/interface_test.go b/staking/slash/interface_test.go index 1ea862e59..f45d74a22 100644 --- a/staking/slash/interface_test.go +++ b/staking/slash/interface_test.go @@ -11,10 +11,6 @@ import ( "github.com/pkg/errors" ) -const ( - fakeChainErrEpoch = 1 -) - var ( errFakeChainUnexpectEpoch = errors.New("epoch not expected") ) 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 9d4f8d077..fc200e961 100644 --- a/staking/types/test/copy_test.go +++ b/staking/types/test/copy_test.go @@ -14,14 +14,6 @@ import ( staking "github.com/harmony-one/harmony/staking/types" ) -var ( - zeroDec = numeric.ZeroDec() - oneThirdDec = numeric.NewDecWithPrec(33, 2) - halfDec = numeric.NewDecWithPrec(5, 1) - twoThirdDec = numeric.NewDecWithPrec(66, 2) - oneDec = numeric.OneDec() -) - var ( testPub = shard.BLSPublicKey{1} ) @@ -35,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) } } @@ -255,13 +247,6 @@ func assertCommissionDeepCopy(c1, c2 staking.Commission) error { return nil } -func assertCommissionRatesDeepCopy(cr1, cr2 staking.CommissionRates) error { - if !reflect.DeepEqual(cr1, cr2) { - return errors.New("not deep equal") - } - return assertCommissionRatesCopy(cr1, cr2) -} - func assertCommissionRatesCopy(cr1, cr2 staking.CommissionRates) error { if err := assertDecCopy(cr1.Rate, cr2.Rate); err != nil { return fmt.Errorf("rate: %v", 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) + } + } +} diff --git a/staking/types/validator.go b/staking/types/validator.go index f2697e71f..6e6d9a6e1 100644 --- a/staking/types/validator.go +++ b/staking/types/validator.go @@ -138,8 +138,8 @@ type CurrentEpochPerformance struct { CurrentSigningPercentage Computed `json:"current-epoch-signing-percent"` } -// ValidatorRPCEnchanced contains extra information for RPC consumer -type ValidatorRPCEnchanced struct { +// ValidatorRPCEnhanced contains extra information for RPC consumer +type ValidatorRPCEnhanced struct { Wrapper ValidatorWrapper `json:"validator"` Performance *CurrentEpochPerformance `json:"current-epoch-performance"` ComputedMetrics *ValidatorStats `json:"metrics"` @@ -148,6 +148,7 @@ type ValidatorRPCEnchanced struct { EPoSStatus string `json:"epos-status"` EPoSWinningStake *numeric.Dec `json:"epos-winning-stake"` BootedStatus *string `json:"booted-status"` + ActiveStatus string `json:"active-status"` Lifetime *AccumulatedOverLifetime `json:"lifetime"` } diff --git a/staking/types/validator_test.go b/staking/types/validator_test.go index 098dd4452..ddcd9ba27 100644 --- a/staking/types/validator_test.go +++ b/staking/types/validator_test.go @@ -26,11 +26,8 @@ var ( ) var ( - zeroDec = numeric.ZeroDec() - oneThirdDec = numeric.NewDecWithPrec(33, 2) - halfDec = numeric.NewDecWithPrec(5, 1) - twoThirdDec = numeric.NewDecWithPrec(66, 2) - oneDec = numeric.OneDec() + zeroDec = numeric.ZeroDec() + oneDec = numeric.OneDec() ) var ( diff --git a/webhooks/yaml.go b/webhooks/yaml.go index d459188a1..2c0331fcc 100644 --- a/webhooks/yaml.go +++ b/webhooks/yaml.go @@ -64,6 +64,9 @@ func DoPost(url string, record interface{}) (*ReportResult, error) { } defer resp.Body.Close() result, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } anon := ReportResult{} if err := json.Unmarshal(result, &anon); err != nil { return nil, err