Merge branch 'master' into blacklist

pull/2172/head
Daniel Van Der Maden 5 years ago committed by GitHub
commit a56ea0d7a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      core/block_validator.go
  2. 9
      core/blockchain.go
  3. 22
      core/state/dump.go
  4. 2
      core/types.go
  5. 5
      hmy/api_backend.go
  6. 1
      hmy/backend.go
  7. 7
      internal/chain/engine.go
  8. 6
      internal/configs/sharding/testnet.go
  9. 1
      internal/hmyapi/apiv1/backend.go
  10. 6
      internal/hmyapi/apiv1/blockchain.go
  11. 1
      internal/hmyapi/apiv2/backend.go
  12. 6
      internal/hmyapi/apiv2/blockchain.go
  13. 1
      internal/hmyapi/backend.go
  14. 49
      node/node_newblock.go
  15. 7
      node/rpc.go
  16. 56
      staking/availability/apply.go
  17. 12
      staking/availability/measure.go
  18. 15
      staking/types/validator.go

@ -19,10 +19,12 @@ package core
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"encoding/hex"
"fmt" "fmt"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
"github.com/harmony-one/harmony/block" "github.com/harmony-one/harmony/block"
"github.com/harmony-one/harmony/internal/ctxerror" "github.com/harmony-one/harmony/internal/ctxerror"
@ -83,7 +85,7 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
// transition, such as amount of used gas, the receipt roots and the state root // transition, such as amount of used gas, the receipt roots and the state root
// itself. ValidateState returns a database batch if the validation was a success // itself. ValidateState returns a database batch if the validation was a success
// otherwise nil and an error is returned. // otherwise nil and an error is returned.
func (v *BlockValidator) ValidateState(block, parent *types.Block, statedb *state.DB, receipts types.Receipts, cxReceipts types.CXReceipts, usedGas uint64) error { func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.DB, receipts types.Receipts, cxReceipts types.CXReceipts, usedGas uint64) error {
header := block.Header() header := block.Header()
if block.GasUsed() != usedGas { if block.GasUsed() != usedGas {
return fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), usedGas) return fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), usedGas)
@ -91,7 +93,9 @@ func (v *BlockValidator) ValidateState(block, parent *types.Block, statedb *stat
// Validate the received block's bloom with the one derived from the generated receipts. // Validate the received block's bloom with the one derived from the generated receipts.
// For valid blocks this should always validate to true. // For valid blocks this should always validate to true.
rbloom := types.CreateBloom(receipts) rbloom := types.CreateBloom(receipts)
if rbloom != header.Bloom() { // Beacon chain block 1213181 is a one-off block with empty bloom which is expected to be non-empty.
// Skip the validation for it to avoid failure.
if rbloom != header.Bloom() && (block.NumberU64() != 1213181 || block.ShardID() != 0) {
return fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom(), rbloom) return fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom(), rbloom)
} }
// Tre receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, R1]])) // Tre receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, R1]]))
@ -113,7 +117,9 @@ func (v *BlockValidator) ValidateState(block, parent *types.Block, statedb *stat
// Validate the state root against the received state root and throw // Validate the state root against the received state root and throw
// an error if they don't match. // an error if they don't match.
if root := statedb.IntermediateRoot(v.config.IsS3(header.Epoch())); header.Root() != root { if root := statedb.IntermediateRoot(v.config.IsS3(header.Epoch())); header.Root() != root {
return fmt.Errorf("invalid merkle root (remote: %x local: %x)", header.Root(), root) dump, _ := rlp.EncodeToBytes(header)
const msg = "invalid merkle root (remote: %x local: %x, rlp dump %s)"
return fmt.Errorf(msg, header.Root(), root, hex.EncodeToString(dump))
} }
return nil return nil
} }

@ -246,6 +246,7 @@ func (bc *BlockChain) ValidateNewBlock(block *types.Block) error {
return err return err
} }
// NOTE Order of mutating state here matters.
// Process block using the parent state as reference point. // Process block using the parent state as reference point.
receipts, cxReceipts, _, usedGas, _, err := bc.processor.Process(block, state, bc.vmConfig) receipts, cxReceipts, _, usedGas, _, err := bc.processor.Process(block, state, bc.vmConfig)
if err != nil { if err != nil {
@ -253,8 +254,7 @@ func (bc *BlockChain) ValidateNewBlock(block *types.Block) error {
return err return err
} }
err = bc.Validator().ValidateState(block, bc.CurrentBlock(), state, receipts, cxReceipts, usedGas) if err := bc.Validator().ValidateState(block, state, receipts, cxReceipts, usedGas); err != nil {
if err != nil {
bc.reportBlock(block, receipts, err) bc.reportBlock(block, receipts, err)
return err return err
} }
@ -1476,8 +1476,9 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifyHeaders bool) (int,
} }
// Validate the state using the default validator // Validate the state using the default validator
err = bc.Validator().ValidateState(block, parent, state, receipts, cxReceipts, usedGas) if err := bc.Validator().ValidateState(
if err != nil { block, state, receipts, cxReceipts, usedGas,
); err != nil {
bc.reportBlock(block, receipts, err) bc.reportBlock(block, receipts, err)
return i, events, coalescedLogs, err return i, events, coalescedLogs, err
} }

@ -23,6 +23,8 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
common2 "github.com/harmony-one/harmony/internal/common"
staking "github.com/harmony-one/harmony/staking/types"
) )
// DumpAccount ... // DumpAccount ...
@ -55,31 +57,41 @@ func (db *DB) RawDump() Dump {
if err := rlp.DecodeBytes(it.Value, &data); err != nil { if err := rlp.DecodeBytes(it.Value, &data); err != nil {
panic(err) panic(err)
} }
obj := newObject(nil, common.BytesToAddress(addr), data) obj := newObject(nil, common.BytesToAddress(addr), data)
var wrapper staking.ValidatorWrapper
wrap := ""
if err := rlp.DecodeBytes(obj.Code(db.db), &wrapper); err != nil {
//
} else {
marsh, err := json.Marshal(wrapper)
if err == nil {
wrap = string(marsh)
}
}
account := DumpAccount{ account := DumpAccount{
Balance: data.Balance.String(), Balance: data.Balance.String(),
Nonce: data.Nonce, Nonce: data.Nonce,
Root: common.Bytes2Hex(data.Root[:]), Root: common.Bytes2Hex(data.Root[:]),
CodeHash: common.Bytes2Hex(data.CodeHash), CodeHash: common.Bytes2Hex(data.CodeHash),
Code: common.Bytes2Hex(obj.Code(db.db)), Code: wrap,
Storage: make(map[string]string), Storage: make(map[string]string),
} }
storageIt := trie.NewIterator(obj.getTrie(db.db).NodeIterator(nil)) storageIt := trie.NewIterator(obj.getTrie(db.db).NodeIterator(nil))
for storageIt.Next() { for storageIt.Next() {
account.Storage[common.Bytes2Hex(db.trie.GetKey(storageIt.Key))] = common.Bytes2Hex(storageIt.Value) account.Storage[common.Bytes2Hex(db.trie.GetKey(storageIt.Key))] = common.Bytes2Hex(storageIt.Value)
} }
dump.Accounts[common.Bytes2Hex(addr)] = account dump.Accounts[common2.MustAddressToBech32(common.BytesToAddress(addr))] = account
} }
return dump return dump
} }
// Dump ... // Dump ...
func (db *DB) Dump() []byte { func (db *DB) Dump() string {
json, err := json.MarshalIndent(db.RawDump(), "", " ") json, err := json.MarshalIndent(db.RawDump(), "", " ")
if err != nil { if err != nil {
fmt.Println("dump err", err) fmt.Println("dump err", err)
} }
return json return string(json)
} }

@ -34,7 +34,7 @@ type Validator interface {
// ValidateState validates the given statedb and optionally the receipts and // ValidateState validates the given statedb and optionally the receipts and
// gas used. // gas used.
ValidateState(block, parent *types.Block, state *state.DB, receipts types.Receipts, cxs types.CXReceipts, usedGas uint64) error ValidateState(block *types.Block, state *state.DB, receipts types.Receipts, cxs types.CXReceipts, usedGas uint64) error
// ValidateHeader checks whether a header conforms to the consensus rules of a // ValidateHeader checks whether a header conforms to the consensus rules of a
// given engine. Verifying the seal may be done optionally here, or explicitly // given engine. Verifying the seal may be done optionally here, or explicitly

@ -427,11 +427,6 @@ func (b *APIBackend) GetCurrentTransactionErrorSink() []types.RPCTransactionErro
return b.hmy.nodeAPI.ErroredTransactionSink() return b.hmy.nodeAPI.ErroredTransactionSink()
} }
// IsBeaconChainExplorerNode ..
func (b *APIBackend) IsBeaconChainExplorerNode() bool {
return b.hmy.nodeAPI.IsBeaconChainExplorerNode()
}
// GetPendingCXReceipts .. // GetPendingCXReceipts ..
func (b *APIBackend) GetPendingCXReceipts() []*types.CXReceiptsProof { func (b *APIBackend) GetPendingCXReceipts() []*types.CXReceiptsProof {
return b.hmy.nodeAPI.PendingCXReceipts() return b.hmy.nodeAPI.PendingCXReceipts()

@ -50,7 +50,6 @@ type NodeAPI interface {
IsCurrentlyLeader() bool IsCurrentlyLeader() bool
ErroredStakingTransactionSink() []staking.RPCTransactionError ErroredStakingTransactionSink() []staking.RPCTransactionError
ErroredTransactionSink() []types.RPCTransactionError ErroredTransactionSink() []types.RPCTransactionError
IsBeaconChainExplorerNode() bool
PendingCXReceipts() []*types.CXReceiptsProof PendingCXReceipts() []*types.CXReceiptsProof
} }

@ -20,6 +20,7 @@ import (
"github.com/harmony-one/harmony/internal/utils" "github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/shard" "github.com/harmony-one/harmony/shard"
"github.com/harmony-one/harmony/shard/committee" "github.com/harmony-one/harmony/shard/committee"
"github.com/harmony-one/harmony/staking/availability"
"github.com/harmony-one/harmony/staking/slash" "github.com/harmony-one/harmony/staking/slash"
staking "github.com/harmony-one/harmony/staking/types" staking "github.com/harmony-one/harmony/staking/types"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -329,6 +330,12 @@ func (e *engineImpl) Finalize(
} }
} }
if isBeaconChain && isNewEpoch && inStakingEra {
if err := availability.Apply(chain, state); err != nil {
return nil, nil, err
}
}
header.SetRoot(state.IntermediateRoot(chain.Config().IsS3(header.Epoch()))) header.SetRoot(state.IntermediateRoot(chain.Config().IsS3(header.Epoch())))
return types.NewBlock(header, txs, receipts, outcxs, incxs, stks), payout, nil return types.NewBlock(header, txs, receipts, outcxs, incxs, stks), payout, nil
} }

@ -27,7 +27,7 @@ const (
func (testnetSchedule) InstanceForEpoch(epoch *big.Int) Instance { func (testnetSchedule) InstanceForEpoch(epoch *big.Int) Instance {
switch { switch {
case epoch.Cmp(params.PangaeaChainConfig.StakingEpoch) >= 0: case epoch.Cmp(params.TestnetChainConfig.StakingEpoch) >= 0:
return testnetV1 return testnetV1
default: // genesis default: // genesis
return testnetV0 return testnetV0
@ -77,8 +77,8 @@ func (ts testnetSchedule) GetShardingStructure(numShard, shardID int) []map[stri
var testnetReshardingEpoch = []*big.Int{ var testnetReshardingEpoch = []*big.Int{
big.NewInt(0), big.NewInt(0),
params.PangaeaChainConfig.StakingEpoch, params.TestnetChainConfig.StakingEpoch,
} }
var testnetV0 = MustNewInstance(3, 10, 7, genesis.TNHarmonyAccounts, genesis.TNFoundationalAccounts, testnetReshardingEpoch, TestnetSchedule.BlocksPerEpoch()) var testnetV0 = MustNewInstance(3, 10, 7, genesis.TNHarmonyAccounts, genesis.TNFoundationalAccounts, testnetReshardingEpoch, TestnetSchedule.BlocksPerEpoch())
var testnetV1 = MustNewInstance(3, 10, 3, genesis.TNHarmonyAccounts, genesis.TNFoundationalAccounts, testnetReshardingEpoch, TestnetSchedule.BlocksPerEpoch()) var testnetV1 = MustNewInstance(3, 20, 7, genesis.TNHarmonyAccounts, genesis.TNFoundationalAccounts, testnetReshardingEpoch, TestnetSchedule.BlocksPerEpoch())

@ -79,7 +79,6 @@ type Backend interface {
GetShardState() (*shard.State, error) GetShardState() (*shard.State, error)
GetCurrentStakingErrorSink() []staking.RPCTransactionError GetCurrentStakingErrorSink() []staking.RPCTransactionError
GetCurrentTransactionErrorSink() []types.RPCTransactionError GetCurrentTransactionErrorSink() []types.RPCTransactionError
IsBeaconChainExplorerNode() bool
GetMedianRawStakeSnapshot() *big.Int GetMedianRawStakeSnapshot() *big.Int
GetPendingCXReceipts() []*types.CXReceiptsProof GetPendingCXReceipts() []*types.CXReceiptsProof
} }

@ -493,16 +493,16 @@ func (s *PublicBlockChainAPI) LatestHeader(ctx context.Context) *HeaderInformati
} }
var ( var (
errNotExplorerNode = errors.New("cannot call this rpc on non beaconchain explorer node") errNotBeaconChainShard = errors.New("cannot call this rpc on non beaconchain node")
) )
// GetMedianRawStakeSnapshot returns the raw median stake, only meant to be called on beaconchain // GetMedianRawStakeSnapshot returns the raw median stake, only meant to be called on beaconchain
// explorer node // explorer node
func (s *PublicBlockChainAPI) GetMedianRawStakeSnapshot() (*big.Int, error) { func (s *PublicBlockChainAPI) GetMedianRawStakeSnapshot() (*big.Int, error) {
if s.b.IsBeaconChainExplorerNode() { if s.b.GetShardID() == shard.BeaconChainShardID {
return s.b.GetMedianRawStakeSnapshot(), nil return s.b.GetMedianRawStakeSnapshot(), nil
} }
return nil, errNotExplorerNode return nil, errNotBeaconChainShard
} }
// GetAllValidatorAddresses returns all validator addresses. // GetAllValidatorAddresses returns all validator addresses.

@ -79,7 +79,6 @@ type Backend interface {
GetShardState() (*shard.State, error) GetShardState() (*shard.State, error)
GetCurrentStakingErrorSink() []staking.RPCTransactionError GetCurrentStakingErrorSink() []staking.RPCTransactionError
GetCurrentTransactionErrorSink() []types.RPCTransactionError GetCurrentTransactionErrorSink() []types.RPCTransactionError
IsBeaconChainExplorerNode() bool
GetMedianRawStakeSnapshot() *big.Int GetMedianRawStakeSnapshot() *big.Int
GetPendingCXReceipts() []*types.CXReceiptsProof GetPendingCXReceipts() []*types.CXReceiptsProof
} }

@ -455,16 +455,16 @@ func (s *PublicBlockChainAPI) LatestHeader(ctx context.Context) *HeaderInformati
} }
var ( var (
errNotExplorerNode = errors.New("cannot call this rpc on non beaconchain explorer node") errNotBeaconChainShard = errors.New("cannot call this rpc on non beaconchain node")
) )
// GetMedianRawStakeSnapshot returns the raw median stake, only meant to be called on beaconchain // GetMedianRawStakeSnapshot returns the raw median stake, only meant to be called on beaconchain
// explorer node // explorer node
func (s *PublicBlockChainAPI) GetMedianRawStakeSnapshot() (*big.Int, error) { func (s *PublicBlockChainAPI) GetMedianRawStakeSnapshot() (*big.Int, error) {
if s.b.IsBeaconChainExplorerNode() { if s.b.GetShardID() == shard.BeaconChainShardID {
return s.b.GetMedianRawStakeSnapshot(), nil return s.b.GetMedianRawStakeSnapshot(), nil
} }
return nil, errNotExplorerNode return nil, errNotBeaconChainShard
} }
// GetAllValidatorAddresses returns all validator addresses. // GetAllValidatorAddresses returns all validator addresses.

@ -81,7 +81,6 @@ type Backend interface {
GetShardState() (*shard.State, error) GetShardState() (*shard.State, error)
GetCurrentStakingErrorSink() []staking.RPCTransactionError GetCurrentStakingErrorSink() []staking.RPCTransactionError
GetCurrentTransactionErrorSink() []types.RPCTransactionError GetCurrentTransactionErrorSink() []types.RPCTransactionError
IsBeaconChainExplorerNode() bool
GetMedianRawStakeSnapshot() *big.Int GetMedianRawStakeSnapshot() *big.Int
GetPendingCXReceipts() []*types.CXReceiptsProof GetPendingCXReceipts() []*types.CXReceiptsProof
} }

@ -167,55 +167,6 @@ func (node *Node) proposeNewBlock() (*types.Block, error) {
} }
} }
// Bump up signers counts
_, header := node.Worker.GetCurrentState(), node.Blockchain().CurrentHeader()
if epoch := header.Epoch(); node.Blockchain().Config().IsStaking(epoch) {
if header.ShardID() == shard.BeaconChainShardID {
superCommittee, err := node.Blockchain().ReadShardState(header.Epoch())
processed := make(map[common.Address]struct{})
if err != nil {
return nil, err
}
for j := range superCommittee.Shards {
shard := superCommittee.Shards[j]
for j := range shard.Slots {
slot := shard.Slots[j]
if slot.EffectiveStake != nil { // For external validator
_, ok := processed[slot.EcdsaAddress]
if !ok {
processed[slot.EcdsaAddress] = struct{}{}
}
}
}
}
// TODO(Edgar) Need to uncomment and fix, unknown why enabling
// this code causes merkle root verification issues
// if err := availability.IncrementValidatorSigningCounts(
// node.Blockchain(), header, header.ShardID(), state, processed,
// ); err != nil {
// return nil, err
// }
// // kick out the inactive validators so they won't come up in the auction as possible
// // candidates in the following call to SuperCommitteeForNextEpoch
// if shard.Schedule.IsLastBlock(header.Number().Uint64()) {
// fmt.Println("hit the last block condition")
// if err := availability.SetInactiveUnavailableValidators(
// node.Blockchain(), state, processed,
// ); err != nil {
// return nil, err
// }
// }
} else {
// TODO Handle shard chain
}
}
// Prepare shard state // Prepare shard state
shardState := new(shard.State) shardState := new(shard.State)
if shardState, err = node.Blockchain().SuperCommitteeForNextEpoch( if shardState, err = node.Blockchain().SuperCommitteeForNextEpoch(

@ -16,7 +16,6 @@ import (
"github.com/harmony-one/harmony/internal/hmyapi/apiv2" "github.com/harmony-one/harmony/internal/hmyapi/apiv2"
"github.com/harmony-one/harmony/internal/hmyapi/filters" "github.com/harmony-one/harmony/internal/hmyapi/filters"
"github.com/harmony-one/harmony/internal/utils" "github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/shard"
staking "github.com/harmony-one/harmony/staking/types" staking "github.com/harmony-one/harmony/staking/types"
) )
@ -85,12 +84,6 @@ func (node *Node) ErroredTransactionSink() []types.RPCTransactionError {
return result return result
} }
// IsBeaconChainExplorerNode ..
func (node *Node) IsBeaconChainExplorerNode() bool {
return node.NodeConfig.Role() == nodeconfig.ExplorerNode &&
node.Consensus.ShardID == shard.BeaconChainShardID
}
// StartRPC start RPC service // StartRPC start RPC service
func (node *Node) StartRPC(nodePort string) error { func (node *Node) StartRPC(nodePort string) error {
// Gather all the possible APIs to surface // Gather all the possible APIs to surface

@ -0,0 +1,56 @@
package availability
import (
"github.com/ethereum/go-ethereum/common"
engine "github.com/harmony-one/harmony/consensus/engine"
"github.com/harmony-one/harmony/core/state"
"github.com/harmony-one/harmony/shard"
)
// Apply ..
func Apply(bc engine.ChainReader, state *state.DB) error {
header := bc.CurrentHeader()
if epoch := header.Epoch(); bc.Config().IsStaking(epoch) {
if header.ShardID() == shard.BeaconChainShardID {
superCommittee, err := bc.ReadShardState(header.Epoch())
processed := make(map[common.Address]struct{})
if err != nil {
return err
}
for j := range superCommittee.Shards {
shard := superCommittee.Shards[j]
for j := range shard.Slots {
slot := shard.Slots[j]
if slot.EffectiveStake != nil { // For external validator
_, ok := processed[slot.EcdsaAddress]
if !ok {
processed[slot.EcdsaAddress] = struct{}{}
}
}
}
}
if err := IncrementValidatorSigningCounts(
bc, header, header.ShardID(), state, processed,
); err != nil {
return err
}
// // kick out the inactive validators so they won't come up in the auction as possible
// // candidates in the following call to SuperCommitteeForNextEpoch
if shard.Schedule.IsLastBlock(header.Number().Uint64()) {
if err := SetInactiveUnavailableValidators(
bc, state, processed,
); err != nil {
return err
}
}
} else {
// TODO Handle shard chain
}
}
return nil
}

@ -16,8 +16,12 @@ import (
var ( var (
measure = new(big.Int).Div(big.NewInt(2), big.NewInt(3)) measure = new(big.Int).Div(big.NewInt(2), big.NewInt(3))
errValidatorEpochDeviation = errors.New("validator snapshot epoch not exactly one epoch behind") errValidatorEpochDeviation = errors.New(
errNegativeSign = errors.New("impossible period of signing") "validator snapshot epoch not exactly one epoch behind",
)
errNegativeSign = errors.New("impossible period of signing")
// ErrDivByZero ..
ErrDivByZero = errors.New("toSign of availability cannot be 0, mistake in protocol")
) )
// BlockSigners .. // BlockSigners ..
@ -216,6 +220,10 @@ func SetInactiveUnavailableValidators(
) )
} }
if toSign.Cmp(common.Big0) == 0 {
return ErrDivByZero
}
if r := new(big.Int).Div(signed, toSign); r.Cmp(measure) == -1 { if r := new(big.Int).Div(signed, toSign); r.Cmp(measure) == -1 {
wrapper.Active = false wrapper.Active = false
if err := state.UpdateStakingInfo(addrs[i], wrapper); err != nil { if err := state.UpdateStakingInfo(addrs[i], wrapper); err != nil {

@ -39,6 +39,9 @@ var (
errBLSKeysNotMatchSigs = errors.New("bls keys and corresponding signatures could not be verified") errBLSKeysNotMatchSigs = errors.New("bls keys and corresponding signatures could not be verified")
errNilMinSelfDelegation = errors.New("MinSelfDelegation can not be nil") errNilMinSelfDelegation = errors.New("MinSelfDelegation can not be nil")
errNilMaxTotalDelegation = errors.New("MaxTotalDelegation can not be nil") errNilMaxTotalDelegation = errors.New("MaxTotalDelegation can not be nil")
errSlotKeyToRemoveNotFound = errors.New("slot key to remove not found")
errSlotKeyToAddExists = errors.New("slot key to add already exists")
errDuplicateSlotKeys = errors.New("slot keys can not have duplicates")
) )
// ValidatorWrapper contains validator and its delegation information // ValidatorWrapper contains validator and its delegation information
@ -259,6 +262,14 @@ func (w *ValidatorWrapper) SanityCheck() error {
) )
} }
allKeys := map[shard.BlsPublicKey]struct{}{}
for i := range w.Validator.SlotPubKeys {
if _, ok := allKeys[w.Validator.SlotPubKeys[i]]; !ok {
allKeys[w.Validator.SlotPubKeys[i]] = struct{}{}
} else {
return errDuplicateSlotKeys
}
}
return nil return nil
} }
@ -433,6 +444,8 @@ func UpdateValidatorFromEditMsg(validator *Validator, edit *EditValidator) error
// we found key to be removed // we found key to be removed
if index >= 0 { if index >= 0 {
validator.SlotPubKeys = append(validator.SlotPubKeys[:index], validator.SlotPubKeys[index+1:]...) validator.SlotPubKeys = append(validator.SlotPubKeys[:index], validator.SlotPubKeys[index+1:]...)
} else {
return errSlotKeyToRemoveNotFound
} }
} }
@ -449,6 +462,8 @@ func UpdateValidatorFromEditMsg(validator *Validator, edit *EditValidator) error
return err return err
} }
validator.SlotPubKeys = append(validator.SlotPubKeys, *edit.SlotKeyToAdd) validator.SlotPubKeys = append(validator.SlotPubKeys, *edit.SlotKeyToAdd)
} else {
return errSlotKeyToAddExists
} }
} }

Loading…
Cancel
Save