diff --git a/consensus/consensus.go b/consensus/consensus.go index 8f2006947..27ac19b73 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -30,6 +30,7 @@ import ( "github.com/kaiachain/kaia/common" "github.com/kaiachain/kaia/kaiax" "github.com/kaiachain/kaia/kaiax/staking" + "github.com/kaiachain/kaia/kaiax/valset" "github.com/kaiachain/kaia/networks/p2p" "github.com/kaiachain/kaia/networks/rpc" "github.com/kaiachain/kaia/params" @@ -177,6 +178,7 @@ type Istanbul interface { // UpdateParam updates the governance parameter UpdateParam(num uint64) error + RegisterValsetModule(mValset valset.ValsetModule) kaiax.ConsensusModuleHost staking.StakingModuleHost } diff --git a/consensus/istanbul/backend.go b/consensus/istanbul/backend.go index c06dcbfb7..5fab732ee 100644 --- a/consensus/istanbul/backend.go +++ b/consensus/istanbul/backend.go @@ -37,19 +37,16 @@ type Backend interface { // Address returns the owner's address Address() common.Address - // Validators returns the validator set - Validators(proposal Proposal) ValidatorSet - // EventMux returns the event mux in backend EventMux() *event.TypeMux // Broadcast sends a message to all validators (include self) - Broadcast(prevHash common.Hash, valSet ValidatorSet, payload []byte) error + Broadcast(prevHash common.Hash, payload []byte) error // Gossip sends a message to all validators (exclude self) - Gossip(valSet ValidatorSet, payload []byte) error + Gossip(payload []byte) error - GossipSubPeer(prevHash common.Hash, valSet ValidatorSet, payload []byte) map[common.Address]bool + GossipSubPeer(prevHash common.Hash, payload []byte) // Commit delivers an approved proposal to backend. // The delivered proposal will be put into blockchain. @@ -75,9 +72,6 @@ type Backend interface { // GetProposer returns the proposer of the given block height GetProposer(number uint64) common.Address - // ParentValidators returns the validator set of the given proposal's parent block - ParentValidators(proposal Proposal) ValidatorSet - // HasBadProposal returns whether the proposal with the hash is a bad proposal HasBadProposal(hash common.Hash) bool @@ -86,4 +80,10 @@ type Backend interface { SetCurrentView(view *View) NodeType() common.ConnType + + GetValidatorSet(num uint64) (*BlockValSet, error) + + GetCommitteeState(num uint64) (*RoundCommitteeState, error) + + GetCommitteeStateByRound(num uint64, round uint64) (*RoundCommitteeState, error) } diff --git a/consensus/istanbul/backend/api.go b/consensus/istanbul/backend/api.go index cb9e434c7..1c299c55a 100644 --- a/consensus/istanbul/backend/api.go +++ b/consensus/istanbul/backend/api.go @@ -48,6 +48,7 @@ type API struct { } // GetSnapshot retrieves the state snapshot at a given block. +// TODO-kaia-valset: consider deprecation of GetSnapshot func (api *API) GetSnapshot(number *rpc.BlockNumber) (*Snapshot, error) { // Retrieve the requested block number (or current if none requested) header, err := headerByRpcNumber(api.chain, number) @@ -58,6 +59,7 @@ func (api *API) GetSnapshot(number *rpc.BlockNumber) (*Snapshot, error) { } // GetSnapshotAtHash retrieves the state snapshot at a given block. +// TODO-kaia-valset: consider deprecation of GetSnapshotAtHash func (api *API) GetSnapshotAtHash(hash common.Hash) (*Snapshot, error) { header := api.chain.GetHeaderByHash(hash) if header == nil { @@ -66,54 +68,18 @@ func (api *API) GetSnapshotAtHash(hash common.Hash) (*Snapshot, error) { return checkStatesAndGetSnapshot(api.chain, api.istanbul, header.Number.Uint64(), header.Hash()) } -// GetValidators retrieves the list of authorized validators with the given block number. +// GetValidators retrieves the list of qualified validators with the given block number. func (api *API) GetValidators(number *rpc.BlockNumber) ([]common.Address, error) { header, err := headerByRpcNumber(api.chain, number) if err != nil { return nil, err } - blockNumber := header.Number.Uint64() - if blockNumber == 0 { - // The committee of genesis block can not be calculated because it requires a previous block. - istanbulExtra, err := types.ExtractIstanbulExtra(header) - if err != nil { - return nil, errExtractIstanbulExtra - } - return istanbulExtra.Validators, nil - } - - snap, err := checkStatesAndGetSnapshot(api.chain, api.istanbul, header.Number.Uint64()-1, header.ParentHash) + valSet, err := api.istanbul.GetValidatorSet(header.Number.Uint64()) if err != nil { - logger.Error("Failed to get snapshot.", "blockNum", blockNumber, "err", err) return nil, err } - return snap.validators(), nil -} - -// GetValidatorsAtHash retrieves the list of authorized validators with the given block hash. -func (api *API) GetValidatorsAtHash(hash common.Hash) ([]common.Address, error) { - header := api.chain.GetHeaderByHash(hash) - if header == nil { - return nil, errUnknownBlock - } - - blockNumber := header.Number.Uint64() - if blockNumber == 0 { - // The committee of genesis block can not be calculated because it requires a previous block. - istanbulExtra, err := types.ExtractIstanbulExtra(header) - if err != nil { - return nil, errExtractIstanbulExtra - } - return istanbulExtra.Validators, nil - } - - snap, err := checkStatesAndGetSnapshot(api.chain, api.istanbul, header.Number.Uint64()-1, header.ParentHash) - if err != nil { - logger.Error("Failed to get snapshot.", "blockNum", blockNumber, "err", err) - return nil, errInternalError - } - return snap.validators(), nil + return valSet.Qualified().BytesCmpSortedList(), nil } // GetDemotedValidators retrieves the list of authorized, but demoted validators with the given block number. @@ -123,47 +89,31 @@ func (api *API) GetDemotedValidators(number *rpc.BlockNumber) ([]common.Address, return nil, err } - blockNumber := header.Number.Uint64() - if blockNumber == 0 { - snap, err := checkStatesAndGetSnapshot(api.chain, api.istanbul, header.Number.Uint64(), header.Hash()) - if err != nil { - logger.Error("Failed to get snapshot.", "blockNum", blockNumber, "err", err) - return nil, err - } - return snap.demotedValidators(), nil - } else { - snap, err := checkStatesAndGetSnapshot(api.chain, api.istanbul, header.Number.Uint64()-1, header.ParentHash) - if err != nil { - logger.Error("Failed to get snapshot.", "blockNum", blockNumber, "err", err) - return nil, err - } - return snap.demotedValidators(), nil + valSet, err := api.istanbul.GetValidatorSet(header.Number.Uint64()) + if err != nil { + return nil, err } + return valSet.Demoted().BytesCmpSortedList(), nil } -// GetDemotedValidatorsAtHash retrieves the list of authorized, but demoted validators with the given block hash. -func (api *API) GetDemotedValidatorsAtHash(hash common.Hash) ([]common.Address, error) { +// GetValidatorsAtHash retrieves the list of authorized validators with the given block hash. +func (api *API) GetValidatorsAtHash(hash common.Hash) ([]common.Address, error) { header := api.chain.GetHeaderByHash(hash) if header == nil { return nil, errUnknownBlock } + rpcBlockNumber := rpc.BlockNumber(header.Number.Uint64()) + return api.GetValidators(&rpcBlockNumber) +} - blockNumber := header.Number.Uint64() - if blockNumber == 0 { - snap, err := checkStatesAndGetSnapshot(api.chain, api.istanbul, header.Number.Uint64(), header.Hash()) - if err != nil { - logger.Error("Failed to get snapshot.", "blockNum", blockNumber, "err", err) - return nil, err - } - return snap.demotedValidators(), nil - } else { - snap, err := checkStatesAndGetSnapshot(api.chain, api.istanbul, header.Number.Uint64()-1, header.ParentHash) - if err != nil { - logger.Error("Failed to get snapshot.", "blockNum", blockNumber, "err", err) - return nil, err - } - return snap.demotedValidators(), nil +// GetDemotedValidatorsAtHash retrieves the list of demoted validators with the given block hash. +func (api *API) GetDemotedValidatorsAtHash(hash common.Hash) ([]common.Address, error) { + header := api.chain.GetHeaderByHash(hash) + if header != nil { + return nil, errUnknownBlock } + rpcBlockNumber := rpc.BlockNumber(header.Number.Uint64()) + return api.GetDemotedValidators(&rpcBlockNumber) } // Candidates returns the current candidates the node tries to uphold and vote on. @@ -210,8 +160,6 @@ var ( errStartLargerThanEnd = errors.New("start should be smaller than end") errRequestedBlocksTooLarge = errors.New("number of requested blocks should be smaller than 50") errRangeNil = errors.New("range values should not be nil") - errExtractIstanbulExtra = errors.New("extract Istanbul Extra from block header of the given block number") - errNoBlockExist = errors.New("block with the given block number is not existed") errNoBlockNumber = errors.New("block number is not assigned") ) @@ -222,32 +170,19 @@ func (api *APIExtension) GetCouncil(number *rpc.BlockNumber) ([]common.Address, return nil, err } - blockNumber := header.Number.Uint64() - if blockNumber == 0 { - // The committee of genesis block can not be calculated because it requires a previous block. - istanbulExtra, err := types.ExtractIstanbulExtra(header) - if err != nil { - return nil, errExtractIstanbulExtra - } - return istanbulExtra.Validators, nil - } - - snap, err := checkStatesAndGetSnapshot(api.chain, api.istanbul, blockNumber-1, header.ParentHash) + valSet, err := api.istanbul.GetValidatorSet(header.Number.Uint64()) if err != nil { - logger.Error("Failed to get snapshot.", "blockNum", blockNumber, "err", err) return nil, err } - - return append(snap.validators(), snap.demotedValidators()...), nil + return valSet.Council(), err } func (api *APIExtension) GetCouncilSize(number *rpc.BlockNumber) (int, error) { council, err := api.GetCouncil(number) - if err == nil { - return len(council), nil - } else { + if err != nil { return -1, err } + return len(council), nil } func (api *APIExtension) GetCommittee(number *rpc.BlockNumber) ([]common.Address, error) { @@ -255,52 +190,19 @@ func (api *APIExtension) GetCommittee(number *rpc.BlockNumber) ([]common.Address if err != nil { return nil, err } - - blockNumber := header.Number.Uint64() - if blockNumber == 0 { - // The committee of genesis block can not be calculated because it requires a previous block. - istanbulExtra, err := types.ExtractIstanbulExtra(header) - if err != nil { - return nil, errExtractIstanbulExtra - } - return istanbulExtra.Validators, nil - } - - snap, err := checkStatesAndGetSnapshot(api.chain, api.istanbul, blockNumber-1, header.ParentHash) - if err != nil { - return nil, err - } - round := header.Round() - view := &istanbul.View{ - Sequence: new(big.Int).SetUint64(blockNumber), - Round: new(big.Int).SetUint64(uint64(round)), - } - - // get the proposer of this block. - proposer, err := ecrecover(header) + roundState, err := api.istanbul.GetCommitteeState(header.Number.Uint64()) if err != nil { return nil, err } - - parentHash := header.ParentHash - - // get the committee list of this block at the view (blockNumber, round) - committee := snap.ValSet.SubListWithProposer(parentHash, proposer, view) - addresses := make([]common.Address, len(committee)) - for i, v := range committee { - addresses[i] = v.Address() - } - - return addresses, nil + return roundState.Committee(), nil } func (api *APIExtension) GetCommitteeSize(number *rpc.BlockNumber) (int, error) { committee, err := api.GetCommittee(number) - if err == nil { - return len(committee), nil - } else { + if err != nil { return -1, err } + return len(committee), nil } func (api *APIExtension) makeRPCBlockOutput(b *types.Block, diff --git a/consensus/istanbul/backend/backend.go b/consensus/istanbul/backend/backend.go index 606449c0d..ee7b46cd5 100644 --- a/consensus/istanbul/backend/backend.go +++ b/consensus/istanbul/backend/backend.go @@ -44,6 +44,7 @@ import ( "github.com/kaiachain/kaia/kaiax" "github.com/kaiachain/kaia/kaiax/gov" "github.com/kaiachain/kaia/kaiax/staking" + "github.com/kaiachain/kaia/kaiax/valset" "github.com/kaiachain/kaia/log" "github.com/kaiachain/kaia/storage/database" ) @@ -113,6 +114,7 @@ type backend struct { db database.DBManager chain consensus.ChainReader stakingModule staking.StakingModule + valsetModule valset.ValsetModule consensusModules []kaiax.ConsensusModule currentBlock func() *types.Block hasBadBlock func(hash common.Hash) bool @@ -170,13 +172,8 @@ func (sb *backend) Address() common.Address { return sb.address } -// Validators implements istanbul.Backend.Validators -func (sb *backend) Validators(proposal istanbul.Proposal) istanbul.ValidatorSet { - return sb.getValidators(proposal.Number().Uint64(), proposal.Hash()) -} - // Broadcast implements istanbul.Backend.Broadcast -func (sb *backend) Broadcast(prevHash common.Hash, valSet istanbul.ValidatorSet, payload []byte) error { +func (sb *backend) Broadcast(prevHash common.Hash, payload []byte) error { // send to others // TODO Check gossip again in event handle // sb.Gossip(valSet, payload) @@ -190,7 +187,7 @@ func (sb *backend) Broadcast(prevHash common.Hash, valSet istanbul.ValidatorSet, } // Broadcast implements istanbul.Backend.Gossip -func (sb *backend) Gossip(valSet istanbul.ValidatorSet, payload []byte) error { +func (sb *backend) Gossip(payload []byte) error { hash := istanbul.RLPHash(payload) sb.knownMessages.Add(hash, true) @@ -224,50 +221,44 @@ func (sb *backend) Gossip(valSet istanbul.ValidatorSet, payload []byte) error { return nil } -// checkInSubList checks if the node is in a sublist -func (sb *backend) checkInSubList(prevHash common.Hash, valSet istanbul.ValidatorSet) bool { - return valSet.CheckInSubList(prevHash, sb.currentView.Load().(*istanbul.View), sb.Address()) -} - // getTargetReceivers returns a map of nodes which need to receive a message -func (sb *backend) getTargetReceivers(prevHash common.Hash, valSet istanbul.ValidatorSet) map[common.Address]bool { - targets := make(map[common.Address]bool) - +func (sb *backend) getTargetReceivers() map[common.Address]bool { cv, ok := sb.currentView.Load().(*istanbul.View) if !ok { logger.Error("Failed to assert type from sb.currentView!!", "cv", cv) return nil } - view := &istanbul.View{ - Round: big.NewInt(cv.Round.Int64()), - Sequence: big.NewInt(cv.Sequence.Int64()), - } - proposer := valSet.GetProposer() + // calculates a map of target nodes which need to receive a message + // committee[currentView].Union(committee[newView]) => targets + targets := make(map[common.Address]bool) for i := 0; i < 2; i++ { - committee := valSet.SubListWithProposer(prevHash, proposer.Address(), view) - for _, val := range committee { - if val.Address() != sb.Address() { - targets[val.Address()] = true + roundCommittee, err := sb.GetCommitteeStateByRound(cv.Sequence.Uint64(), cv.Round.Uint64()+uint64(i)) + if err != nil { + return nil + } + // i == 0: get current round's committee. additionally, check if the node is in the current view's committee + if i == 0 && !roundCommittee.IsCommitteeMember(sb.Address()) { + return nil + } + for _, val := range roundCommittee.Committee() { + if val != sb.Address() { + targets[val] = true } } - view.Round = view.Round.Add(view.Round, common.Big1) - proposer = valSet.Selector(valSet, common.Address{}, view.Round.Uint64()) } return targets } // GossipSubPeer implements istanbul.Backend.Gossip -func (sb *backend) GossipSubPeer(prevHash common.Hash, valSet istanbul.ValidatorSet, payload []byte) map[common.Address]bool { - if !sb.checkInSubList(prevHash, valSet) { - return nil +func (sb *backend) GossipSubPeer(prevHash common.Hash, payload []byte) { + targets := sb.getTargetReceivers() + if targets == nil { + return } - hash := istanbul.RLPHash(payload) sb.knownMessages.Add(hash, true) - targets := sb.getTargetReceivers(prevHash, valSet) - if sb.broadcaster != nil && len(targets) > 0 { ps := sb.broadcaster.FindCNPeers(targets) for addr, p := range ps { @@ -294,7 +285,7 @@ func (sb *backend) GossipSubPeer(prevHash common.Hash, valSet istanbul.Validator go p.Send(IstanbulMsg, cmsg) } } - return targets + return } // Commit implements istanbul.Backend.Commit @@ -396,15 +387,6 @@ func (sb *backend) HasPropsal(hash common.Hash, number *big.Int) bool { return sb.chain.GetHeader(hash, number.Uint64()) != nil } -// GetProposer implements istanbul.Backend.GetProposer -func (sb *backend) GetProposer(number uint64) common.Address { - if h := sb.chain.GetHeaderByNumber(number); h != nil { - a, _ := sb.Author(h) - return a - } - return common.Address{} -} - // ParentValidators implements istanbul.Backend.GetParentValidators func (sb *backend) ParentValidators(proposal istanbul.Proposal) istanbul.ValidatorSet { if block, ok := proposal.(*types.Block); ok { diff --git a/consensus/istanbul/backend/backend_test.go b/consensus/istanbul/backend/backend_test.go index f37027912..b8316f3af 100644 --- a/consensus/istanbul/backend/backend_test.go +++ b/consensus/istanbul/backend/backend_test.go @@ -25,413 +25,26 @@ package backend import ( "bytes" "crypto/ecdsa" - "fmt" "math/big" - "sort" "strings" "testing" "time" - "github.com/golang/mock/gomock" "github.com/kaiachain/kaia/blockchain/types" "github.com/kaiachain/kaia/common" "github.com/kaiachain/kaia/consensus/istanbul" - "github.com/kaiachain/kaia/consensus/istanbul/validator" "github.com/kaiachain/kaia/crypto" "github.com/kaiachain/kaia/governance" - "github.com/kaiachain/kaia/kaiax/staking/mock" + "github.com/kaiachain/kaia/kaiax/valset" "github.com/kaiachain/kaia/params" "github.com/kaiachain/kaia/storage/database" + "github.com/stretchr/testify/assert" ) var ( - testSigningData = []byte("dummy data") - // testing node's private key - PRIVKEY = "ce7671a2880493dfb8d04218707a16b1532dfcac97f0289d770a919d5ff7b068" - // Max blockNum - maxBlockNum = int64(100) - committeeBlocks = map[Pair]bool{ - {Sequence: 0, Round: 0}: false, - {Sequence: 0, Round: 1}: false, - {Sequence: 0, Round: 2}: false, - {Sequence: 0, Round: 3}: false, - {Sequence: 0, Round: 4}: false, - {Sequence: 0, Round: 5}: false, - {Sequence: 0, Round: 6}: false, - {Sequence: 0, Round: 7}: false, - {Sequence: 0, Round: 8}: false, - {Sequence: 0, Round: 9}: false, - {Sequence: 0, Round: 10}: false, - {Sequence: 0, Round: 11}: false, - {Sequence: 0, Round: 12}: false, - {Sequence: 0, Round: 13}: false, - {Sequence: 0, Round: 14}: false, - {Sequence: 5, Round: 4}: false, - {Sequence: 6, Round: 6}: false, - {Sequence: 6, Round: 9}: false, - {Sequence: 7, Round: 11}: false, - {Sequence: 7, Round: 12}: false, - {Sequence: 7, Round: 14}: false, - {Sequence: 8, Round: 5}: false, - {Sequence: 8, Round: 13}: false, - {Sequence: 8, Round: 14}: false, - {Sequence: 9, Round: 0}: false, - {Sequence: 9, Round: 10}: false, - {Sequence: 9, Round: 11}: false, - {Sequence: 9, Round: 12}: false, - {Sequence: 9, Round: 13}: false, - {Sequence: 9, Round: 14}: false, - {Sequence: 10, Round: 1}: false, - {Sequence: 10, Round: 8}: false, - {Sequence: 10, Round: 11}: false, - {Sequence: 10, Round: 12}: false, - {Sequence: 10, Round: 14}: false, - {Sequence: 11, Round: 0}: false, - {Sequence: 11, Round: 7}: false, - {Sequence: 11, Round: 8}: false, - {Sequence: 11, Round: 10}: false, - {Sequence: 11, Round: 11}: false, - {Sequence: 12, Round: 0}: false, - {Sequence: 12, Round: 6}: false, - {Sequence: 12, Round: 8}: false, - {Sequence: 12, Round: 9}: false, - {Sequence: 12, Round: 10}: false, - {Sequence: 12, Round: 11}: false, - {Sequence: 12, Round: 13}: false, - {Sequence: 13, Round: 8}: false, - {Sequence: 13, Round: 9}: false, - {Sequence: 13, Round: 12}: false, - {Sequence: 13, Round: 13}: false, - {Sequence: 14, Round: 0}: false, - {Sequence: 14, Round: 5}: false, - {Sequence: 14, Round: 7}: false, - {Sequence: 14, Round: 8}: false, - {Sequence: 14, Round: 10}: false, - {Sequence: 14, Round: 14}: false, - {Sequence: 15, Round: 5}: false, - {Sequence: 15, Round: 6}: false, - {Sequence: 15, Round: 7}: false, - {Sequence: 15, Round: 11}: false, - {Sequence: 16, Round: 5}: false, - {Sequence: 16, Round: 6}: false, - {Sequence: 16, Round: 7}: false, - {Sequence: 17, Round: 4}: false, - {Sequence: 17, Round: 5}: false, - {Sequence: 17, Round: 7}: false, - {Sequence: 17, Round: 9}: false, - {Sequence: 18, Round: 1}: false, - {Sequence: 18, Round: 3}: false, - {Sequence: 18, Round: 4}: false, - {Sequence: 18, Round: 7}: false, - {Sequence: 18, Round: 9}: false, - {Sequence: 18, Round: 10}: false, - {Sequence: 19, Round: 2}: false, - {Sequence: 19, Round: 3}: false, - {Sequence: 19, Round: 5}: false, - {Sequence: 19, Round: 7}: false, - {Sequence: 19, Round: 10}: false, - {Sequence: 19, Round: 13}: false, - {Sequence: 20, Round: 1}: false, - {Sequence: 20, Round: 2}: false, - {Sequence: 20, Round: 3}: false, - {Sequence: 20, Round: 11}: false, - {Sequence: 21, Round: 0}: false, - {Sequence: 21, Round: 1}: false, - {Sequence: 21, Round: 2}: false, - {Sequence: 21, Round: 7}: false, - {Sequence: 21, Round: 11}: false, - {Sequence: 21, Round: 12}: false, - {Sequence: 22, Round: 0}: false, - {Sequence: 22, Round: 3}: false, - {Sequence: 22, Round: 6}: false, - {Sequence: 23, Round: 2}: false, - {Sequence: 23, Round: 10}: false, - {Sequence: 23, Round: 11}: false, - {Sequence: 24, Round: 0}: false, - {Sequence: 24, Round: 3}: false, - {Sequence: 24, Round: 4}: false, - {Sequence: 24, Round: 8}: false, - {Sequence: 25, Round: 3}: false, - {Sequence: 25, Round: 4}: false, - {Sequence: 25, Round: 5}: false, - {Sequence: 25, Round: 14}: false, - {Sequence: 26, Round: 11}: false, - {Sequence: 26, Round: 13}: false, - {Sequence: 27, Round: 5}: false, - {Sequence: 27, Round: 6}: false, - {Sequence: 27, Round: 9}: false, - {Sequence: 27, Round: 14}: false, - {Sequence: 28, Round: 2}: false, - {Sequence: 28, Round: 3}: false, - {Sequence: 28, Round: 5}: false, - {Sequence: 28, Round: 9}: false, - {Sequence: 28, Round: 13}: false, - {Sequence: 29, Round: 7}: false, - {Sequence: 29, Round: 11}: false, - {Sequence: 30, Round: 3}: false, - {Sequence: 30, Round: 6}: false, - {Sequence: 30, Round: 7}: false, - {Sequence: 30, Round: 10}: false, - {Sequence: 30, Round: 12}: false, - {Sequence: 30, Round: 13}: false, - {Sequence: 31, Round: 3}: false, - {Sequence: 31, Round: 9}: false, - {Sequence: 31, Round: 12}: false, - {Sequence: 32, Round: 0}: false, - {Sequence: 32, Round: 1}: false, - {Sequence: 32, Round: 5}: false, - {Sequence: 32, Round: 12}: false, - {Sequence: 32, Round: 13}: false, - {Sequence: 33, Round: 0}: false, - {Sequence: 33, Round: 2}: false, - {Sequence: 33, Round: 3}: false, - {Sequence: 33, Round: 8}: false, - {Sequence: 33, Round: 14}: false, - {Sequence: 34, Round: 0}: false, - {Sequence: 34, Round: 3}: false, - {Sequence: 34, Round: 6}: false, - {Sequence: 34, Round: 7}: false, - {Sequence: 34, Round: 8}: false, - {Sequence: 34, Round: 10}: false, - {Sequence: 34, Round: 11}: false, - {Sequence: 35, Round: 0}: false, - {Sequence: 35, Round: 5}: false, - {Sequence: 35, Round: 6}: false, - {Sequence: 35, Round: 9}: false, - {Sequence: 35, Round: 14}: false, - {Sequence: 36, Round: 1}: false, - {Sequence: 37, Round: 0}: false, - {Sequence: 37, Round: 1}: false, - {Sequence: 37, Round: 5}: false, - {Sequence: 37, Round: 8}: false, - {Sequence: 37, Round: 12}: false, - {Sequence: 37, Round: 14}: false, - {Sequence: 38, Round: 0}: false, - {Sequence: 38, Round: 14}: false, - {Sequence: 39, Round: 0}: false, - {Sequence: 39, Round: 1}: false, - {Sequence: 39, Round: 3}: false, - {Sequence: 39, Round: 10}: false, - {Sequence: 39, Round: 13}: false, - {Sequence: 39, Round: 14}: false, - {Sequence: 40, Round: 3}: false, - {Sequence: 40, Round: 10}: false, - {Sequence: 40, Round: 12}: false, - {Sequence: 41, Round: 6}: false, - {Sequence: 41, Round: 8}: false, - {Sequence: 41, Round: 11}: false, - {Sequence: 42, Round: 3}: false, - {Sequence: 42, Round: 5}: false, - {Sequence: 42, Round: 6}: false, - {Sequence: 42, Round: 11}: false, - {Sequence: 42, Round: 13}: false, - {Sequence: 43, Round: 0}: false, - {Sequence: 43, Round: 3}: false, - {Sequence: 43, Round: 6}: false, - {Sequence: 43, Round: 7}: false, - {Sequence: 43, Round: 9}: false, - {Sequence: 44, Round: 3}: false, - {Sequence: 44, Round: 4}: false, - {Sequence: 44, Round: 7}: false, - {Sequence: 44, Round: 13}: false, - {Sequence: 44, Round: 14}: false, - {Sequence: 45, Round: 2}: false, - {Sequence: 45, Round: 6}: false, - {Sequence: 45, Round: 12}: false, - {Sequence: 45, Round: 13}: false, - {Sequence: 46, Round: 3}: false, - {Sequence: 46, Round: 4}: false, - {Sequence: 46, Round: 7}: false, - {Sequence: 46, Round: 8}: false, - {Sequence: 46, Round: 10}: false, - {Sequence: 46, Round: 12}: false, - {Sequence: 47, Round: 1}: false, - {Sequence: 47, Round: 3}: false, - {Sequence: 47, Round: 4}: false, - {Sequence: 47, Round: 10}: false, - {Sequence: 47, Round: 12}: false, - {Sequence: 49, Round: 0}: false, - {Sequence: 49, Round: 8}: false, - {Sequence: 49, Round: 10}: false, - {Sequence: 49, Round: 14}: false, - {Sequence: 50, Round: 3}: false, - {Sequence: 50, Round: 4}: false, - {Sequence: 50, Round: 10}: false, - {Sequence: 50, Round: 11}: false, - {Sequence: 50, Round: 14}: false, - {Sequence: 52, Round: 1}: false, - {Sequence: 52, Round: 3}: false, - {Sequence: 52, Round: 7}: false, - {Sequence: 52, Round: 11}: false, - {Sequence: 53, Round: 4}: false, - {Sequence: 53, Round: 7}: false, - {Sequence: 54, Round: 4}: false, - {Sequence: 54, Round: 10}: false, - {Sequence: 54, Round: 12}: false, - {Sequence: 55, Round: 2}: false, - {Sequence: 55, Round: 12}: false, - {Sequence: 56, Round: 2}: false, - {Sequence: 56, Round: 9}: false, - {Sequence: 56, Round: 12}: false, - {Sequence: 56, Round: 14}: false, - {Sequence: 57, Round: 7}: false, - {Sequence: 57, Round: 13}: false, - {Sequence: 58, Round: 1}: false, - {Sequence: 58, Round: 4}: false, - {Sequence: 58, Round: 7}: false, - {Sequence: 58, Round: 12}: false, - {Sequence: 59, Round: 5}: false, - {Sequence: 59, Round: 10}: false, - {Sequence: 59, Round: 13}: false, - {Sequence: 60, Round: 2}: false, - {Sequence: 60, Round: 6}: false, - {Sequence: 61, Round: 2}: false, - {Sequence: 61, Round: 3}: false, - {Sequence: 62, Round: 1}: false, - {Sequence: 62, Round: 12}: false, - {Sequence: 62, Round: 13}: false, - {Sequence: 63, Round: 1}: false, - {Sequence: 63, Round: 2}: false, - {Sequence: 63, Round: 5}: false, - {Sequence: 63, Round: 7}: false, - {Sequence: 63, Round: 9}: false, - {Sequence: 63, Round: 11}: false, - {Sequence: 64, Round: 4}: false, - {Sequence: 64, Round: 7}: false, - {Sequence: 64, Round: 9}: false, - {Sequence: 65, Round: 6}: false, - {Sequence: 65, Round: 11}: false, - {Sequence: 65, Round: 13}: false, - {Sequence: 65, Round: 14}: false, - {Sequence: 66, Round: 3}: false, - {Sequence: 66, Round: 4}: false, - {Sequence: 66, Round: 11}: false, - {Sequence: 67, Round: 5}: false, - {Sequence: 67, Round: 6}: false, - {Sequence: 67, Round: 10}: false, - {Sequence: 67, Round: 11}: false, - {Sequence: 68, Round: 9}: false, - {Sequence: 68, Round: 11}: false, - {Sequence: 68, Round: 14}: false, - {Sequence: 69, Round: 2}: false, - {Sequence: 69, Round: 5}: false, - {Sequence: 69, Round: 6}: false, - {Sequence: 69, Round: 10}: false, - {Sequence: 69, Round: 12}: false, - {Sequence: 69, Round: 14}: false, - {Sequence: 70, Round: 0}: false, - {Sequence: 70, Round: 4}: false, - {Sequence: 70, Round: 12}: false, - {Sequence: 71, Round: 0}: false, - {Sequence: 71, Round: 5}: false, - {Sequence: 71, Round: 10}: false, - {Sequence: 72, Round: 2}: false, - {Sequence: 72, Round: 8}: false, - {Sequence: 72, Round: 9}: false, - {Sequence: 73, Round: 5}: false, - {Sequence: 73, Round: 8}: false, - {Sequence: 73, Round: 10}: false, - {Sequence: 73, Round: 12}: false, - {Sequence: 73, Round: 14}: false, - {Sequence: 74, Round: 6}: false, - {Sequence: 74, Round: 10}: false, - {Sequence: 74, Round: 12}: false, - {Sequence: 75, Round: 2}: false, - {Sequence: 75, Round: 5}: false, - {Sequence: 75, Round: 6}: false, - {Sequence: 75, Round: 7}: false, - {Sequence: 76, Round: 7}: false, - {Sequence: 77, Round: 0}: false, - {Sequence: 77, Round: 7}: false, - {Sequence: 78, Round: 0}: false, - {Sequence: 78, Round: 2}: false, - {Sequence: 78, Round: 5}: false, - {Sequence: 79, Round: 0}: false, - {Sequence: 79, Round: 4}: false, - {Sequence: 79, Round: 11}: false, - {Sequence: 79, Round: 12}: false, - {Sequence: 80, Round: 2}: false, - {Sequence: 80, Round: 4}: false, - {Sequence: 80, Round: 5}: false, - {Sequence: 80, Round: 7}: false, - {Sequence: 80, Round: 8}: false, - {Sequence: 80, Round: 10}: false, - {Sequence: 80, Round: 14}: false, - {Sequence: 81, Round: 1}: false, - {Sequence: 81, Round: 9}: false, - {Sequence: 81, Round: 11}: false, - {Sequence: 81, Round: 14}: false, - {Sequence: 82, Round: 0}: false, - {Sequence: 82, Round: 11}: false, - {Sequence: 82, Round: 13}: false, - {Sequence: 82, Round: 14}: false, - {Sequence: 83, Round: 0}: false, - {Sequence: 83, Round: 5}: false, - {Sequence: 83, Round: 6}: false, - {Sequence: 83, Round: 8}: false, - {Sequence: 83, Round: 9}: false, - {Sequence: 83, Round: 12}: false, - {Sequence: 84, Round: 2}: false, - {Sequence: 84, Round: 11}: false, - {Sequence: 85, Round: 4}: false, - {Sequence: 85, Round: 7}: false, - {Sequence: 85, Round: 8}: false, - {Sequence: 86, Round: 5}: false, - {Sequence: 86, Round: 9}: false, - {Sequence: 87, Round: 1}: false, - {Sequence: 87, Round: 5}: false, - {Sequence: 87, Round: 6}: false, - {Sequence: 87, Round: 7}: false, - {Sequence: 87, Round: 9}: false, - {Sequence: 87, Round: 10}: false, - {Sequence: 87, Round: 12}: false, - {Sequence: 87, Round: 14}: false, - {Sequence: 88, Round: 8}: false, - {Sequence: 89, Round: 0}: false, - {Sequence: 89, Round: 7}: false, - {Sequence: 90, Round: 3}: false, - {Sequence: 90, Round: 4}: false, - {Sequence: 90, Round: 9}: false, - {Sequence: 90, Round: 10}: false, - {Sequence: 90, Round: 11}: false, - {Sequence: 91, Round: 10}: false, - {Sequence: 91, Round: 12}: false, - {Sequence: 91, Round: 13}: false, - {Sequence: 92, Round: 0}: false, - {Sequence: 92, Round: 1}: false, - {Sequence: 92, Round: 2}: false, - {Sequence: 92, Round: 5}: false, - {Sequence: 92, Round: 10}: false, - {Sequence: 92, Round: 14}: false, - {Sequence: 93, Round: 0}: false, - {Sequence: 93, Round: 4}: false, - {Sequence: 93, Round: 5}: false, - {Sequence: 93, Round: 8}: false, - {Sequence: 93, Round: 10}: false, - {Sequence: 93, Round: 12}: false, - {Sequence: 93, Round: 14}: false, - {Sequence: 94, Round: 2}: false, - {Sequence: 94, Round: 6}: false, - {Sequence: 94, Round: 7}: false, - {Sequence: 94, Round: 10}: false, - {Sequence: 95, Round: 8}: false, - {Sequence: 95, Round: 9}: false, - {Sequence: 95, Round: 10}: false, - {Sequence: 95, Round: 13}: false, - {Sequence: 96, Round: 1}: false, - {Sequence: 96, Round: 7}: false, - {Sequence: 96, Round: 8}: false, - {Sequence: 96, Round: 10}: false, - {Sequence: 96, Round: 12}: false, - {Sequence: 96, Round: 14}: false, - {Sequence: 97, Round: 4}: false, - {Sequence: 97, Round: 5}: false, - {Sequence: 97, Round: 13}: false, - {Sequence: 98, Round: 10}: false, - {Sequence: 98, Round: 12}: false, - {Sequence: 99, Round: 4}: false, - {Sequence: 99, Round: 14}: false, - } + testCommitteeSize = uint64(21) + testSigningData = []byte("dummy data") + maxBlockNum = int64(100) ) type keys []*ecdsa.PrivateKey @@ -448,229 +61,6 @@ func (slice keys) Swap(i, j int) { slice[i], slice[j] = slice[j], slice[i] } -type Pair struct { - Sequence int64 - Round int64 -} - -func getTestCouncil() []common.Address { - return []common.Address{ - common.HexToAddress("0x414790CA82C14A8B975cEBd66098c3dA590bf969"), // Node Address for test - common.HexToAddress("0x604973C51f6389dF2782E018000c3AC1257dee90"), - common.HexToAddress("0x5Ac1689ae5F521B05145C5Cd15a3E8F6ab39Af19"), - common.HexToAddress("0x0688CaC68bbF7c1a0faedA109c668a868BEd855E"), - common.HexToAddress("0xaD227Fd4d8a6f464Fb5A8bcf38533337A02Db4e0"), - common.HexToAddress("0xf2E89C8e9B4C903c046Bb183a3175405fE98A1Db"), - common.HexToAddress("0xc5D9E04E58717A7Dc4757DF98B865A1525187060"), - common.HexToAddress("0x99F215e780e352647A1f83E17Eb91930aDbaf3e2"), - common.HexToAddress("0xf4eBE96E668c5a372C6ad2924C2B9892817c1b61"), - common.HexToAddress("0x0351D00Cf34b6c2891bf91B6130651903BBdE7df"), - common.HexToAddress("0xbc0182AA0516666cec5c8185311d80d02b2Bb1F5"), - common.HexToAddress("0xf8cbeF7eDf33c1437d17aeAC1b2AfC8430a5eAd7"), - common.HexToAddress("0xbEC6E6457aAE091FC905122DeFf1f97532395896"), - common.HexToAddress("0x3c1587F672Cf9C457FC229ccA9Cc0c8e29af88BE"), - common.HexToAddress("0xcE9E07d403Cf3fC3fa6E2F3e0f88e0272Af42EF3"), - common.HexToAddress("0x711EAd71f23e6b84BD536021Bd02B3706B81A3bE"), - common.HexToAddress("0xb84A4985CD5B5b1CE56527b89b3e447820633856"), - common.HexToAddress("0x739E64B2a55626c921114ee8B49cD1b7E5E2bBB0"), - common.HexToAddress("0x0312b2B142855986C5937eb33E0993ecA142Caca"), - common.HexToAddress("0xd3AbD32504eA87409Bbb5190059c077C6a9df879"), - common.HexToAddress("0x0978D638BAc5990c64E52A69C6F33222F16117Ee"), - common.HexToAddress("0x131855B4D54E9AE376E568dd7c9d925AA6eE0545"), - common.HexToAddress("0x0c7f24972D43B1F6dD1286275EC089755b15514D"), - common.HexToAddress("0xc46C39B333C0828820087E7950Ad9F7b30E38373"), - common.HexToAddress("0x5b3c0E461409a671B15fD1D284F87d2CD1994386"), - common.HexToAddress("0xB5EA5afCC5045690Afd2afb62748227e16872efA"), - common.HexToAddress("0x598cC8b6026d666681598ea1d0C85D2b06876277"), - common.HexToAddress("0xD86D0dB9f101600A4ED8043772654C9D076c0616"), - common.HexToAddress("0x82d718F86D04454cF5736215110d0a5d9bEe420D"), - common.HexToAddress("0xE0F03B6915B9e900B258384106644CDab6aAfc24"), - common.HexToAddress("0xCd28661B14DCda6e002408a0D3A35C1448be7f23"), - common.HexToAddress("0x77Fd4877Dda3641588E5362744fc6A5d4AE3b731"), - common.HexToAddress("0xD7E2FEFE5c1C33e91deFf3cAeC3adC8bD3b6afB8"), - common.HexToAddress("0x7A15B06DDd3ff274F686F9f4a69975BF27aBe37b"), - common.HexToAddress("0x34083A2D7b252EA7c4EC1C92A2D9D6F2bE6B347e"), - common.HexToAddress("0x7C73ee31E9b79aEb26B634E46202C46B13160Eba"), - common.HexToAddress("0x16F7D538Afd579068B88384bCa3c3aefb66C0E52"), - common.HexToAddress("0x7584dbFad6664F604C9C0dE3c822961A10E064f9"), - common.HexToAddress("0xE6c82318Da0819880137cCAE993e73d2bC1b8b20"), - common.HexToAddress("0x0699d025c98ce2CB3C96050efFB1450fE64f5C9E"), - common.HexToAddress("0x9b6Bdb3a669A721b3FeE537B5B5914B8D9d7F980"), - common.HexToAddress("0x4CbC62FF9893df8acF8982f0D3948Ec54F8a1d6c"), - common.HexToAddress("0xBE993372712Cb7ff05c2fc7D21F36a75e8033224"), - common.HexToAddress("0x2C4CF83c05B7127a714E1749B3079B016E1a7B8f"), - common.HexToAddress("0xEDE8613a71A914FD3AFC6E219f798f904ffB63e5"), - common.HexToAddress("0x5fB8bc27982B7122ae6Fd4D4ddbA3E69779422B3"), - common.HexToAddress("0x1b95B986EeDa22e31f10c6499Cbc236386Ac6817"), - common.HexToAddress("0xC6B027F0d09348020Bd5e3E6ed692f2F145c6D73"), - common.HexToAddress("0x06Fc6F3032C03f90eA5C0bE7B95AB8973074D9f4"), - common.HexToAddress("0x183ed276Ef12bA138c96B51D86619a5A8De82b3e"), - common.HexToAddress("0xb2AaC4685Bbf98a114f3949Db456545C62AF096c"), - common.HexToAddress("0xD9c041861214F1D7484e924F87c30B7a5e0DA462"), - common.HexToAddress("0xa3255A75799De01f3b93Bf0D9DF34F5d767CeDE0"), - common.HexToAddress("0xFC112865D09332c2122c5b29bb6FccA171A6c41c"), - common.HexToAddress("0xFA58289602bfE7B029b09D74046a36fA80a41F71"), - common.HexToAddress("0x7d60D7c9ae172d5C8A61eDc37e55F6A60E175f14"), - common.HexToAddress("0x2A7D561FFAA1fD6f4Db0eC0eB20799cfdd9AfA37"), - common.HexToAddress("0xaAa5133219d8fdB6Cad565CBEBc9113b818C64b5"), - common.HexToAddress("0x219bFfaB40F95673F3a4eb243C23E0803a343d9E"), - common.HexToAddress("0x6A53aA17DBaAE11a428576501D72fe346cB7B1f7"), - common.HexToAddress("0x4225BE9Eae8309FdE653CFd3d6A8146Bd0389d3b"), - common.HexToAddress("0x63F7e13f7e586480D2AD4901311A66aecfd714F4"), - common.HexToAddress("0xAb571920e680A1350FAb4a8eecF3cf8451FBBD4D"), - common.HexToAddress("0xB12cD0799C6caf355A7cccB188E1C6d5FA6e789c"), - common.HexToAddress("0xFEf0372eb16b77B67C4AC39e8aF045624392F53c"), - common.HexToAddress("0x9Ee226196605D536feed2e3Ac4c87a695c394200"), - common.HexToAddress("0x1e6E55D4485853Bb5b42Ec591e2900ACd9C8a7eA"), - common.HexToAddress("0xdf0C7B415738E5167E5E43563f11FFDF46696FB0"), - common.HexToAddress("0x718a3B4b201849E0B2905EC0A29c5c9323017FC3"), - common.HexToAddress("0xa906b367D2B0E8426C00B622488F6495b333a5c3"), - common.HexToAddress("0x4dBDAb308824dF54225F5a8B11c890B386b39C2C"), - common.HexToAddress("0xA0D13983Daa2604e66F692Cf695d5D04b39958c4"), - common.HexToAddress("0x5bae720C342157325B1D710766326766a41D50F2"), - common.HexToAddress("0x4d1890BdB54dde6656E89e9b1447672759AF18aB"), - common.HexToAddress("0x4e2cFB15010A576B6c2c70C1701E2d2dfF4FB2A7"), - common.HexToAddress("0x8858B61E2A724aEc8542f1B53aE7C1b08Bf823c8"), - common.HexToAddress("0x8849E54A211B6d8D096C150DF33795d6B1cF6ba9"), - common.HexToAddress("0x14193546e619761795973b9De768753c61C5f9CB"), - common.HexToAddress("0xdFa4b5b4f241F4F7D254A840F648B81ca2338F82"), - common.HexToAddress("0xB51aF0332993Dc3cB7211AD42C761F736A9Af82a"), - common.HexToAddress("0x69660E66352f91D19Ac12CEc20ee27aC4346AC3F"), - common.HexToAddress("0xdB0384Ece61E79e99F7944f4651a0b129c383279"), - common.HexToAddress("0x65a13961017181Ba5Be64959527242251aBB21B9"), - common.HexToAddress("0xc96a196e136403f530C9a6642C3181530124cb59"), - common.HexToAddress("0x4788f45D1AF4508A4c9a26dAB839556466f1481b"), - common.HexToAddress("0xc53e50937E481364b6b6926F572ea65b3B790cDA"), - common.HexToAddress("0xF94270fD8a0393202233D5e0163a41cb0A272DEe"), - common.HexToAddress("0xEF2892748176A6345D7EC22924C4A5f6ec563ccc"), - common.HexToAddress("0x6A01Eba6729F2Fa4A570ff94FA4Cf51Dde428c27"), - common.HexToAddress("0x0CD9337A1C5B744B9D99db496706d916c76a3B27"), - common.HexToAddress("0xffE19f7e59eB3bc0a29907D3E8d5397703CAe605"), - common.HexToAddress("0xE74dCd57694FE43D1813595a43AC8c54DEd62Fed"), - common.HexToAddress("0x7E07f18eD745cD216341147c990088f445179a1c"), - common.HexToAddress("0x0eC5d785C5b47C70154cCd93D71cCCB624b00b29"), - common.HexToAddress("0x6D576beB3ec319Ab366aC7455d3D096e9d2De67d"), - common.HexToAddress("0x8D8083125960CB0F4e638dB1a0ee855d438a707E"), - common.HexToAddress("0xCF9b3b510f11285356167396BABe9A4063DAa1dD"), - common.HexToAddress("0x38b6bAb66D8CD7035f4f90d87aF7d41d7958bed7"), - common.HexToAddress("0x8c597E4b0A71571C209293c278099B6D85Cd3290"), - common.HexToAddress("0x39Cdf8a09f5c516951E0FBa684086702912a4810"), - } -} - -func getTestRewards() []common.Address { - return []common.Address{ - common.HexToAddress("0x2A35FE72F847aa0B509e4055883aE90c87558AaD"), - common.HexToAddress("0xF91B8EBa583C7fa603B400BE17fBaB7629568A4a"), - common.HexToAddress("0x240ed27c8bDc9Bb6cA08fa3D239699Fba525d05a"), - common.HexToAddress("0x3B980293396Fb0e827929D573e3e42d2EA902502"), - common.HexToAddress("0x11F3B5a36DBc8d8D289CE41894253C0D513cf777"), - common.HexToAddress("0x7eDD28614052D42430F326A78956859a462f3Cd1"), - common.HexToAddress("0x3e473733b16D88B695fc9E2278Ab854F449Ea017"), - common.HexToAddress("0x873B6D0404b46Ddde4DcfC34380C14f54002aE27"), - common.HexToAddress("0xC85719Ffb158e1Db55D6704B6AE193aa00f5341e"), - common.HexToAddress("0x957A2DB51Ba8BADA57218FA8580a23a9c64B618f"), - common.HexToAddress("0x5195eC4dC0048032a7dA40B861D47d560Bfd9462"), - common.HexToAddress("0x11e3219418e6214c12E3A6271d1B56a68526fB99"), - common.HexToAddress("0x2edB0c9072497A2276248B0ae11D560114b273fC"), - common.HexToAddress("0x26e059665D30Bbe015B3b131175F53903cAa2Df9"), - common.HexToAddress("0x70528eC69e4e710F71c22aEdA6315Fe1750298e3"), - common.HexToAddress("0xFb538Db8C0b71236bF2B7091AdD0576879D2B8D4"), - common.HexToAddress("0x705Ec0595A40D1A3B77360CE9cA138b731b193ac"), - common.HexToAddress("0x55Cede11Bfe34cdB844c87979a35a7369B36Af2D"), - common.HexToAddress("0xCe79d52e0215872C0515767E46D3C29EC6C858B7"), - common.HexToAddress("0xf83cedb170F84517A693e0A111F99Fe5C4FA50B2"), - common.HexToAddress("0xCF4ff5446B6e1Ee4151B5E455B5a6f80a2296AdB"), - common.HexToAddress("0xB5A7a3bFA0FA44a9CcBED4dc7777013Dfc4D43bc"), - common.HexToAddress("0xcBa042B2B44512FbB31Be4a478252Fcc78Bd94fB"), - common.HexToAddress("0xed7a54066A503A93Cd04F572E39e0F02eE0c92af"), - common.HexToAddress("0x1630a3F6fe4Fc3a4eb2212d12306AEF856F874Df"), - common.HexToAddress("0x8fb35e1bB2Cf26355b7906b3747db941Fc48bf0C"), - common.HexToAddress("0x211F9Bf01C553644B22AbA5ca83C2B8F9A07E49B"), - common.HexToAddress("0xf08Cb412aeb59de13a5F56Df43BA75EaA3B2F0dE"), - common.HexToAddress("0xc57047791C184Baf0184bd1e0a5e012532b40A45"), - common.HexToAddress("0x6660E78cE36d96d81e447D51D83ce3930b54effC"), - common.HexToAddress("0x0736f11d0c066C6e76CE40d89AfCa86FA0Ce9AB5"), - common.HexToAddress("0x6881515A8499f871E95a4EC17689a37afB5Bc600"), - common.HexToAddress("0x96b8473f80DB5Fa8056CBb1bCECAe1f25C35733D"), - common.HexToAddress("0xf9D4D5286bbDeffbcf34d342c47b3E6e52Bfcc08"), - common.HexToAddress("0x272a690C3B3136d4d431800486233EECBb2092C5"), - common.HexToAddress("0x98760b1954017DCDAaf466C03d85954E4bF2E431"), - common.HexToAddress("0xbfFFE194Ebf9953C92a206Fb9c71612afBA80D53"), - common.HexToAddress("0xF6dF311801f6B9ec7ce7e5aba31CDf3dC02de133"), - common.HexToAddress("0x675D19896DA6bca2E236F7C3229E1d82993dDB69"), - common.HexToAddress("0x426d004E495DdE95AdCeE5912cdbf90F2692667b"), - common.HexToAddress("0xe4ed55aB59335341A7dfc153f8068984d53bbaa8"), - common.HexToAddress("0xf7399b92de54BC059E957CE9c1baE294aEF1DA08"), - common.HexToAddress("0x41259376048AF3FBf302b8E588fF8003e3b44F06"), - common.HexToAddress("0xb007291DDE0Ca6BDd69aB16902bea67819abc515"), - common.HexToAddress("0x9A04d8d78fd8c536563cdA344b5A04DbF27D67D3"), - common.HexToAddress("0x12465F01b52CAEC4c3C0197308F6dF8E54163d9c"), - common.HexToAddress("0x323cf51E7A511De099d5E39dC3ed8B8DDdB3F038"), - common.HexToAddress("0x36e3E1E34d456975e8C6aFbD7c6F5Fc0F98E0887"), - common.HexToAddress("0x1A1ed8ae1dC926Be272888ed56EA13503fcE5680"), - common.HexToAddress("0x8D5D4eD1678F77b7337BA8042fb8C9E7361cbFF5"), - common.HexToAddress("0xbBbfc9358f931Cd105107d93b5DF97159C933096"), - common.HexToAddress("0xA82FacF50E9722d6fFa30e186A01625CCE03aA5a"), - common.HexToAddress("0xA2b73E026CAFB45af42705f994e884fa1D7c25Ad"), - common.HexToAddress("0x3dEE1382719E04505Df63df4A604CCB705FaC016"), - common.HexToAddress("0xDf41a247f7F7Ed4F191932844F1C1Dd23D9a76fB"), - common.HexToAddress("0x95debC7f1a4B30F1451c95178aCaC6e72539C633"), - common.HexToAddress("0x505594ACc508D855eAAF68CDd516011D53F76a54"), - common.HexToAddress("0xfFE3160f6B5c73952D2A8f55022f827854cdB8C5"), - common.HexToAddress("0x76BaE5bdE095a4a5F256DEa65259d24d454B777d"), - common.HexToAddress("0xfF34f95Cf7815268Bf3e62AbFBc5452606bdB336"), - common.HexToAddress("0x677799E7804c930Dd430470B9F0C6Cd6523Dd487"), - common.HexToAddress("0x7b3D56CFfe4CB5F5EDbDd4b7a41b5eBa848e9348"), - common.HexToAddress("0x9e80C52fC892C916616Fc7235B885047e1165Dfe"), - common.HexToAddress("0x5E100F1FFe080CFD7dCBAEe2Fa1DbE4D81327955"), - common.HexToAddress("0x0C4Cf38D7769243f843DE339f866bB6f770Bc7DD"), - common.HexToAddress("0xf23A77988e0592CDa049fe7aBb46E900a704eE90"), - common.HexToAddress("0xC67a2ff05C5CA95D76525f5E471701813A877fB2"), - common.HexToAddress("0x8bA5d606bEBe3CC4F4C001B68764771584cD9E4f"), - common.HexToAddress("0x82A39CddA3e64b2EcE2E161116AfCc7Bc5aABC7c"), - common.HexToAddress("0x0324216F0bBA982B01B38A0A354783324B1A6a00"), - common.HexToAddress("0x229CdA4Dfc2ED2503aBCfdca39aA1D4d665281a6"), - common.HexToAddress("0x53C65264994dccEFBe1C3C6b4c2C9fCf4fc3458a"), - common.HexToAddress("0x822BB8E5a0650740424B2bBbd3BcdaD2B8e4FB96"), - common.HexToAddress("0xaE939D5C93fB3d8522738bC5D84a69b7d9ec625F"), - common.HexToAddress("0xF32Dc9C078028A2C8a4104ea70615ADB3010B2b0"), - common.HexToAddress("0x2c231E031e9803e35B97578B536eE3739EBa886F"), - common.HexToAddress("0xF6c24F7e7461BA43800E20bdd32c8483eB8aA152"), - common.HexToAddress("0xB3363359C172bdE5a0747444cbC9108e50FEf826"), - common.HexToAddress("0xc6a42020AD8fB61fa3A4c248310625bBdd5cd04c"), - common.HexToAddress("0xbE797039B57007CEa7F4F5961a6AB64dFc076D0c"), - common.HexToAddress("0xe12F6f36D939ff66CBF35b5F67f3D8670dD4228f"), - common.HexToAddress("0x4c9d56C6c06b48511FdbFD4Dd7C82D56bcAC88f3"), - common.HexToAddress("0x1Ab93C25Fd220e8C80ae0dA73bcfC56f969aD8Db"), - common.HexToAddress("0xF25ccD01c83ee36a0361f60B164108ea1E57FE14"), - common.HexToAddress("0x6f7C69666a9E6B34835f505C3025bcd95Aae2600"), - common.HexToAddress("0xc78387B2384Dcef8c7a9b39BD688d2C2776945E2"), - common.HexToAddress("0x4786F93B4e041eBB7F1EcF9172fE4Cf0c16bD88E"), - common.HexToAddress("0x1d64Ea74B2EEB37fB24158A5d12Eb5b61c718f44"), - common.HexToAddress("0xf5E9Fc7Bf47c9b0eE7C8349eBf3F6442fec426d8"), - common.HexToAddress("0x8aA20E60Da8b86717A9785ec255Aceb8E509107f"), - common.HexToAddress("0x74cc084275253fabD14a0ee6264047503057Aa88"), - common.HexToAddress("0xc1AF209ed6fe5ae23ac5ad3f4D189E0E04D925E8"), - common.HexToAddress("0x4725563C44e432013B20d06395130A0d24ad091F"), - common.HexToAddress("0x6aFB4f059BA732d5143f0fAfD5534501Cf57A47C"), - common.HexToAddress("0xCB1c4478cbE37a4B7a1510065802e0B669979cFD"), - common.HexToAddress("0xf7ed2347390b1f742DA9785242dF90BbbC8Af90C"), - common.HexToAddress("0x8BC37790112C66DF94cB05dC5168249a7e6d6717"), - common.HexToAddress("0x9087C8dd3dE26B6DB3a471D7254d78FB1afCbeF2"), - common.HexToAddress("0xcAfaA922f46d0a0FcE13B0f2aA822aF9C4b9a31C"), - common.HexToAddress("0xa21b5d69efDFE56a9C272f7957Bd6d4205a1e6Ff"), - } -} - -func getTestVotingPowers(num int) []uint64 { - vps := make([]uint64, 0, num) - for i := 0; i < num; i++ { - vps = append(vps, 1) - } - return vps -} - func getTestConfig() *params.ChainConfig { config := params.TestChainConfig.Copy() config.Governance = params.GetDefaultGovernanceConfig() @@ -678,132 +68,76 @@ func getTestConfig() *params.ChainConfig { return config } -func Benchmark_getTargetReceivers(b *testing.B) { - _, backend := newBlockChain(1) - defer backend.Stop() - - backend.currentView.Store(&istanbul.View{Sequence: big.NewInt(0), Round: big.NewInt(0)}) - - // Create ValidatorSet - council := getTestCouncil() - rewards := getTestRewards() - valSet := validator.NewWeightedCouncil(council, nil, rewards, getTestVotingPowers(len(council)), nil, istanbul.WeightedRandom, 21, 0, 0, nil) - valSet.SetBlockNum(uint64(1)) - valSet.CalcProposer(valSet.GetProposer().Address(), uint64(1)) - hex := fmt.Sprintf("%015d000000000000000000000000000000000000000000000000000", 1) - prevHash := common.HexToHash(hex) - - for i := 0; i < b.N; i++ { - _ = backend.getTargetReceivers(prevHash, valSet) - } -} - -// Test_GossipSubPeerTargets checks if the gossiping targets are same as council members -func Test_GossipSubPeerTargets(t *testing.T) { - // get testing node's address - key, _ := crypto.HexToECDSA(PRIVKEY) // This key is to be provided to create backend - - _, backend := newBlockChain(1, istanbulCompatibleBlock(big.NewInt(5)), key) - defer backend.Stop() - - // Create ValidatorSet - council := getTestCouncil() - rewards := getTestRewards() - valSet := validator.NewWeightedCouncil(council, nil, rewards, getTestVotingPowers(len(council)), nil, istanbul.WeightedRandom, 21, 0, 0, nil) - valSet.SetBlockNum(uint64(5)) +// TestBackend_GetTargetReceivers checks if the gossiping targets are same as council members +func TestBackend_GetTargetReceivers(t *testing.T) { + stakes := []uint64{5000000, 5000000, 5000000, 5000000} + ctrl, mStaking := makeMockStakingManager(t, stakes, 0) + defer ctrl.Finish() + + var configItems []interface{} + configItems = append(configItems, proposerPolicy(params.WeightedRandom)) + configItems = append(configItems, proposerUpdateInterval(1)) + configItems = append(configItems, epoch(3)) + configItems = append(configItems, governanceMode("single")) + configItems = append(configItems, minimumStake(new(big.Int).SetUint64(4000000))) + configItems = append(configItems, istanbulCompatibleBlock(new(big.Int).SetUint64(5))) + configItems = append(configItems, blockPeriod(0)) // set block period to 0 to prevent creating future block + configItems = append(configItems, mStaking) + + chain, istBackend := newBlockChain(len(stakes), configItems...) + chain.RegisterExecutionModule(istBackend.govModule) + defer istBackend.Stop() // Test for blocks from 0 to maxBlockNum // from 0 to 4: before istanbul hard fork // from 5 to 100: after istanbul hard fork + var previousBlock, currentBlock *types.Block = nil, chain.CurrentBlock() for i := int64(0); i < maxBlockNum; i++ { - // Test for round 0 to round 14 - for round := int64(0); round < 15; round++ { - backend.currentView.Store(&istanbul.View{Sequence: big.NewInt(i), Round: big.NewInt(round)}) - valSet.SetBlockNum(uint64(i)) - valSet.CalcProposer(valSet.GetProposer().Address(), uint64(round)) - - // Use block number as prevHash. In SubList() only left 15 bytes are being used. - hex := fmt.Sprintf("%015d000000000000000000000000000000000000000000000000000", i) - prevHash := common.HexToHash(hex) - - // committees[0]: current committee - // committees[1]: next committee - committees := make([][]istanbul.Validator, 2) - - // Getting the current round's committee - viewCurrent := backend.currentView.Load().(*istanbul.View) - committees[0] = valSet.SubList(prevHash, viewCurrent) - - // Getting the next round's committee - viewCurrent.Round = viewCurrent.Round.Add(viewCurrent.Round, common.Big1) - backend.currentView.Store(viewCurrent) + previousBlock = currentBlock + currentBlock = makeBlockWithSeal(chain, istBackend, previousBlock) + _, err := chain.InsertChain(types.Blocks{currentBlock}) + assert.NoError(t, err) + } - valSet.CalcProposer(valSet.GetProposer().Address(), uint64(round+1)) - committees[1] = valSet.SubList(prevHash, viewCurrent) + for i := int64(0); i < maxBlockNum; i++ { + // Test for round 0 to round 14 + for round := int64(0); round < 14; round++ { + currentCouncilState, err := istBackend.GetCommitteeStateByRound(uint64(i), uint64(round)) + assert.NoError(t, err) + + // skip if the testing node is not in a committee + isInSubList := currentCouncilState.IsCommitteeMember(istBackend.Address()) + if isInSubList == false { + continue + } - // Reduce round by 1 to set round to the current round before calling GossipSubPeer - viewCurrent.Round = viewCurrent.Round.Sub(viewCurrent.Round, common.Big1) - valSet.CalcProposer(valSet.GetProposer().Address(), uint64(round)) - backend.currentView.Store(viewCurrent) + nextCouncilState, err := istBackend.GetCommitteeStateByRound(uint64(i), uint64(round)+1) + assert.NoError(t, err) // Receiving the receiver list of a message - targets := backend.GossipSubPeer(prevHash, valSet, nil) - - // Check if the testing node is in a committee - isInSubList := backend.checkInSubList(prevHash, valSet) - isInCommitteeBlocks := checkInCommitteeBlocks(i, round) - - // Check if the result of checkInSubList is same as expected. It is to detect an unexpected change in SubList logic - if isInSubList != isInCommitteeBlocks { - t.Errorf("Difference in expected data and calculated one. Changed committee selection? HARD FORK may happen!! Sequence: %d, Round: %d", i, round) - } else { - if isInSubList == false { - continue - } + targets := istBackend.getTargetReceivers() + targetAddrs := make(valset.AddressList, 0, len(targets)) + for addr := range targets { + targetAddrs = append(targetAddrs, addr) } // number of message receivers have to be smaller than or equal to the number of the current committee and the next committee - if len(targets) > len(committees[0])+len(committees[1]) { - t.Errorf("Target has too many validators. targets: %d, sum of committees: %d", len(targets), len(committees[0])+len(committees[1])) - } + // committees[0]: current round's committee + // committees[1]: next view's committee + committees := make([]valset.AddressList, 2) + committees[0], committees[1] = currentCouncilState.Committee(), nextCouncilState.Committee() + assert.True(t, len(targets) <= len(committees[0])+len(committees[1])) // Check all nodes in the current and the next round are included in the target list - for n := 0; n < len(committees); n++ { - for _, x := range committees[n] { - if _, ok := targets[x.Address()]; !ok && x.Address() != backend.Address() { - t.Errorf("Block: %d, Round: %d, Committee member %v not found in targets", i, round, x.Address().String()) - } else { - // Mark the target is in the current or in the next committee - targets[x.Address()] = false - } - } - } + assert.Equal(t, 0, len(committees[0].Subtract(targetAddrs).Subtract(valset.AddressList{istBackend.Address()}))) + assert.Equal(t, 0, len(committees[1].Subtract(targetAddrs).Subtract(valset.AddressList{istBackend.Address()}))) // Check if a validator not in the current/next committee is included in target list - for k, v := range targets { - if v == true { - t.Errorf("Block: %d, Round: %d, Validator not in committees included %v", i, round, k.String()) - } - } - } - } - // Check if the testing node is in all committees that it is supposed to be - for k, v := range committeeBlocks { - if !v { - fmt.Printf("The node is missing in committee that it should be included in. Sequence %d, Round %d\n", k.Sequence, k.Round) + assert.Equal(t, 0, len(targetAddrs.Subtract(committees[0]).Subtract(committees[1]))) } } } -func checkInCommitteeBlocks(seq int64, round int64) bool { - v := Pair{seq, round} - if _, ok := committeeBlocks[v]; ok { - committeeBlocks[v] = true - return true - } - return false -} - func newTestBackend() (b *backend) { config := getTestConfig() config.Istanbul.ProposerPolicy = params.WeightedRandom @@ -821,15 +155,17 @@ func newTestBackendWithConfig(chainConfig *params.ChainConfig, blockPeriod uint6 chainConfig.Governance.GoverningNode = crypto.PubkeyToAddress(key.PublicKey) } g := governance.NewMixedEngine(chainConfig, dbm) - istanbulConfig := istanbul.DefaultConfig - istanbulConfig.BlockPeriod = blockPeriod - istanbulConfig.ProposerPolicy = istanbul.ProposerPolicy(chainConfig.Istanbul.ProposerPolicy) - istanbulConfig.Epoch = chainConfig.Istanbul.Epoch - istanbulConfig.SubGroupSize = chainConfig.Istanbul.SubGroupSize + istanbulConfig := &istanbul.Config{ + Epoch: chainConfig.Istanbul.Epoch, + ProposerPolicy: istanbul.ProposerPolicy(chainConfig.Istanbul.ProposerPolicy), + SubGroupSize: chainConfig.Istanbul.SubGroupSize, + BlockPeriod: blockPeriod, + Timeout: 10000, + } backend := New(&BackendOpts{ IstanbulConfig: istanbulConfig, - Rewardbase: getTestRewards()[0], + Rewardbase: common.HexToAddress("0x2A35FE72F847aa0B509e4055883aE90c87558AaD"), PrivateKey: key, DB: dbm, Governance: g, @@ -838,40 +174,23 @@ func newTestBackendWithConfig(chainConfig *params.ChainConfig, blockPeriod uint6 return backend } -func newTestValidatorSet(n int, policy istanbul.ProposerPolicy) (istanbul.ValidatorSet, []*ecdsa.PrivateKey) { - // generate validators - keys := make(keys, n) - addrs := make([]common.Address, n) - for i := 0; i < n; i++ { - privateKey, _ := crypto.GenerateKey() - keys[i] = privateKey - addrs[i] = crypto.PubkeyToAddress(privateKey.PublicKey) - } - vset := validator.NewSet(addrs, policy) - sort.Sort(keys) // Keys need to be sorted by its public key address - return vset, keys -} - func TestSign(t *testing.T) { b := newTestBackend() + defer b.Stop() sig, err := b.Sign(testSigningData) - if err != nil { - t.Errorf("error mismatch: have %v, want nil", err) - } + assert.NoError(t, err) // Check signature recover hashData := crypto.Keccak256([]byte(testSigningData)) pubkey, _ := crypto.Ecrecover(hashData, sig) actualSigner := common.BytesToAddress(crypto.Keccak256(pubkey[1:])[12:]) - - if actualSigner != b.address { - t.Errorf("address mismatch: have %v, want %s", actualSigner.Hex(), b.address.Hex()) - } + assert.Equal(t, b.address, actualSigner) } func TestCheckSignature(t *testing.T) { b := newTestBackend() + defer b.Stop() // testAddr is derived from testPrivateKey. testPrivateKey, _ := crypto.HexToECDSA("bb047e5940b6d83354d9432db7c449ac8fca2248008aaa7271369880f9f11cc1") @@ -880,155 +199,98 @@ func TestCheckSignature(t *testing.T) { hashData := crypto.Keccak256([]byte(testSigningData)) sig, err := crypto.Sign(hashData, testPrivateKey) - if err != nil { - t.Fatalf("unexpected failure: %v", err) - } - - if err := b.CheckSignature(testSigningData, testAddr, sig); err != nil { - t.Errorf("error mismatch: have %v, want nil", err) - } + assert.NoError(t, err) - if err := b.CheckSignature(testSigningData, testInvalidAddr, sig); err != errInvalidSignature { - t.Errorf("error mismatch: have %v, want %v", err, errInvalidSignature) - } + assert.NoError(t, b.CheckSignature(testSigningData, testAddr, sig)) + assert.Equal(t, errInvalidSignature, b.CheckSignature(testSigningData, testInvalidAddr, sig)) } func TestCheckValidatorSignature(t *testing.T) { - vset, keys := newTestValidatorSet(5, istanbul.WeightedRandom) + // generate validators + setNodeKeys(5, nil) + valSet := istanbul.NewBlockValSet(addrs, addrs) // 1. Positive test: sign with validator's key should succeed - hashData := crypto.Keccak256([]byte(testSigningData)) - for i, k := range keys { + hashData := crypto.Keccak256(testSigningData) + for i, k := range nodeKeys { // Sign sig, err := crypto.Sign(hashData, k) - if err != nil { - t.Errorf("error mismatch: have %v, want nil", err) - } + assert.NoError(t, err) // CheckValidatorSignature should succeed - addr, err := istanbul.CheckValidatorSignature(vset, testSigningData, sig) - if err != nil { - t.Errorf("error mismatch: have %v, want nil", err) - } - validator := vset.GetByIndex(uint64(i)) - if addr != validator.Address() { - t.Errorf("validator address mismatch: have %v, want %v", addr, validator.Address()) - } + addr, err := valSet.CheckValidatorSignature(testSigningData, sig) + assert.NoError(t, err) + assert.Equal(t, valSet.Qualified()[uint64(i)], addr) } // 2. Negative test: sign with any key other than validator's key should return error key, err := crypto.GenerateKey() - if err != nil { - t.Errorf("error mismatch: have %v, want nil", err) - } + assert.NoError(t, err) // Sign sig, err := crypto.Sign(hashData, key) - if err != nil { - t.Errorf("error mismatch: have %v, want nil", err) - } + assert.NoError(t, err) // CheckValidatorSignature should return ErrUnauthorizedAddress - addr, err := istanbul.CheckValidatorSignature(vset, testSigningData, sig) - if err != istanbul.ErrUnauthorizedAddress { - t.Errorf("error mismatch: have %v, want %v", err, istanbul.ErrUnauthorizedAddress) - } - emptyAddr := common.Address{} - if addr != emptyAddr { - t.Errorf("address mismatch: have %v, want %v", addr, emptyAddr) - } + addr, err := valSet.CheckValidatorSignature(testSigningData, sig) + assert.Equal(t, istanbul.ErrUnauthorizedAddress, err) + assert.True(t, common.EmptyAddress(addr)) } func TestCommit(t *testing.T) { - backend := newTestBackend() - mockCtrl, mStaking := makeMockStakingManager(t, nil, 0) - backend.RegisterStakingModule(mStaking) - defer mockCtrl.Finish() - commitCh := make(chan *types.Block) + // Case: it's a proposer, so the backend.commit will receive channel result from backend.Commit function - testCases := []struct { + for _, test := range []struct { expectedErr error expectedSignature [][]byte - expectedBlock func() *types.Block }{ { // normal case nil, [][]byte{append([]byte{1}, bytes.Repeat([]byte{0x00}, types.IstanbulExtraSeal-1)...)}, - func() *types.Block { - chain, engine := newBlockChain(1) - engine.RegisterStakingModule(mStaking) - defer engine.Stop() - - block := makeBlockWithoutSeal(chain, engine, chain.Genesis()) - expectedBlock, _ := engine.updateBlock(block) - return expectedBlock - }, }, { // invalid signature errInvalidCommittedSeals, nil, - func() *types.Block { - chain, engine := newBlockChain(1) - engine.RegisterStakingModule(mStaking) - defer engine.Stop() - - block := makeBlockWithoutSeal(chain, engine, chain.Genesis()) - expectedBlock, _ := engine.updateBlock(block) - return expectedBlock - }, }, - } + } { + chain, engine := newBlockChain(1) + + block := makeBlockWithoutSeal(chain, engine, chain.Genesis()) + expBlock, _ := engine.updateBlock(block) - for _, test := range testCases { - expBlock := test.expectedBlock() go func() { select { - case result := <-backend.commitCh: + case result := <-engine.commitCh: commitCh <- result.Block return } }() - backend.proposedBlockHash = expBlock.Hash() - if err := backend.Commit(expBlock, test.expectedSignature); err != nil { - if err != test.expectedErr { - t.Errorf("error mismatch: have %v, want %v", err, test.expectedErr) - } - } + engine.proposedBlockHash = expBlock.Hash() + assert.Equal(t, test.expectedErr, engine.Commit(expBlock, test.expectedSignature)) if test.expectedErr == nil { // to avoid race condition is occurred by goroutine select { case result := <-commitCh: - if result.Hash() != expBlock.Hash() { - t.Errorf("hash mismatch: have %v, want %v", result.Hash(), expBlock.Hash()) - } + assert.Equal(t, expBlock.Hash(), result.Hash()) case <-time.After(10 * time.Second): t.Fatal("timeout") } } + engine.Stop() } } func TestGetProposer(t *testing.T) { - chain, engine := newBlockChain(1) - defer engine.Stop() + ctrl, mStaking := makeMockStakingManager(t, nil, 0) + defer ctrl.Finish() - si := makeTestStakingInfo(nil, 0) - mockCtrl := gomock.NewController(t) - defer mockCtrl.Finish() - mStaking := mock.NewMockStakingModule(mockCtrl) - mStaking.EXPECT().GetStakingInfo(gomock.Any()).Return(si, nil).AnyTimes() - engine.RegisterStakingModule(mStaking) + chain, engine := newBlockChain(1, mStaking) + defer engine.Stop() - block := makeBlock(chain, engine, chain.Genesis()) + block := makeBlockWithSeal(chain, engine, chain.Genesis()) _, err := chain.InsertChain(types.Blocks{block}) - if err != nil { - t.Errorf("failed to insert chain: %v", err) - } - expected := engine.GetProposer(1) - actual := engine.Address() - if actual != expected { - t.Errorf("proposer mismatch: have %v, want %v", actual.Hex(), expected.Hex()) - } + assert.NoError(t, err) + assert.Equal(t, engine.GetProposer(1), engine.Address()) } diff --git a/consensus/istanbul/backend/engine.go b/consensus/istanbul/backend/engine.go index f991a9ad0..a152b0f04 100644 --- a/consensus/istanbul/backend/engine.go +++ b/consensus/istanbul/backend/engine.go @@ -45,6 +45,7 @@ import ( "github.com/kaiachain/kaia/crypto/sha3" "github.com/kaiachain/kaia/kaiax" "github.com/kaiachain/kaia/kaiax/staking" + "github.com/kaiachain/kaia/kaiax/valset" "github.com/kaiachain/kaia/networks/rpc" "github.com/kaiachain/kaia/params" "github.com/kaiachain/kaia/rlp" @@ -327,10 +328,7 @@ func (sb *backend) verifySigner(chain consensus.ChainReader, header *types.Heade } // Retrieve the snapshot needed to verify this header and cache it - snap, err := sb.snapshot(chain, number-1, header.ParentHash, parents, true) - if err != nil { - return err - } + valSet, err := sb.GetValidatorSet(number) // resolve the authorization key and check against signers signer, err := ecrecover(header) @@ -339,7 +337,7 @@ func (sb *backend) verifySigner(chain consensus.ChainReader, header *types.Heade } // Signer should be in the validator set of previous block's extraData. - if _, v := snap.ValSet.GetByAddress(signer); v == nil { + if !valSet.IsQualifiedMember(signer) { return errUnauthorized } return nil @@ -354,7 +352,7 @@ func (sb *backend) verifyCommittedSeals(chain consensus.ChainReader, header *typ } // Retrieve the snapshot needed to verify this header and cache it - snap, err := sb.snapshot(chain, number-1, header.ParentHash, parents, true) + valSet, err := sb.GetCommitteeState(number) if err != nil { return err } @@ -368,7 +366,7 @@ func (sb *backend) verifyCommittedSeals(chain consensus.ChainReader, header *typ return errEmptyCommittedSeals } - validators := snap.ValSet.Copy() + council := valSet.Council().Copy() // Check whether the committed seals are generated by parent's validators validSeal := 0 proposalSeal := istanbulCore.PrepareCommittedSeal(header.Hash()) @@ -381,7 +379,7 @@ func (sb *backend) verifyCommittedSeals(chain consensus.ChainReader, header *typ } // Every validator can have only one seal. If more than one seals are signed by a // validator, the validator cannot be found and errInvalidCommittedSeals is returned. - if validators.RemoveValidator(addr) { + if council.Remove(addr) { validSeal += 1 } else { return errInvalidCommittedSeals @@ -389,7 +387,7 @@ func (sb *backend) verifyCommittedSeals(chain consensus.ChainReader, header *typ } // The length of validSeal should be larger than number of faulty node + 1 - if validSeal <= 2*snap.ValSet.F() { + if validSeal <= 2*valSet.F() { return errInvalidCommittedSeals } @@ -415,24 +413,18 @@ func (sb *backend) VerifySeal(chain consensus.ChainReader, header *types.Header) // Prepare initializes the consensus fields of a block header according to the // rules of a particular engine. The changes are executed inline. func (sb *backend) Prepare(chain consensus.ChainReader, header *types.Header) error { - // unused fields, force to set to empty - header.Rewardbase = sb.rewardbase - // copy the parent extra data as the header extra data number := header.Number.Uint64() parent := chain.GetHeader(header.ParentHash, number-1) if parent == nil { return consensus.ErrUnknownAncestor } + + // unused fields, force to set to empty + header.Rewardbase = sb.rewardbase // use the same blockscore for all blocks header.BlockScore = defaultBlockScore - // Assemble the voting snapshot - snap, err := sb.snapshot(chain, number-1, header.ParentHash, nil, true) - if err != nil { - return err - } - // If it reaches the Epoch, governance config will be added to block header pset := sb.govModule.EffectiveParamSet(number) if number%pset.Epoch == 0 { @@ -459,8 +451,12 @@ func (sb *backend) Prepare(chain consensus.ChainReader, header *types.Header) er header.MixHash = mixHash } - // add validators (council list) in snapshot to extraData's validators section - extra, err := prepareExtra(header, snap.validators()) + // add validators (council list) to extraData's validators section + valSet, err := sb.GetValidatorSet(number) + if err != nil { + return err + } + extra, err := prepareExtra(header, valSet.Qualified()) if err != nil { return err } @@ -476,7 +472,7 @@ func (sb *backend) Prepare(chain consensus.ChainReader, header *types.Header) er } for _, module := range sb.consensusModules { - if err := module.PrepareHeader(header); err != nil { + if err = module.PrepareHeader(header); err != nil { return err } } @@ -518,19 +514,25 @@ func (sb *backend) Finalize(chain consensus.ChainReader, header *types.Header, s // Determine and update Rewardbase when mining. When mining, state root is not yet determined and will be determined at the end of this Finalize below. if common.EmptyHash(header.Root) { // TODO-Kaia Let's redesign below logic and remove dependency between block reward and istanbul consensus. - lastHeader := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) - valSet := sb.getValidators(lastHeader.Number.Uint64(), lastHeader.Hash()) + var ( + blockNum = header.Number.Uint64() + rewardAddress = sb.GetRewardAddress(blockNum, sb.address) + ) + + valSet, err := sb.GetValidatorSet(blockNum) + if err != nil { + return nil, err + } var logMsg string - _, nodeValidator := valSet.GetByAddress(sb.address) - if nodeValidator == nil || (nodeValidator.RewardAddress() == common.Address{}) { + if !valSet.IsQualifiedMember(sb.address) || (rewardAddress == common.Address{}) { logMsg = "No reward address for nodeValidator. Use node's rewardbase." } else { // use reward address of current node. // only a block made by proposer will be accepted. However, due to round change any node can be the proposer of a block. // so need to write reward address of current node to receive reward when it becomes proposer. // if current node does not become proposer, the block will be abandoned - header.Rewardbase = nodeValidator.RewardAddress() + header.Rewardbase = rewardAddress logMsg = "Use reward address for nodeValidator." } logger.Trace(logMsg, "header.Number", header.Number.Uint64(), "node address", sb.address, "rewardbase", header.Rewardbase) @@ -591,11 +593,11 @@ func (sb *backend) Seal(chain consensus.ChainReader, block *types.Block, stop <- number := header.Number.Uint64() // Bail out if we're unauthorized to sign a block - snap, err := sb.snapshot(chain, number-1, header.ParentHash, nil, true) + valSet, err := sb.GetValidatorSet(number) if err != nil { return nil, err } - if _, v := snap.ValSet.GetByAddress(sb.address); v == nil { + if !valSet.IsQualifiedMember(sb.address) { return nil, errUnauthorized } @@ -691,6 +693,10 @@ func (sb *backend) SetChain(chain consensus.ChainReader) { sb.chain = chain } +func (sb *backend) RegisterValsetModule(mValset valset.ValsetModule) { + sb.valsetModule = mValset +} + func (sb *backend) RegisterStakingModule(module staking.StakingModule) { sb.stakingModule = module } diff --git a/consensus/istanbul/backend/engine_test.go b/consensus/istanbul/backend/engine_test.go index 8f9a69626..fd9b86066 100644 --- a/consensus/istanbul/backend/engine_test.go +++ b/consensus/istanbul/backend/engine_test.go @@ -50,6 +50,8 @@ import ( "github.com/kaiachain/kaia/kaiax/staking" staking_impl "github.com/kaiachain/kaia/kaiax/staking/impl" "github.com/kaiachain/kaia/kaiax/staking/mock" + "github.com/kaiachain/kaia/kaiax/valset" + valset_impl "github.com/kaiachain/kaia/kaiax/valset/impl" "github.com/kaiachain/kaia/params" "github.com/kaiachain/kaia/rlp" "github.com/stretchr/testify/assert" @@ -134,6 +136,20 @@ func disableVotes(paramNames []gov.ParamName) { } } +func setNodeKeys(n int, governingNode *ecdsa.PrivateKey) ([]*ecdsa.PrivateKey, []common.Address) { + nodeKeys = make([]*ecdsa.PrivateKey, n) + addrs = make([]common.Address, n) + for i := 0; i < n; i++ { + if i == 0 && governingNode != nil { + nodeKeys[i] = governingNode + } else { + nodeKeys[i], _ = crypto.GenerateKey() + } + addrs[i] = crypto.PubkeyToAddress(nodeKeys[i].PublicKey) + } + return nodeKeys, addrs +} + // in this test, we can set n to 1, and it means we can process Istanbul and commit a // block by one node. Otherwise, if n is larger than 1, we have to generate // other fake events to process Istanbul. @@ -144,9 +160,9 @@ func newBlockChain(n int, items ...interface{}) (*blockchain.BlockChain, *backen genesis.Timestamp = uint64(time.Now().Unix()) var ( - key *ecdsa.PrivateKey - period = istanbul.DefaultConfig.BlockPeriod - err error + period = istanbul.DefaultConfig.BlockPeriod + mStaking staking.StakingModule + err error ) // force enable Istanbul engine and governance genesis.Config.Istanbul = params.GetDefaultIstanbulConfig() @@ -185,28 +201,19 @@ func newBlockChain(n int, items ...interface{}) (*blockchain.BlockChain, *backen genesis.Config.Governance.Reward.MintingAmount = v case governanceMode: genesis.Config.Governance.GovernanceMode = string(v) - case *ecdsa.PrivateKey: - key = v case blockPeriod: period = uint64(v) + case *mock.MockStakingModule: + mStaking = v } } - nodeKeys = make([]*ecdsa.PrivateKey, n) - addrs = make([]common.Address, n) - var b *backend - if len(items) != 0 { - b = newTestBackendWithConfig(genesis.Config, period, key) - } else { - b = newTestBackend() + if len(nodeKeys) != n { + setNodeKeys(n, nil) } - nodeKeys[0] = b.privateKey - addrs[0] = b.address // if governance mode is single, this address is the governing node address - for i := 1; i < n; i++ { - nodeKeys[i], _ = crypto.GenerateKey() - addrs[i] = crypto.PubkeyToAddress(nodeKeys[i].PublicKey) - } + // if governance mode is single, this address is the governing node address + b := newTestBackendWithConfig(genesis.Config, period, nodeKeys[0]) appendValidators(genesis, addrs) @@ -241,8 +248,34 @@ func newBlockChain(n int, items ...interface{}) (*blockchain.BlockChain, *backen ChainConfig: genesis.Config, NodeAddress: b.address, }) + + if mStaking == nil { + mStaking = staking_impl.NewStakingModule() + } + + mReward := reward_impl.NewRewardModule() + mReward.Init(&reward_impl.InitOpts{ + ChainConfig: bc.Config(), + Chain: bc, + GovModule: mGov, + StakingModule: mStaking, // Irrelevant in ProposerPolicy=0. Won't inject mock. + }) + + mValset := valset_impl.NewValsetModule() + mValset.Init(&valset_impl.InitOpts{ + Chain: bc, + ChainKv: bc.StateCache().TrieDB().DiskDB().GetMiscDB(), + Governance: mGov, + StakingInfo: mStaking, + NodeAddress: b.address, + }) + b.govModule = mGov + b.RegisterStakingModule(mStaking) + b.RegisterValsetModule(mValset) + b.RegisterConsensusModule(mReward, mGov) + mValset.Start() if b.Start(bc, bc.CurrentBlock, bc.HasBadBlock) != nil { panic(err) } @@ -347,9 +380,6 @@ func TestPrepare(t *testing.T) { func TestSealStopChannel(t *testing.T) { chain, engine := newBlockChain(4) defer engine.Stop() - mockCtrl, mStaking := makeMockStakingManager(t, nil, 0) - engine.RegisterStakingModule(mStaking) - defer mockCtrl.Finish() block := makeBlockWithoutSeal(chain, engine, chain.Genesis()) stop := make(chan struct{}, 1) @@ -368,33 +398,20 @@ func TestSealStopChannel(t *testing.T) { go eventLoop() finalBlock, err := engine.Seal(chain, block, stop) - if err != nil { - t.Errorf("error mismatch: have %v, want nil", err) - } - - if finalBlock != nil { - t.Errorf("block mismatch: have %v, want nil", finalBlock) - } + assert.NoError(t, err) + assert.Nil(t, finalBlock) } func TestSealCommitted(t *testing.T) { chain, engine := newBlockChain(1) defer engine.Stop() - mockCtrl, mStaking := makeMockStakingManager(t, nil, 0) - engine.RegisterStakingModule(mStaking) - defer mockCtrl.Finish() block := makeBlockWithoutSeal(chain, engine, chain.Genesis()) expectedBlock, _ := engine.updateBlock(block) actualBlock, err := engine.Seal(chain, block, make(chan struct{})) - if err != nil { - t.Errorf("error mismatch: have %v, want %v", err, expectedBlock) - } - - if actualBlock.Hash() != expectedBlock.Hash() { - t.Errorf("hash mismatch: have %v, want %v", actualBlock.Hash(), expectedBlock.Hash()) - } + assert.NoError(t, err) + assert.Equal(t, expectedBlock.Hash(), actualBlock.Hash()) } func TestVerifyHeader(t *testing.T) { @@ -462,9 +479,6 @@ func TestVerifyHeader(t *testing.T) { func TestVerifySeal(t *testing.T) { chain, engine := newBlockChain(1) defer engine.Stop() - mockCtrl, mStaking := makeMockStakingManager(t, nil, 0) - engine.RegisterStakingModule(mStaking) - defer mockCtrl.Finish() genesis := chain.Genesis() @@ -499,32 +513,36 @@ func TestVerifySeal(t *testing.T) { } func TestVerifyHeaders(t *testing.T) { - chain, engine := newBlockChain(1) - defer engine.Stop() - mockCtrl, mStaking := makeMockStakingManager(t, nil, 0) - engine.RegisterStakingModule(mStaking) - defer mockCtrl.Finish() + var configItems []interface{} + configItems = append(configItems, proposerPolicy(params.WeightedRandom)) + configItems = append(configItems, proposerUpdateInterval(1)) + configItems = append(configItems, epoch(3)) + configItems = append(configItems, governanceMode("single")) + configItems = append(configItems, minimumStake(new(big.Int).SetUint64(4000000))) + configItems = append(configItems, istanbulCompatibleBlock(new(big.Int).SetUint64(0))) + configItems = append(configItems, blockPeriod(0)) // set block period to 0 to prevent creating future block - genesis := chain.Genesis() + ctrl, mStaking := makeMockStakingManager(t, nil, 0) + ctrl.Finish() + + chain, engine := newBlockChain(1, append(configItems, mStaking)...) + chain.RegisterExecutionModule(engine.govModule) + defer engine.Stop() // success case headers := []*types.Header{} blocks := []*types.Block{} size := 100 + var previousBlock, currentBlock *types.Block = nil, chain.Genesis() for i := 0; i < size; i++ { - var b *types.Block // 100 headers with 50 of them empty committed seals, 50 of them invalid committed seals. - if i == 0 { - b = makeBlockWithoutSeal(chain, engine, genesis) - b, _ = engine.updateBlock(b) - engine.db.WriteHeader(b.Header()) - } else { - b = makeBlockWithoutSeal(chain, engine, blocks[i-1]) - b, _ = engine.updateBlock(b) - engine.db.WriteHeader(b.Header()) - } - blocks = append(blocks, b) + previousBlock = currentBlock + currentBlock = makeBlockWithSeal(chain, engine, previousBlock) + _, err := chain.InsertChain(types.Blocks{currentBlock}) + assert.NoError(t, err) + + blocks = append(blocks, currentBlock) headers = append(headers, blocks[i].Header()) } @@ -774,18 +792,7 @@ func TestRewardDistribution(t *testing.T) { chain, engine := newBlockChain(1, configItems...) chain.RegisterExecutionModule(engine.govModule) - engine.RegisterConsensusModule(engine.govModule) defer engine.Stop() - mReward := reward_impl.NewRewardModule() - if err := mReward.Init(&reward_impl.InitOpts{ - ChainConfig: chain.Config(), - Chain: chain, - GovModule: engine.govModule, - StakingModule: staking_impl.NewStakingModule(), // Irrelevant in ProposerPolicy=0. Won't inject mock. - }); err != nil { - t.Fatalf("Failed to initialize reward module: %v", err) - } - engine.RegisterConsensusModule(mReward) assert.Equal(t, uint64(testEpoch), engine.govModule.EffectiveParamSet(0).Epoch) assert.Equal(t, mintAmount, engine.govModule.EffectiveParamSet(0).MintingAmount.Uint64()) @@ -828,26 +835,16 @@ func makeSnapshotTestConfigItems(stakingInterval, proposerInterval uint64) []int } func makeMockStakingManager(t *testing.T, amounts []uint64, blockNum uint64) (*gomock.Controller, *mock.MockStakingModule) { - si := makeTestStakingInfo(amounts, blockNum) - - mockCtrl := gomock.NewController(t) - mStaking := mock.NewMockStakingModule(mockCtrl) - mStaking.EXPECT().GetStakingInfo(gomock.Any()).Return(si, nil).AnyTimes() - return mockCtrl, mStaking -} - -// Set StakingInfo with given amount for nodeKeys. If amounts == nil, set to 0 amounts. -func setTestStakingInfo(t *testing.T, b *backend, amounts []uint64, blockNum uint64) *gomock.Controller { - if amounts == nil { - amounts = make([]uint64, len(nodeKeys)) + if len(nodeKeys) != len(amounts) { + setNodeKeys(len(amounts), nil) // explictly set the nodeKey } + si := makeTestStakingInfo(amounts, blockNum) mockCtrl := gomock.NewController(t) mStaking := mock.NewMockStakingModule(mockCtrl) mStaking.EXPECT().GetStakingInfo(gomock.Any()).Return(si, nil).AnyTimes() - b.RegisterStakingModule(mStaking) - return mockCtrl + return mockCtrl, mStaking } func makeTestStakingInfo(amounts []uint64, blockNum uint64) *staking.StakingInfo { @@ -871,15 +868,7 @@ func makeTestStakingInfo(amounts []uint64, blockNum uint64) *staking.StakingInfo return si } -func toAddressList(validators []istanbul.Validator) []common.Address { - addresses := make([]common.Address, len(validators)) - for idx, val := range validators { - addresses[idx] = val.Address() - } - return addresses -} - -func copyAndSortAddrs(addrs []common.Address) []common.Address { +func copyAndSortAddrs(addrs []common.Address) valset.AddressList { copied := make([]common.Address, len(addrs)) copy(copied, addrs) @@ -890,7 +879,7 @@ func copyAndSortAddrs(addrs []common.Address) []common.Address { return copied } -func makeExpectedResult(indices []int, candidate []common.Address) []common.Address { +func makeExpectedResult(indices []int, candidate []common.Address) valset.AddressList { expected := make([]common.Address, len(indices)) for eIdx, cIdx := range indices { expected[eIdx] = candidate[cIdx] @@ -905,7 +894,7 @@ func assertMapSubset[M ~map[K]any, K comparable](t *testing.T, subset, set M) { } } -func TestSnapshot_Validators_AfterMinimumStakingVotes(t *testing.T) { +func Test_AfterMinimumStakingVotes(t *testing.T) { // temporaily enable forbidden votes enableVotes([]gov.ParamName{gov.RewardMinimumStake, gov.GovernanceGovernanceMode}) defer disableVotes([]gov.ParamName{gov.RewardMinimumStake, gov.GovernanceGovernanceMode}) @@ -1005,11 +994,9 @@ func TestSnapshot_Validators_AfterMinimumStakingVotes(t *testing.T) { configItems = append(configItems, blockPeriod(0)) // set block period to 0 to prevent creating future block for _, tc := range testcases { - chain, engine := newBlockChain(4, configItems...) - mockCtrl, mStaking := makeMockStakingManager(t, tc.stakingAmounts, 0) + ctrl, mStaking := makeMockStakingManager(t, tc.stakingAmounts, 0) + chain, engine := newBlockChain(len(tc.stakingAmounts), append(configItems, mStaking)...) chain.RegisterExecutionModule(engine.govModule) - engine.RegisterStakingModule(mStaking) - engine.RegisterConsensusModule(engine.govModule) var previousBlock, currentBlock *types.Block = nil, chain.Genesis() @@ -1041,27 +1028,23 @@ func TestSnapshot_Validators_AfterMinimumStakingVotes(t *testing.T) { for _, e := range tc.expected { for _, num := range e.blocks { - block := chain.GetBlockByNumber(num) - snap, err := engine.snapshot(chain, block.NumberU64(), block.Hash(), nil, true) + valSet, err := engine.GetValidatorSet(num + 1) assert.NoError(t, err) - validators := toAddressList(snap.ValSet.List()) - demoted := toAddressList(snap.ValSet.DemotedList()) - expectedValidators := makeExpectedResult(e.validators, addrs) expectedDemoted := makeExpectedResult(e.demoted, addrs) - assert.Equal(t, expectedValidators, validators) - assert.Equal(t, expectedDemoted, demoted) + assert.Equal(t, expectedValidators, valSet.Qualified(), "blockNum:%d", num+1) + assert.Equal(t, expectedDemoted, valSet.Demoted(), "blockNum:%d", num+1) } } - mockCtrl.Finish() + ctrl.Finish() engine.Stop() } } -func TestSnapshot_Validators_AfterKaia_BasedOnStaking(t *testing.T) { +func Test_AfterKaia_BasedOnStaking(t *testing.T) { type testcase struct { stakingAmounts []uint64 // test staking amounts of each validator isKaiaCompatible bool // whether or not if the inserted block is kaia compatible @@ -1118,36 +1101,37 @@ func TestSnapshot_Validators_AfterKaia_BasedOnStaking(t *testing.T) { } else { configItems = append(configItems, istanbulCompatibleBlock(new(big.Int).SetUint64(0))) } - chain, engine := newBlockChain(testNum, configItems...) - + setNodeKeys(testNum, nil) mockCtrl := gomock.NewController(t) mStaking := mock.NewMockStakingModule(mockCtrl) mStaking.EXPECT().GetStakingInfo(uint64(1)).Return(makeTestStakingInfo(genesisStakingAmounts, 0), nil).AnyTimes() - mStaking.EXPECT().GetStakingInfo(uint64(2)).Return(makeTestStakingInfo(tc.stakingAmounts, 1), nil).AnyTimes() - engine.RegisterStakingModule(mStaking) + if tc.isKaiaCompatible { + mStaking.EXPECT().GetStakingInfo(uint64(2)).Return(makeTestStakingInfo(tc.stakingAmounts, 1), nil).AnyTimes() + } else { + mStaking.EXPECT().GetStakingInfo(uint64(2)).Return(makeTestStakingInfo(genesisStakingAmounts, 0), nil).AnyTimes() + } + + chain, engine := newBlockChain(testNum, append(configItems, mStaking)...) block := makeBlockWithSeal(chain, engine, chain.Genesis()) _, err := chain.InsertChain(types.Blocks{block}) assert.NoError(t, err) - snap, err := engine.snapshot(chain, block.NumberU64(), block.Hash(), nil, true) + valSet, err := engine.GetValidatorSet(block.NumberU64() + 1) assert.NoError(t, err) - validators := toAddressList(snap.ValSet.List()) - demoted := toAddressList(snap.ValSet.DemotedList()) - expectedValidators := makeExpectedResult(tc.expectedValidators, addrs) expectedDemoted := makeExpectedResult(tc.expectedDemoted, addrs) - assert.Equal(t, expectedValidators, validators) - assert.Equal(t, expectedDemoted, demoted) + assert.Equal(t, expectedValidators, valSet.Qualified()) + assert.Equal(t, expectedDemoted, valSet.Demoted()) mockCtrl.Finish() engine.Stop() } } -func TestSnapshot_Validators_BasedOnStaking(t *testing.T) { +func Test_BasedOnStaking(t *testing.T) { type testcase struct { stakingAmounts []uint64 // test staking amounts of each validator isIstanbulCompatible bool // whether or not if the inserted block is istanbul compatible @@ -1275,7 +1259,6 @@ func TestSnapshot_Validators_BasedOnStaking(t *testing.T) { }, } - testNum := 4 ms := uint64(5500000) configItems := makeSnapshotTestConfigItems(1, 1) configItems = append(configItems, minimumStake(new(big.Int).SetUint64(ms))) @@ -1286,34 +1269,31 @@ func TestSnapshot_Validators_BasedOnStaking(t *testing.T) { if tc.isSingleMode { configItems = append(configItems, governanceMode("single")) } - chain, engine := newBlockChain(testNum, configItems...) mockCtrl, mStaking := makeMockStakingManager(t, tc.stakingAmounts, 0) - engine.RegisterStakingModule(mStaking) + if tc.isIstanbulCompatible { + mStaking.EXPECT().GetStakingInfo(gomock.Any()).Return(makeTestStakingInfo(tc.stakingAmounts, 0), nil).AnyTimes() + } + chain, engine := newBlockChain(len(tc.stakingAmounts), append(configItems, mStaking)...) block := makeBlockWithSeal(chain, engine, chain.Genesis()) _, err := chain.InsertChain(types.Blocks{block}) assert.NoError(t, err) - snap, err := engine.snapshot(chain, block.NumberU64(), block.Hash(), nil, true) + councilState, err := engine.GetValidatorSet(block.NumberU64() + 1) assert.NoError(t, err) - validators := toAddressList(snap.ValSet.List()) - demoted := toAddressList(snap.ValSet.DemotedList()) - expectedValidators := makeExpectedResult(tc.expectedValidators, addrs) expectedDemoted := makeExpectedResult(tc.expectedDemoted, addrs) - assert.Equal(t, expectedValidators, validators) - assert.Equal(t, expectedDemoted, demoted) + assert.Equal(t, expectedValidators, councilState.Qualified()) + assert.Equal(t, expectedDemoted, councilState.Demoted()) mockCtrl.Finish() engine.Stop() } } -func TestSnapshot_Validators_AddRemove(t *testing.T) { - // TODO-kaiax: valset module is required - t.Skip("skipping") +func Test_AddRemove(t *testing.T) { type vote struct { key string value interface{} @@ -1457,11 +1437,9 @@ func TestSnapshot_Validators_AddRemove(t *testing.T) { for _, tc := range testcases { // Create test blockchain - chain, engine := newBlockChain(4, configItems...) - mockCtrl, mStaking := makeMockStakingManager(t, stakes, 0) - engine.RegisterStakingModule(mStaking) - // Don't register gov module here because old gov and govModule cannot coexist for this test, - // because header.Vote format of address list is different. + ctrl, mStaking := makeMockStakingManager(t, stakes, 0) + chain, engine := newBlockChain(len(stakes), append(configItems, mStaking)...) + chain.RegisterExecutionModule(engine.valsetModule, engine.govModule) // Backup the globals. The globals `nodeKeys` and `addrs` will be // modified according to validator change votes. @@ -1481,7 +1459,7 @@ func TestSnapshot_Validators_AddRemove(t *testing.T) { require.NotNil(t, vote, fmt.Sprintf("vote is nil for %v %v", v.key, addr)) engine.govModule.(*gov_impl.GovModule).Hgm.PushMyVotes(vote) } else { - addrList := makeExpectedResult(v.value.([]int), allAddrs) + addrList := []common.Address(makeExpectedResult(v.value.([]int), allAddrs)) vote := headergov.NewVoteData(engine.address, v.key, addrList) require.NotNil(t, vote, fmt.Sprintf("vote is nil for %v %v", v.key, addrList)) engine.govModule.(*gov_impl.GovModule).Hgm.PushMyVotes(vote) @@ -1515,22 +1493,19 @@ func TestSnapshot_Validators_AddRemove(t *testing.T) { } } - // Calculate historical validators using the snapshot. + // Calculate historical validators for i := 0; i < tc.length; i++ { if _, ok := tc.expected[i]; !ok { continue } - block := chain.GetBlockByNumber(uint64(i)) - snap, err := engine.snapshot(chain, block.NumberU64(), block.Hash(), nil, true) + valSet, err := engine.GetValidatorSet(uint64(i) + 1) assert.NoError(t, err) - validators := copyAndSortAddrs(toAddressList(snap.ValSet.List())) expectedValidators := makeExpectedResult(tc.expected[i].validators, allAddrs) - assert.Equal(t, expectedValidators, validators) - // t.Logf("snap at block #%d: size %d", i, snap.ValSet.Size()) + assert.Equal(t, expectedValidators, valSet.Qualified()) } - mockCtrl.Finish() + ctrl.Finish() engine.Stop() } } @@ -1755,11 +1730,9 @@ func TestGovernance_Votes(t *testing.T) { configItems = append(configItems, governanceMode("single")) configItems = append(configItems, blockPeriod(0)) // set block period to 0 to prevent creating future block for _, tc := range testcases { - chain, engine := newBlockChain(1, configItems...) mockCtrl, mStaking := makeMockStakingManager(t, nil, 0) - chain.RegisterExecutionModule(engine.govModule) - engine.RegisterStakingModule(mStaking) - engine.RegisterConsensusModule(engine.govModule) + chain, engine := newBlockChain(1, append(configItems, mStaking)...) + chain.RegisterExecutionModule(engine.valsetModule, engine.govModule) // test initial governance items pset := engine.govModule.EffectiveParamSet(chain.CurrentHeader().Number.Uint64() + 1) @@ -1864,11 +1837,9 @@ func TestGovernance_GovModule(t *testing.T) { for _, tc := range testcases { // Create test blockchain - chain, engine := newBlockChain(4, configItems...) mockCtrl, mStaking := makeMockStakingManager(t, stakes, 0) - chain.RegisterExecutionModule(engine.govModule) - engine.RegisterStakingModule(mStaking) - engine.RegisterConsensusModule(engine.govModule) + chain, engine := newBlockChain(len(stakes), append(configItems, mStaking)...) + chain.RegisterExecutionModule(engine.valsetModule, engine.govModule) var previousBlock, currentBlock *types.Block = nil, chain.Genesis() diff --git a/consensus/istanbul/backend/handler.go b/consensus/istanbul/backend/handler.go index 89cfdf601..c6d1858d5 100644 --- a/consensus/istanbul/backend/handler.go +++ b/consensus/istanbul/backend/handler.go @@ -106,16 +106,12 @@ func (sb *backend) ValidatePeerType(addr common.Address) error { for sb.chain == nil { return errNoChainReader } - validators := sb.getValidators(sb.chain.CurrentHeader().Number.Uint64(), sb.chain.CurrentHeader().Hash()) - for _, val := range validators.List() { - if addr == val.Address() { - return nil - } + valSet, err := sb.GetValidatorSet(sb.chain.CurrentHeader().Number.Uint64()) + if err != nil { + return errInvalidPeerAddress } - for _, val := range validators.DemotedList() { - if addr == val.Address() { - return nil - } + if valSet.IsCouncilMember(addr) { + return nil } return errInvalidPeerAddress } diff --git a/consensus/istanbul/backend/handler_test.go b/consensus/istanbul/backend/handler_test.go index 5ec8f6244..2c2c632b1 100644 --- a/consensus/istanbul/backend/handler_test.go +++ b/consensus/istanbul/backend/handler_test.go @@ -130,6 +130,8 @@ func TestBackend_HandleMsg(t *testing.T) { func TestBackend_Protocol(t *testing.T) { backend := newTestBackend() + defer backend.Stop() + assert.Equal(t, IstanbulProtocol, backend.Protocol()) } diff --git a/consensus/istanbul/backend/testutil_test.go b/consensus/istanbul/backend/testutil_test.go index 1fae902d5..fdd9f5d4e 100644 --- a/consensus/istanbul/backend/testutil_test.go +++ b/consensus/istanbul/backend/testutil_test.go @@ -35,6 +35,8 @@ import ( "github.com/kaiachain/kaia/crypto/bls" "github.com/kaiachain/kaia/governance" gov_impl "github.com/kaiachain/kaia/kaiax/gov/impl" + staking_impl "github.com/kaiachain/kaia/kaiax/staking/impl" + valset_impl "github.com/kaiachain/kaia/kaiax/valset/impl" "github.com/kaiachain/kaia/log" "github.com/kaiachain/kaia/params" "github.com/kaiachain/kaia/rlp" @@ -198,8 +200,27 @@ func newTestContext(numNodes int, config *params.ChainConfig, overrides *testOve NodeAddress: engine.Address(), }) + mStaking := staking_impl.NewStakingModule() + mStaking.Init(&staking_impl.InitOpts{ + ChainKv: dbm.GetMiscDB(), + ChainConfig: config, + Chain: chain, + }) + mValset := valset_impl.NewValsetModule() + mValset.Init(&valset_impl.InitOpts{ + ChainKv: dbm.GetMiscDB(), + Chain: chain, + StakingInfo: mStaking, + Governance: mGov, + NodeAddress: engine.Address(), + }) + engine.RegisterValsetModule(mValset) + if err = mValset.Start(); err != nil { + panic(err) + } + // Start the engine - if err := engine.Start(chain, chain.CurrentBlock, chain.HasBadBlock); err != nil { + if err = engine.Start(chain, chain.CurrentBlock, chain.HasBadBlock); err != nil { panic(err) } diff --git a/consensus/istanbul/backend/validator.go b/consensus/istanbul/backend/validator.go new file mode 100644 index 000000000..974c60476 --- /dev/null +++ b/consensus/istanbul/backend/validator.go @@ -0,0 +1,72 @@ +package backend + +import ( + "github.com/kaiachain/kaia/common" + "github.com/kaiachain/kaia/consensus/istanbul" +) + +func (sb *backend) GetValidatorSet(num uint64) (*istanbul.BlockValSet, error) { + council, err := sb.valsetModule.GetCouncil(num) + if err != nil { + return nil, err + } + + qualified, err := sb.valsetModule.GetQualifiedValidators(num) + if err != nil { + return nil, err + } + + return istanbul.NewBlockValSet(council, qualified), nil +} + +func (sb *backend) GetCommitteeState(num uint64) (*istanbul.RoundCommitteeState, error) { + header := sb.chain.GetHeaderByNumber(num) + if header == nil { + return nil, errUnknownBlock + } + + return sb.GetCommitteeStateByRound(num, uint64(header.Round())) +} + +func (sb *backend) GetCommitteeStateByRound(num uint64, round uint64) (*istanbul.RoundCommitteeState, error) { + blockValSet, err := sb.GetValidatorSet(num) + if err != nil { + return nil, err + } + + committee, err := sb.valsetModule.GetCommittee(num, round) + if err != nil { + return nil, err + } + + proposer, err := sb.valsetModule.GetProposer(num, round) + if err != nil { + return nil, err + } + + committeeSize := sb.govModule.EffectiveParamSet(num).CommitteeSize + return istanbul.NewRoundCommitteeState(blockValSet, committeeSize, committee, proposer), nil +} + +// GetProposer implements istanbul.Backend.GetProposer +func (sb *backend) GetProposer(number uint64) common.Address { + if h := sb.chain.GetHeaderByNumber(number); h != nil { + a, _ := sb.Author(h) + return a + } + return common.Address{} +} + +func (sb *backend) GetRewardAddress(num uint64, address common.Address) common.Address { + sInfo, err := sb.stakingModule.GetStakingInfo(num) + if err != nil { + return common.Address{} + } + + for idx, nodeId := range sInfo.NodeIds { + if nodeId == address { + return sInfo.RewardAddrs[idx] + } + } + return common.Address{} +} diff --git a/consensus/istanbul/config.go b/consensus/istanbul/config.go index 6e65fbd87..c3e2a7104 100644 --- a/consensus/istanbul/config.go +++ b/consensus/istanbul/config.go @@ -38,6 +38,16 @@ type Config struct { SubGroupSize uint64 `toml:",omitempty"` } +func (c *Config) Copy() *Config { + return &Config{ + Timeout: c.Timeout, + BlockPeriod: c.BlockPeriod, + ProposerPolicy: c.ProposerPolicy, + Epoch: c.Epoch, + SubGroupSize: c.SubGroupSize, + } +} + // TODO-Kaia-Istanbul: Do not use DefaultConfig except for assigning new config var DefaultConfig = &Config{ Timeout: 10000, diff --git a/consensus/istanbul/core/backlog.go b/consensus/istanbul/core/backlog.go index 95d1e6428..6d94b9b28 100644 --- a/consensus/istanbul/core/backlog.go +++ b/consensus/istanbul/core/backlog.go @@ -80,10 +80,10 @@ func (c *core) checkMessage(msgCode uint64, view *istanbul.View) error { return nil } -func (c *core) storeBacklog(msg *message, src istanbul.Validator) { +func (c *core) storeBacklog(msg *message, src common.Address) { logger := c.logger.NewWith("from", src, "state", c.state) - if src.Address() == c.Address() { + if src == c.Address() { logger.Warn("Backlog from self") return } @@ -93,7 +93,7 @@ func (c *core) storeBacklog(msg *message, src istanbul.Validator) { c.backlogsMu.Lock() defer c.backlogsMu.Unlock() - backlog := c.backlogs[src.Address()] + backlog := c.backlogs[src] if backlog == nil { backlog = prque.New() } @@ -112,7 +112,7 @@ func (c *core) storeBacklog(msg *message, src istanbul.Validator) { backlog.Push(msg, toPriority(msg.Code, p.View)) } } - c.backlogs[src.Address()] = backlog + c.backlogs[src] = backlog } func (c *core) processBacklog() { diff --git a/consensus/istanbul/core/commit.go b/consensus/istanbul/core/commit.go index 7a446f2ba..8a108908c 100644 --- a/consensus/istanbul/core/commit.go +++ b/consensus/istanbul/core/commit.go @@ -34,15 +34,13 @@ func (c *core) sendCommit() { return } - sub := c.current.Subject() - prevHash := c.current.Proposal().ParentHash() - - // Do not send message if the owner of the core is not a member of the committee for the `sub.View` - if !c.valSet.CheckInSubList(prevHash, sub.View, c.Address()) { + // Do not send message if the owner of the core is not a member of the committee for the current view + if !c.currentCommittee.IsCommitteeMember(c.Address()) { return } // TODO-Kaia-Istanbul: generalize broadcastCommit for all istanbul message types + sub := c.current.Subject() c.broadcastCommit(sub) } @@ -71,7 +69,7 @@ func (c *core) broadcastCommit(sub *istanbul.Subject) { }) } -func (c *core) handleCommit(msg *message, src istanbul.Validator) error { +func (c *core) handleCommit(msg *message, src common.Address) error { // Decode COMMIT message var commit *istanbul.Subject err := msg.Decode(&commit) @@ -94,9 +92,9 @@ func (c *core) handleCommit(msg *message, src istanbul.Validator) error { return err } - if !c.valSet.CheckInSubList(msg.Hash, commit.View, src.Address()) { + if !c.currentCommittee.IsCommitteeMember(src) { logger.Warn("received an istanbul commit message from non-committee", - "currentSequence", c.current.sequence.Uint64(), "sender", src.Address().String(), "msgView", commit.View.String()) + "currentSequence", c.current.sequence.Uint64(), "sender", src.String(), "msgView", commit.View.String()) return errNotFromCommittee } @@ -111,8 +109,8 @@ func (c *core) handleCommit(msg *message, src istanbul.Validator) error { logger.Warn("received commit of the hash locked proposal and change state to prepared", "msgType", msgCommit) c.setState(StatePrepared) c.sendCommit() - } else if c.current.GetPrepareOrCommitSize() >= RequiredMessageCount(c.valSet) { - logger.Info("received a quorum of the messages and change state to prepared", "msgType", msgCommit, "valSet", c.valSet.Size()) + } else if c.current.GetPrepareOrCommitSize() >= c.currentCommittee.RequiredMessageCount() { + logger.Info("received a quorum of the messages and change state to prepared", "msgType", msgCommit, "valSet", len(c.currentCommittee.Qualified())) c.current.LockHash() c.setState(StatePrepared) c.sendCommit() @@ -124,7 +122,7 @@ func (c *core) handleCommit(msg *message, src istanbul.Validator) error { // If we already have a proposal, we may have chance to speed up the consensus process // by committing the proposal without PREPARE messages. //logger.Error("### consensus check","len(commits)",c.current.Commits.Size(),"f(2/3)",2*c.valSet.F(),"state",c.state.Cmp(StateCommitted)) - if c.state.Cmp(StateCommitted) < 0 && c.current.Commits.Size() >= RequiredMessageCount(c.valSet) { + if c.state.Cmp(StateCommitted) < 0 && c.current.Commits.Size() >= c.currentCommittee.RequiredMessageCount() { // Still need to call LockHash here since state can skip Prepared state and jump directly to the Committed state. c.current.LockHash() c.commit() @@ -134,8 +132,8 @@ func (c *core) handleCommit(msg *message, src istanbul.Validator) error { } // verifyCommit verifies if the received COMMIT message is equivalent to our subject -func (c *core) verifyCommit(commit *istanbul.Subject, src istanbul.Validator) error { - logger := c.logger.NewWith("from", src, "state", c.state) +func (c *core) verifyCommit(commit *istanbul.Subject, src common.Address) error { + logger := c.logger.NewWith("from", src.Hex(), "state", c.state) sub := c.current.Subject() if !commit.Equal(sub) { @@ -146,14 +144,13 @@ func (c *core) verifyCommit(commit *istanbul.Subject, src istanbul.Validator) er return nil } -func (c *core) acceptCommit(msg *message, src istanbul.Validator) error { - logger := c.logger.NewWith("from", src, "state", c.state) +func (c *core) acceptCommit(msg *message, src common.Address) error { + logger := c.logger.NewWith("from", src.Hex(), "state", c.state) // Add the COMMIT message to current round state if err := c.current.Commits.Add(msg); err != nil { logger.Error("Failed to record commit message", "msg", msg, "err", err) return err } - return nil } diff --git a/consensus/istanbul/core/commit_test.go b/consensus/istanbul/core/commit_test.go index 7439f8e89..16cf04720 100644 --- a/consensus/istanbul/core/commit_test.go +++ b/consensus/istanbul/core/commit_test.go @@ -16,15 +16,15 @@ package core import ( + "math/big" "testing" "github.com/golang/mock/gomock" "github.com/kaiachain/kaia/blockchain/types" - "github.com/kaiachain/kaia/common" "github.com/kaiachain/kaia/consensus/istanbul" - mock_istanbul "github.com/kaiachain/kaia/consensus/istanbul/mocks" "github.com/kaiachain/kaia/fork" "github.com/kaiachain/kaia/params" + "github.com/stretchr/testify/assert" ) func TestCore_sendCommit(t *testing.T) { @@ -32,67 +32,40 @@ func TestCore_sendCommit(t *testing.T) { defer fork.ClearHardForkBlockNumberConfig() validatorAddrs, validatorKeyMap := genValidators(6) - mockBackend, mockCtrl := newMockBackend(t, validatorAddrs) - istConfig := istanbul.DefaultConfig - istConfig.ProposerPolicy = istanbul.WeightedRandom - - istCore := New(mockBackend, istConfig).(*core) - if err := istCore.Start(); err != nil { - t.Fatal(err) - } - defer istCore.Stop() - - lastProposal, lastProposer := mockBackend.LastProposal() - proposal, err := genBlock(lastProposal.(*types.Block), validatorKeyMap[validatorAddrs[0]]) - if err != nil { - t.Fatal(err) - } - - istCore.current.Preprepare = &istanbul.Preprepare{ - View: istCore.currentView(), - Proposal: proposal, - } - - mockCtrl.Finish() - - // invalid case - not committee - { - // Increase round number until the owner of istanbul.core is not a member of the committee - for istCore.valSet.CheckInSubList(lastProposal.Hash(), istCore.currentView(), istCore.Address()) { - istCore.current.round.Add(istCore.current.round, common.Big1) - istCore.valSet.CalcProposer(lastProposer, istCore.current.round.Uint64()) + for _, tc := range []struct { + tcName string + round int64 + valid bool + }{ + {"valid case", 0, true}, + {"invalid case - not committee", 2, false}, + } { + { + mockBackend, mockCtrl := newMockBackend(t, validatorAddrs) + if tc.valid { + mockBackend.EXPECT().Sign(gomock.Any()).Return(nil, nil).AnyTimes() + mockBackend.EXPECT().Broadcast(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() + } + + istConfig := istanbul.DefaultConfig.Copy() + istConfig.ProposerPolicy = istanbul.WeightedRandom + + istCore := New(mockBackend, istConfig).(*core) + assert.NoError(t, istCore.Start()) + + lastProposal, _ := mockBackend.LastProposal() + proposal, err := genBlock(lastProposal.(*types.Block), validatorKeyMap[validatorAddrs[0]]) + assert.NoError(t, err) + + istCore.current.round.Set(big.NewInt(tc.round)) + istCore.current.Preprepare = &istanbul.Preprepare{ + View: istCore.currentView(), + Proposal: proposal, + } + istCore.sendCommit() + istCore.Stop() + mockCtrl.Finish() } - - mockCtrl := gomock.NewController(t) - mockBackend := mock_istanbul.NewMockBackend(mockCtrl) - mockBackend.EXPECT().Sign(gomock.Any()).Return(nil, nil).Times(0) - mockBackend.EXPECT().Broadcast(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(0) - - istCore.backend = mockBackend - istCore.sendCommit() - - // methods of mockBackend should be executed given times - mockCtrl.Finish() - } - - // valid case - { - // Increase round number until the owner of istanbul.core become a member of the committee - for !istCore.valSet.CheckInSubList(lastProposal.Hash(), istCore.currentView(), istCore.Address()) { - istCore.current.round.Add(istCore.current.round, common.Big1) - istCore.valSet.CalcProposer(lastProposer, istCore.current.round.Uint64()) - } - - mockCtrl := gomock.NewController(t) - mockBackend := mock_istanbul.NewMockBackend(mockCtrl) - mockBackend.EXPECT().Sign(gomock.Any()).Return(nil, nil).Times(2) - mockBackend.EXPECT().Broadcast(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(1) - - istCore.backend = mockBackend - istCore.sendCommit() - - // methods of mockBackend should be executed given times - mockCtrl.Finish() } } diff --git a/consensus/istanbul/core/core.go b/consensus/istanbul/core/core.go index 905c79a9c..d8fa29bcd 100644 --- a/consensus/istanbul/core/core.go +++ b/consensus/istanbul/core/core.go @@ -82,15 +82,15 @@ type core struct { timeoutSub *event.TypeMuxSubscription futurePreprepareTimer *time.Timer - valSet istanbul.ValidatorSet waitingForRoundChange bool validateFn func([]byte, []byte) (common.Address, error) backlogs map[common.Address]*prque.Prque backlogsMu *sync.Mutex - current *roundState - handlerWg *sync.WaitGroup + currentCommittee *istanbul.RoundCommitteeState + current *roundState + handlerWg *sync.WaitGroup roundChangeSet *roundChangeSet roundChangeTimer atomic.Value //*time.Timer @@ -158,7 +158,7 @@ func (c *core) broadcast(msg *message) { } // Broadcast payload - if err = c.backend.Broadcast(msg.Hash, c.valSet, payload); err != nil { + if err = c.backend.Broadcast(msg.Hash, payload); err != nil { logger.Error("Failed to broadcast message", "msg", msg, "err", err) return } @@ -172,7 +172,7 @@ func (c *core) currentView() *istanbul.View { } func (c *core) isProposer() bool { - v := c.valSet + v := c.currentCommittee if v == nil { return false } @@ -219,7 +219,7 @@ func (c *core) startNewRound(round *big.Int) { roundChange := false // Try to get last proposal - lastProposal, lastProposer := c.backend.LastProposal() + lastProposal, _ := c.backend.LastProposal() //if c.valSet != nil && c.valSet.IsSubSet() { // c.current = nil //} else { @@ -249,7 +249,15 @@ func (c *core) startNewRound(round *big.Int) { } //} - var newView *istanbul.View + var ( + newView *istanbul.View + err error + oldProposer common.Address + ) + + if c.currentCommittee != nil { + oldProposer = c.currentCommittee.Proposer() + } if roundChange { newView = &istanbul.View{ Sequence: new(big.Int).Set(c.current.Sequence()), @@ -260,10 +268,16 @@ func (c *core) startNewRound(round *big.Int) { Sequence: new(big.Int).Add(lastProposal.Number(), common.Big1), Round: new(big.Int), } - c.valSet = c.backend.Validators(lastProposal) + } + + c.currentCommittee, err = c.backend.GetCommitteeStateByRound(newView.Sequence.Uint64(), round.Uint64()) + if err != nil { + logger.Error("Failed to get current round's committee state", "err", err) + return + } - councilSize := int64(c.valSet.Size()) - committeeSize := int64(c.valSet.SubGroupSize()) + if !roundChange { + councilSize, committeeSize := int64(len(c.currentCommittee.Qualified())), int64(c.currentCommittee.CommitteeSize()) if committeeSize > councilSize { committeeSize = councilSize } @@ -273,13 +287,12 @@ func (c *core) startNewRound(round *big.Int) { c.backend.SetCurrentView(newView) // Update logger - logger = logger.NewWith("old_proposer", c.valSet.GetProposer()) + logger = logger.NewWith("old_proposer", oldProposer) // Clear invalid ROUND CHANGE messages - c.roundChangeSet = newRoundChangeSet(c.valSet) + c.roundChangeSet = newRoundChangeSet(c.currentCommittee.ValSet()) // New snapshot for new round - c.updateRoundState(newView, c.valSet, roundChange) + c.updateRoundState(newView, c.currentCommittee, roundChange) // Calculate new proposer - c.valSet.CalcProposer(lastProposer, newView.Round.Uint64()) c.waitingForRoundChange = false c.setState(StateAcceptRequest) if roundChange && c.isProposer() && c.current != nil { @@ -296,12 +309,12 @@ func (c *core) startNewRound(round *big.Int) { } c.newRoundChangeTimer() - logger.Debug("New round", "new_round", newView.Round, "new_seq", newView.Sequence, "new_proposer", c.valSet.GetProposer(), "isProposer", c.isProposer()) - logger.Trace("New round", "new_round", newView.Round, "new_seq", newView.Sequence, "size", c.valSet.Size(), "valSet", c.valSet.List()) + logger.Debug("New round", "new_round", newView.Round, "new_seq", newView.Sequence, "new_proposer", c.currentCommittee.Proposer(), "isProposer", c.isProposer()) + logger.Trace("New round", "new_round", newView.Round, "new_seq", newView.Sequence, "size", len(c.currentCommittee.Qualified()), "valSet", c.currentCommittee.Qualified().ToString()) } func (c *core) catchUpRound(view *istanbul.View) { - logger := c.logger.NewWith("old_round", c.current.Round(), "old_seq", c.current.Sequence(), "old_proposer", c.valSet.GetProposer()) + logger := c.logger.NewWith("old_round", c.current.Round(), "old_seq", c.current.Sequence(), "old_proposer", c.currentCommittee.Proposer()) if view.Round.Cmp(c.current.Round()) > 0 { c.roundMeter.Mark(new(big.Int).Sub(view.Round, c.current.Round()).Int64()) @@ -309,24 +322,24 @@ func (c *core) catchUpRound(view *istanbul.View) { c.waitingForRoundChange = true // Need to keep block locked for round catching up - c.updateRoundState(view, c.valSet, true) + c.updateRoundState(view, c.currentCommittee, true) c.roundChangeSet.Clear(view.Round) c.newRoundChangeTimer() - logger.Warn("[RC] Catch up round", "new_round", view.Round, "new_seq", view.Sequence, "new_proposer", c.valSet.GetProposer()) + logger.Warn("[RC] Catch up round", "new_round", view.Round, "new_seq", view.Sequence, "new_proposer", c.currentCommittee.Proposer()) } // updateRoundState updates round state by checking if locking block is necessary -func (c *core) updateRoundState(view *istanbul.View, validatorSet istanbul.ValidatorSet, roundChange bool) { +func (c *core) updateRoundState(view *istanbul.View, cState *istanbul.RoundCommitteeState, roundChange bool) { // Lock only if both roundChange is true and it is locked if roundChange && c.current != nil { if c.current.IsHashLocked() { - c.current = newRoundState(view, validatorSet, c.current.GetLockedHash(), c.current.Preprepare, c.current.pendingRequest, c.backend.HasBadProposal) + c.current = newRoundState(view, cState.ValSet(), c.current.GetLockedHash(), c.current.Preprepare, c.current.pendingRequest, c.backend.HasBadProposal) } else { - c.current = newRoundState(view, validatorSet, common.Hash{}, nil, c.current.pendingRequest, c.backend.HasBadProposal) + c.current = newRoundState(view, cState.ValSet(), common.Hash{}, nil, c.current.pendingRequest, c.backend.HasBadProposal) } } else { - c.current = newRoundState(view, validatorSet, common.Hash{}, nil, nil, c.backend.HasBadProposal) + c.current = newRoundState(view, cState.ValSet(), common.Hash{}, nil, nil, c.backend.HasBadProposal) } c.currentRoundGauge.Update(c.current.round.Int64()) if c.current.IsHashLocked() { @@ -376,7 +389,7 @@ func (c *core) newRoundChangeTimer() { } current := c.current - proposer := c.valSet.GetProposer() + proposer := c.currentCommittee.Proposer() c.roundChangeTimer.Store(time.AfterFunc(timeout, func() { var loc, proposerStr string @@ -386,7 +399,7 @@ func (c *core) newRoundChangeTimer() { } else { loc = "catchUpRound" } - if proposer == nil { + if common.EmptyAddress(proposer) { proposerStr = "" } else { proposerStr = proposer.String() @@ -418,7 +431,7 @@ func (c *core) newRoundChangeTimer() { } func (c *core) checkValidatorSignature(data []byte, sig []byte) (common.Address, error) { - return istanbul.CheckValidatorSignature(c.valSet, data, sig) + return c.currentCommittee.CheckValidatorSignature(data, sig) } // PrepareCommittedSeal returns a committed seal for the given hash @@ -428,20 +441,3 @@ func PrepareCommittedSeal(hash common.Hash) []byte { buf.Write([]byte{byte(msgCommit)}) return buf.Bytes() } - -// Minimum required number of consensus messages to proceed -func RequiredMessageCount(valSet istanbul.ValidatorSet) int { - var size uint64 - if valSet.IsSubSet() { - size = valSet.SubGroupSize() - } else { - size = valSet.Size() - } - // For less than 4 validators, quorum size equals validator count. - if size < 4 { - return int(size) - } - // Adopted QBFT quorum implementation - // https://github.com/Consensys/quorum/blob/master/consensus/istanbul/qbft/core/core.go#L312 - return int(math.Ceil(float64(2*size) / 3)) -} diff --git a/consensus/istanbul/core/handler.go b/consensus/istanbul/core/handler.go index c7aee5934..642301133 100644 --- a/consensus/istanbul/core/handler.go +++ b/consensus/istanbul/core/handler.go @@ -102,23 +102,22 @@ func (c *core) handleEvents() { } case istanbul.MessageEvent: if err := c.handleMsg(ev.Payload); err == nil { - c.backend.GossipSubPeer(ev.Hash, c.valSet, ev.Payload) + c.backend.GossipSubPeer(ev.Hash, ev.Payload) // c.backend.Gossip(c.valSet, ev.Payload) } case backlogEvent: - _, src := c.valSet.GetByAddress(ev.src) - if src == nil { + if !c.currentCommittee.IsQualifiedMember(ev.src) { c.logger.Error("Invalid address in valSet", "addr", ev.src) continue } // No need to check signature for internal messages - if err := c.handleCheckedMsg(ev.msg, src); err == nil { + if err := c.handleCheckedMsg(ev.msg, ev.src); err == nil { p, err := ev.msg.Payload() if err != nil { c.logger.Warn("Get message payload failed", "err", err) continue } - c.backend.GossipSubPeer(ev.Hash, c.valSet, p) + c.backend.GossipSubPeer(ev.Hash, p) // c.backend.Gossip(c.valSet, p) } } @@ -177,16 +176,15 @@ func (c *core) handleMsg(payload []byte) error { } // Only accept message if the address is valid - _, src := c.valSet.GetByAddress(msg.Address) - if src == nil { + if !c.currentCommittee.IsQualifiedMember(msg.Address) { logger.Error("Invalid address in message", "msg", msg) return istanbul.ErrUnauthorizedAddress } - return c.handleCheckedMsg(msg, src) + return c.handleCheckedMsg(msg, msg.Address) } -func (c *core) handleCheckedMsg(msg *message, src istanbul.Validator) error { +func (c *core) handleCheckedMsg(msg *message, src common.Address) error { logger := c.logger.NewWith("address", c.address, "from", src) // Store the message if it's a future message @@ -238,7 +236,7 @@ func (c *core) handleTimeoutMsg(nextView *istanbul.View) { // the max round with F+1 round change message. We only need to catch up // if the max round is larger than current round. if !c.waitingForRoundChange { - maxRound := c.roundChangeSet.MaxRound(c.valSet.F() + 1) + maxRound := c.roundChangeSet.MaxRound(c.currentCommittee.F() + 1) if maxRound != nil && maxRound.Cmp(c.current.Round()) > 0 { logger.Warn("[RC] Send round change because of timeout event") c.sendRoundChange(maxRound) diff --git a/consensus/istanbul/core/handler_test.go b/consensus/istanbul/core/handler_test.go index d42a60da0..2dc9fdc5a 100644 --- a/consensus/istanbul/core/handler_test.go +++ b/consensus/istanbul/core/handler_test.go @@ -26,12 +26,10 @@ import ( "time" "github.com/golang/mock/gomock" - "github.com/kaiachain/kaia/blockchain" "github.com/kaiachain/kaia/blockchain/types" "github.com/kaiachain/kaia/common" "github.com/kaiachain/kaia/consensus/istanbul" mock_istanbul "github.com/kaiachain/kaia/consensus/istanbul/mocks" - "github.com/kaiachain/kaia/consensus/istanbul/validator" "github.com/kaiachain/kaia/crypto" "github.com/kaiachain/kaia/crypto/sha3" "github.com/kaiachain/kaia/event" @@ -69,16 +67,26 @@ func newMockBackend(t *testing.T, validatorAddrs []common.Address) (*mock_istanb }) eventMux := new(event.TypeMux) - validatorSet := validator.NewWeightedCouncil(validatorAddrs, nil, validatorAddrs, nil, nil, - istanbul.WeightedRandom, committeeSize, 0, 0, &blockchain.BlockChain{}) mockCtrl := gomock.NewController(t) + mockValset := istanbul.NewBlockValSet(validatorAddrs, validatorAddrs) mockBackend := mock_istanbul.NewMockBackend(mockCtrl) // Consider the last proposal is "initBlock" and the owner of mockBackend is validatorAddrs[0] + mockBackend.EXPECT().Address().Return(validatorAddrs[0]).AnyTimes() mockBackend.EXPECT().LastProposal().Return(initBlock, validatorAddrs[0]).AnyTimes() - mockBackend.EXPECT().Validators(initBlock).Return(validatorSet).AnyTimes() + mockBackend.EXPECT().GetCommitteeStateByRound(gomock.Any(), gomock.Any()).DoAndReturn( + func(num uint64, round uint64) (*istanbul.RoundCommitteeState, error) { + if round == 2 { + return istanbul.NewRoundCommitteeState( + mockValset, committeeSize, validatorAddrs[2:committeeSize+2], validatorAddrs[0], + ), nil + } + return istanbul.NewRoundCommitteeState( + mockValset, committeeSize, validatorAddrs[0:committeeSize], validatorAddrs[0], + ), nil + }).AnyTimes() mockBackend.EXPECT().NodeType().Return(common.CONSENSUSNODE).AnyTimes() // Set an eventMux in which istanbul core will subscribe istanbul events @@ -89,8 +97,8 @@ func newMockBackend(t *testing.T, validatorAddrs []common.Address) (*mock_istanb // Always return nil for broadcasting related functions mockBackend.EXPECT().Sign(gomock.Any()).Return(nil, nil).AnyTimes() - mockBackend.EXPECT().Broadcast(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() - mockBackend.EXPECT().GossipSubPeer(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() + mockBackend.EXPECT().Broadcast(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() + mockBackend.EXPECT().GossipSubPeer(gomock.Any(), gomock.Any()).Return().AnyTimes() // Verify checks whether the proposal of the preprepare message is a valid block. Consider it valid. mockBackend.EXPECT().Verify(gomock.Any()).Return(time.Duration(0), nil).AnyTimes() @@ -111,31 +119,6 @@ func genValidators(n int) ([]common.Address, map[common.Address]*ecdsa.PrivateKe return addrs, keyMap } -// getRandomValidator selects a validator in the given validator set. -// `isCommittee` determines whether it returns a committee or a non-committee. -func getRandomValidator(isCommittee bool, valSet istanbul.ValidatorSet, prevHash common.Hash, view *istanbul.View) istanbul.Validator { - committee := valSet.SubList(prevHash, view) - - if isCommittee { - return committee[rand.Int()%(len(committee)-1)] - } - - for _, val := range valSet.List() { - for _, com := range committee { - if val.Address() == com.Address() { - isCommittee = true - } - } - if !isCommittee { - return val - } - isCommittee = false - } - - // it should not be happened - return nil -} - // signBlock signs the given block with the given private key func signBlock(block *types.Block, privateKey *ecdsa.PrivateKey) (*types.Block, error) { var hash common.Hash @@ -274,19 +257,20 @@ func TestCore_handleEvents_scenario_invalidSender(t *testing.T) { eventMux := mockBackend.EventMux() lastProposal, _ := mockBackend.LastProposal() lastBlock := lastProposal.(*types.Block) - validators := mockBackend.Validators(lastBlock) + cState, err := mockBackend.GetCommitteeStateByRound(istCore.currentView().Sequence.Uint64(), istCore.currentView().Round.Uint64()) + assert.NoError(t, err) // Preprepare message originated from invalid sender { - msgSender := getRandomValidator(false, validators, lastBlock.Hash(), istCore.currentView()) - msgSenderKey := validatorKeyMap[msgSender.Address()] + msgSender := cState.NonCommittee()[rand.Int()%(len(cState.NonCommittee())-1)] + msgSenderKey := validatorKeyMap[msgSender] newProposal, err := genBlock(lastBlock, msgSenderKey) if err != nil { t.Fatal(err) } - istanbulMsg, err := genIstanbulMsg(msgPreprepare, lastProposal.Hash(), newProposal, msgSender.Address(), msgSenderKey) + istanbulMsg, err := genIstanbulMsg(msgPreprepare, lastProposal.Hash(), newProposal, msgSender, msgSenderKey) if err != nil { t.Fatal(err) } @@ -301,15 +285,15 @@ func TestCore_handleEvents_scenario_invalidSender(t *testing.T) { // Preprepare message originated from valid sender and set a new proposal in the istanbul core { - msgSender := validators.GetProposer() - msgSenderKey := validatorKeyMap[msgSender.Address()] + msgSender := cState.Proposer() + msgSenderKey := validatorKeyMap[msgSender] newProposal, err := genBlock(lastBlock, msgSenderKey) if err != nil { t.Fatal(err) } - istanbulMsg, err := genIstanbulMsg(msgPreprepare, lastBlock.Hash(), newProposal, msgSender.Address(), msgSenderKey) + istanbulMsg, err := genIstanbulMsg(msgPreprepare, lastBlock.Hash(), newProposal, msgSender, msgSenderKey) if err != nil { t.Fatal(err) } @@ -319,15 +303,16 @@ func TestCore_handleEvents_scenario_invalidSender(t *testing.T) { } time.Sleep(time.Second) - assert.Equal(t, istCore.current.Preprepare.Proposal.Header().String(), newProposal.Header().String()) + currentHeader, proposalHeader := istCore.current.Preprepare.Proposal.Header().String(), newProposal.Header().String() + assert.Equal(t, currentHeader, proposalHeader) } // Prepare message originated from invalid sender { - msgSender := getRandomValidator(false, validators, lastBlock.Hash(), istCore.currentView()) - msgSenderKey := validatorKeyMap[msgSender.Address()] + msgSender := cState.NonCommittee()[rand.Int()%(len(cState.NonCommittee())-1)] + msgSenderKey := validatorKeyMap[msgSender] - istanbulMsg, err := genIstanbulMsg(msgPrepare, lastBlock.Hash(), istCore.current.Preprepare.Proposal.(*types.Block), msgSender.Address(), msgSenderKey) + istanbulMsg, err := genIstanbulMsg(msgPrepare, lastBlock.Hash(), istCore.current.Preprepare.Proposal.(*types.Block), msgSender, msgSenderKey) if err != nil { t.Fatal(err) } @@ -342,10 +327,10 @@ func TestCore_handleEvents_scenario_invalidSender(t *testing.T) { // Prepare message originated from valid sender { - msgSender := getRandomValidator(true, validators, lastBlock.Hash(), istCore.currentView()) - msgSenderKey := validatorKeyMap[msgSender.Address()] + msgSender := cState.Committee()[rand.Int()%(len(cState.Committee())-1)] + msgSenderKey := validatorKeyMap[msgSender] - istanbulMsg, err := genIstanbulMsg(msgPrepare, lastBlock.Hash(), istCore.current.Preprepare.Proposal.(*types.Block), msgSender.Address(), msgSenderKey) + istanbulMsg, err := genIstanbulMsg(msgPrepare, lastBlock.Hash(), istCore.current.Preprepare.Proposal.(*types.Block), msgSender, msgSenderKey) if err != nil { t.Fatal(err) } @@ -360,10 +345,10 @@ func TestCore_handleEvents_scenario_invalidSender(t *testing.T) { // Commit message originated from invalid sender { - msgSender := getRandomValidator(false, validators, lastBlock.Hash(), istCore.currentView()) - msgSenderKey := validatorKeyMap[msgSender.Address()] + msgSender := cState.NonCommittee()[rand.Int()%(len(cState.NonCommittee())-1)] + msgSenderKey := validatorKeyMap[msgSender] - istanbulMsg, err := genIstanbulMsg(msgCommit, lastBlock.Hash(), istCore.current.Preprepare.Proposal.(*types.Block), msgSender.Address(), msgSenderKey) + istanbulMsg, err := genIstanbulMsg(msgCommit, lastBlock.Hash(), istCore.current.Preprepare.Proposal.(*types.Block), msgSender, msgSenderKey) if err != nil { t.Fatal(err) } @@ -378,10 +363,10 @@ func TestCore_handleEvents_scenario_invalidSender(t *testing.T) { // Commit message originated from valid sender { - msgSender := getRandomValidator(true, validators, lastBlock.Hash(), istCore.currentView()) - msgSenderKey := validatorKeyMap[msgSender.Address()] + msgSender := cState.Committee()[rand.Int()%(len(cState.Committee())-1)] + msgSenderKey := validatorKeyMap[msgSender] - istanbulMsg, err := genIstanbulMsg(msgCommit, lastBlock.Hash(), istCore.current.Preprepare.Proposal.(*types.Block), msgSender.Address(), msgSenderKey) + istanbulMsg, err := genIstanbulMsg(msgCommit, lastBlock.Hash(), istCore.current.Preprepare.Proposal.(*types.Block), msgSender, msgSenderKey) if err != nil { t.Fatal(err) } @@ -414,10 +399,10 @@ func TestCore_handleEvents_scenario_invalidSender(t *testing.T) { // RoundChange message originated from valid sender { - msgSender := getRandomValidator(true, validators, lastBlock.Hash(), istCore.currentView()) - msgSenderKey := validatorKeyMap[msgSender.Address()] + msgSender := cState.NonCommittee()[rand.Int()%(len(cState.NonCommittee())-1)] + msgSenderKey := validatorKeyMap[msgSender] - istanbulMsg, err := genIstanbulMsg(msgRoundChange, lastBlock.Hash(), istCore.current.Preprepare.Proposal.(*types.Block), msgSender.Address(), msgSenderKey) + istanbulMsg, err := genIstanbulMsg(msgRoundChange, lastBlock.Hash(), istCore.current.Preprepare.Proposal.(*types.Block), msgSender, msgSenderKey) if err != nil { t.Fatal(err) } @@ -439,7 +424,7 @@ func TestCore_handlerMsg(t *testing.T) { mockBackend, mockCtrl := newMockBackend(t, validatorAddrs) defer mockCtrl.Finish() - istConfig := istanbul.DefaultConfig + istConfig := istanbul.DefaultConfig.Copy() istConfig.ProposerPolicy = istanbul.WeightedRandom istCore := New(mockBackend, istConfig).(*core) @@ -450,7 +435,7 @@ func TestCore_handlerMsg(t *testing.T) { lastProposal, _ := mockBackend.LastProposal() lastBlock := lastProposal.(*types.Block) - validators := mockBackend.Validators(lastBlock) + cState, _ := mockBackend.GetCommitteeStateByRound(lastBlock.NumberU64()+1, 0) // invalid format { @@ -481,15 +466,15 @@ func TestCore_handlerMsg(t *testing.T) { // valid message { - msgSender := validators.GetProposer() - msgSenderKey := validatorKeyMap[msgSender.Address()] + msgSender := cState.Proposer() + msgSenderKey := validatorKeyMap[msgSender] newProposal, err := genBlock(lastBlock, msgSenderKey) if err != nil { t.Fatal(err) } - istanbulMsg, err := genIstanbulMsg(msgPreprepare, lastBlock.Hash(), newProposal, msgSender.Address(), msgSenderKey) + istanbulMsg, err := genIstanbulMsg(msgPreprepare, lastBlock.Hash(), newProposal, msgSender, msgSenderKey) if err != nil { t.Fatal(err) } @@ -517,11 +502,11 @@ func enableLog() { // splitSubList splits a committee into two groups w/o proposer // one for n nodes, the other for len(committee) - n - 1 nodes -func splitSubList(committee []istanbul.Validator, n int, proposerAddr common.Address) ([]istanbul.Validator, []istanbul.Validator) { - var subCN, remainingCN []istanbul.Validator +func splitSubList(committee []common.Address, n int, proposerAddr common.Address) ([]common.Address, []common.Address) { + var subCN, remainingCN []common.Address for _, val := range committee { - if val.Address() == proposerAddr { + if val == proposerAddr { // proposer is not included in any group continue } @@ -559,9 +544,9 @@ func simulateMaliciousCN(t *testing.T, numValidators int, numMalicious int) Stat // malProposal is an incorrect block that malicious CNs use to try stop consensus lastProposal, _ = mockBackend.LastProposal() lastBlock = lastProposal.(*types.Block) - validators = mockBackend.Validators(lastBlock) - proposer = validators.GetProposer() - proposerKey = validatorKeyMap[proposer.Address()] + cState, _ = mockBackend.GetCommitteeStateByRound(lastBlock.NumberU64()+1, 0) + proposer = cState.Proposer() + proposerKey = validatorKeyMap[proposer] // the proposer generates a block as newProposal // malicious CNs does not accept the proposer's block and use malProposal's hash value for consensus newProposal, _ = genBlockParams(lastBlock, proposerKey, 0, 1, 1) @@ -572,14 +557,13 @@ func simulateMaliciousCN(t *testing.T, numValidators int, numMalicious int) Stat istConfig := istanbul.DefaultConfig istConfig.ProposerPolicy = istanbul.WeightedRandom istCore := New(mockBackend, istConfig).(*core) - err := istCore.Start() - require.Nil(t, err) + require.Nil(t, istCore.Start()) defer istCore.Stop() // Step 1 - Pre-prepare with correct message // Create pre-prepare message - istanbulMsg, err := genIstanbulMsg(msgPreprepare, lastBlock.Hash(), newProposal, proposer.Address(), proposerKey) + istanbulMsg, err := genIstanbulMsg(msgPreprepare, lastBlock.Hash(), newProposal, proposer, proposerKey) require.Nil(t, err) // Handle pre-prepare message @@ -587,15 +571,14 @@ func simulateMaliciousCN(t *testing.T, numValidators int, numMalicious int) Stat require.Nil(t, err) // splitSubList split current committee into benign CNs and malicious CNs - subList := validators.SubList(lastBlock.Hash(), istCore.currentView()) - maliciousCNs, benignCNs := splitSubList(subList, numMalicious, proposer.Address()) + subList := cState.Committee() + maliciousCNs, benignCNs := splitSubList(subList, numMalicious, proposer) benignCNs = append(benignCNs, proposer) // Shortcut for sending consensus message to everyone in `CNList` - sendMessages := func(state uint64, proposal *types.Block, CNList []istanbul.Validator) { + sendMessages := func(state uint64, proposal *types.Block, CNList []common.Address) { for _, val := range CNList { - valKey := validatorKeyMap[val.Address()] - istanbulMsg, err := genIstanbulMsg(state, lastBlock.Hash(), proposal, val.Address(), valKey) + istanbulMsg, err = genIstanbulMsg(state, lastBlock.Hash(), proposal, val, validatorKeyMap[val]) assert.Nil(t, err) err = istCore.handleMsg(istanbulMsg.Payload) // assert.Nil(t, err) @@ -652,9 +635,9 @@ func simulateChainSplit(t *testing.T, numValidators int) (State, State) { var ( lastProposal, _ = mockBackend.LastProposal() lastBlock = lastProposal.(*types.Block) - validators = mockBackend.Validators(lastBlock) - proposer = validators.GetProposer() - proposerKey = validatorKeyMap[proposer.Address()] + cState, _ = mockBackend.GetCommitteeStateByRound(lastBlock.NumberU64()+1, 0) + proposer = cState.Proposer() + proposerKey = validatorKeyMap[proposer] ) // Start istanbul core @@ -675,8 +658,7 @@ func simulateChainSplit(t *testing.T, numValidators int) (State, State) { // the number of group size is (numValidators-1/2) + 1 // groupA consists of proposer, coreA, unnamed node(s) // groupB consists of proposer, coreB, unnamed node(s) - subList := validators.SubList(lastBlock.Hash(), coreProposer.currentView()) - groupA, groupB := splitSubList(subList, (numValidators-1)/2, proposer.Address()) + groupA, groupB := splitSubList(cState.Committee(), (numValidators-1)/2, proposer) groupA = append(groupA, proposer) groupB = append(groupB, proposer) @@ -688,14 +670,14 @@ func simulateChainSplit(t *testing.T, numValidators int) (State, State) { assert.Nil(t, err) // Shortcut for sending message `proposal` to core `c` - sendMessages := func(state uint64, proposal *types.Block, CNList []istanbul.Validator, c *core) { + sendMessages := func(state uint64, proposal *types.Block, CNList []common.Address, c *core) { for _, val := range CNList { - valKey := validatorKeyMap[val.Address()] + valKey := validatorKeyMap[val] if state == msgPreprepare { - istanbulMsg, _ := genIstanbulMsg(state, lastBlock.Hash(), proposal, proposer.Address(), valKey) + istanbulMsg, _ := genIstanbulMsg(state, lastBlock.Hash(), proposal, proposer, valKey) err = c.handleMsg(istanbulMsg.Payload) } else { - istanbulMsg, _ := genIstanbulMsg(state, lastBlock.Hash(), proposal, val.Address(), valKey) + istanbulMsg, _ := genIstanbulMsg(state, lastBlock.Hash(), proposal, val, valKey) err = c.handleMsg(istanbulMsg.Payload) } if err != nil { diff --git a/consensus/istanbul/core/message_set.go b/consensus/istanbul/core/message_set.go index c58922cbe..67d2c3e70 100644 --- a/consensus/istanbul/core/message_set.go +++ b/consensus/istanbul/core/message_set.go @@ -33,7 +33,7 @@ import ( ) // Construct a new message set to accumulate messages for given sequence/view number. -func newMessageSet(valSet istanbul.ValidatorSet) *messageSet { +func newMessageSet(valSet *istanbul.BlockValSet) *messageSet { return &messageSet{ view: &istanbul.View{ Round: new(big.Int), @@ -49,7 +49,7 @@ func newMessageSet(valSet istanbul.ValidatorSet) *messageSet { type messageSet struct { view *istanbul.View - valSet istanbul.ValidatorSet + valSet *istanbul.BlockValSet messagesMu *sync.Mutex messages map[common.Address]*message } @@ -107,7 +107,7 @@ func (ms *messageSet) Get(addr common.Address) *message { func (ms *messageSet) verify(msg *message) error { // verify if the message comes from one of the validators - if _, v := ms.valSet.GetByAddress(msg.Address); v == nil { + if !ms.valSet.IsQualifiedMember(msg.Address) { return istanbul.ErrUnauthorizedAddress } diff --git a/consensus/istanbul/core/prepare.go b/consensus/istanbul/core/prepare.go index f447c774c..a9ab844aa 100644 --- a/consensus/istanbul/core/prepare.go +++ b/consensus/istanbul/core/prepare.go @@ -23,20 +23,19 @@ package core import ( + "github.com/kaiachain/kaia/common" "github.com/kaiachain/kaia/consensus/istanbul" ) func (c *core) sendPrepare() { logger := c.logger.NewWith("state", c.state) - sub := c.current.Subject() - prevHash := c.current.Proposal().ParentHash() - - // Do not send message if the owner of the core is not a member of the committee for the `sub.View` - if !c.valSet.CheckInSubList(prevHash, sub.View, c.Address()) { + // Do not send message if the owner of the core is not a member of the committee for the current view + if !c.currentCommittee.IsCommitteeMember(c.Address()) { return } + sub := c.current.Subject() encodedSubject, err := Encode(sub) if err != nil { logger.Error("Failed to encode", "subject", sub) @@ -44,13 +43,13 @@ func (c *core) sendPrepare() { } c.broadcast(&message{ - Hash: prevHash, + Hash: c.current.Proposal().ParentHash(), Code: msgPrepare, Msg: encodedSubject, }) } -func (c *core) handlePrepare(msg *message, src istanbul.Validator) error { +func (c *core) handlePrepare(msg *message, src common.Address) error { // Decode PREPARE message var prepare *istanbul.Subject err := msg.Decode(&prepare) @@ -70,9 +69,9 @@ func (c *core) handlePrepare(msg *message, src istanbul.Validator) error { return err } - if !c.valSet.CheckInSubList(msg.Hash, prepare.View, src.Address()) { + if !c.currentCommittee.IsCommitteeMember(src) { logger.Warn("received an istanbul prepare message from non-committee", - "currentSequence", c.current.sequence.Uint64(), "sender", src.Address().String(), "msgView", prepare.View.String()) + "currentSequence", c.current.sequence.Uint64(), "sender", src.String(), "msgView", prepare.View.String()) return errNotFromCommittee } @@ -87,8 +86,10 @@ func (c *core) handlePrepare(msg *message, src istanbul.Validator) error { logger.Warn("received prepare of the hash locked proposal and change state to prepared", "msgType", msgPrepare) c.setState(StatePrepared) c.sendCommit() - } else if c.current.GetPrepareOrCommitSize() >= RequiredMessageCount(c.valSet) { - logger.Info("received a quorum of the messages and change state to prepared", "msgType", msgPrepare, "prepareMsgNum", c.current.Prepares.Size(), "commitMsgNum", c.current.Commits.Size(), "valSet", c.valSet.Size()) + } else if c.current.GetPrepareOrCommitSize() >= c.currentCommittee.RequiredMessageCount() { + logger.Info("received a quorum of the messages and change state to prepared", "msgType", msgPrepare, + "prepareMsgNum", c.current.Prepares.Size(), "commitMsgNum", c.current.Commits.Size(), + "valSet", len(c.currentCommittee.Qualified())) c.current.LockHash() c.setState(StatePrepared) c.sendCommit() @@ -99,7 +100,7 @@ func (c *core) handlePrepare(msg *message, src istanbul.Validator) error { } // verifyPrepare verifies if the received PREPARE message is equivalent to our subject -func (c *core) verifyPrepare(prepare *istanbul.Subject, src istanbul.Validator) error { +func (c *core) verifyPrepare(prepare *istanbul.Subject, src common.Address) error { logger := c.logger.NewWith("from", src, "state", c.state) sub := c.current.Subject() @@ -111,7 +112,7 @@ func (c *core) verifyPrepare(prepare *istanbul.Subject, src istanbul.Validator) return nil } -func (c *core) acceptPrepare(msg *message, src istanbul.Validator) error { +func (c *core) acceptPrepare(msg *message, src common.Address) error { logger := c.logger.NewWith("from", src, "state", c.state) // Add the PREPARE message to current round state diff --git a/consensus/istanbul/core/prepare_test.go b/consensus/istanbul/core/prepare_test.go index 7206958b5..104d864ea 100644 --- a/consensus/istanbul/core/prepare_test.go +++ b/consensus/istanbul/core/prepare_test.go @@ -26,7 +26,6 @@ import ( "github.com/kaiachain/kaia/blockchain/types" "github.com/kaiachain/kaia/common" "github.com/kaiachain/kaia/consensus/istanbul" - mock_istanbul "github.com/kaiachain/kaia/consensus/istanbul/mocks" "github.com/kaiachain/kaia/fork" "github.com/kaiachain/kaia/params" "github.com/stretchr/testify/assert" @@ -37,67 +36,39 @@ func TestCore_sendPrepare(t *testing.T) { defer fork.ClearHardForkBlockNumberConfig() validatorAddrs, validatorKeyMap := genValidators(6) - mockBackend, mockCtrl := newMockBackend(t, validatorAddrs) - istConfig := istanbul.DefaultConfig - istConfig.ProposerPolicy = istanbul.WeightedRandom - - istCore := New(mockBackend, istConfig).(*core) - if err := istCore.Start(); err != nil { - t.Fatal(err) - } - defer istCore.Stop() - - lastProposal, lastProposer := mockBackend.LastProposal() - proposal, err := genBlock(lastProposal.(*types.Block), validatorKeyMap[validatorAddrs[0]]) - if err != nil { - t.Fatal(err) - } - - istCore.current.Preprepare = &istanbul.Preprepare{ - View: istCore.currentView(), - Proposal: proposal, - } - - mockCtrl.Finish() - - // invalid case - not committee - { - // Increase round number until the owner of istanbul.core is not a member of the committee - for istCore.valSet.CheckInSubList(lastProposal.Hash(), istCore.currentView(), istCore.Address()) { - istCore.current.round.Add(istCore.current.round, common.Big1) - istCore.valSet.CalcProposer(lastProposer, istCore.current.round.Uint64()) + for _, tc := range []struct { + tcName string + round int64 + valid bool + }{ + {"valid case", 0, true}, + {"invalid case - not committee", 2, false}, + } { + mockBackend, mockCtrl := newMockBackend(t, validatorAddrs) + if tc.valid { + mockBackend.EXPECT().Sign(gomock.Any()).Return(nil, nil).AnyTimes() + mockBackend.EXPECT().Broadcast(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() } - mockCtrl := gomock.NewController(t) - mockBackend := mock_istanbul.NewMockBackend(mockCtrl) - mockBackend.EXPECT().Sign(gomock.Any()).Return(nil, nil).Times(0) - mockBackend.EXPECT().Broadcast(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(0) + istConfig := istanbul.DefaultConfig.Copy() + istConfig.ProposerPolicy = istanbul.WeightedRandom - istCore.backend = mockBackend - istCore.sendPrepare() + istCore := New(mockBackend, istConfig).(*core) + assert.NoError(t, istCore.Start()) - // methods of mockBackend should be executed given times - mockCtrl.Finish() - } + lastProposal, _ := mockBackend.LastProposal() + proposal, err := genBlock(lastProposal.(*types.Block), validatorKeyMap[validatorAddrs[0]]) + assert.NoError(t, err) - // valid case - { - // Increase round number until the owner of istanbul.core become a member of the committee - for !istCore.valSet.CheckInSubList(lastProposal.Hash(), istCore.currentView(), istCore.Address()) { - istCore.current.round.Add(istCore.current.round, common.Big1) - istCore.valSet.CalcProposer(lastProposer, istCore.current.round.Uint64()) + istCore.current.round.Set(big.NewInt(tc.round)) + istCore.current.Preprepare = &istanbul.Preprepare{ + View: istCore.currentView(), + Proposal: proposal, } - mockCtrl := gomock.NewController(t) - mockBackend := mock_istanbul.NewMockBackend(mockCtrl) - mockBackend.EXPECT().Sign(gomock.Any()).Return(nil, nil).Times(1) - mockBackend.EXPECT().Broadcast(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(1) - - istCore.backend = mockBackend istCore.sendPrepare() - - // methods of mockBackend should be executed given times + istCore.Stop() mockCtrl.Finish() } } diff --git a/consensus/istanbul/core/preprepare.go b/consensus/istanbul/core/preprepare.go index 75b232a22..2f2d6f952 100644 --- a/consensus/istanbul/core/preprepare.go +++ b/consensus/istanbul/core/preprepare.go @@ -26,6 +26,7 @@ import ( "time" "github.com/kaiachain/kaia/blockchain/types" + "github.com/kaiachain/kaia/common" "github.com/kaiachain/kaia/consensus" "github.com/kaiachain/kaia/consensus/istanbul" ) @@ -56,7 +57,7 @@ func (c *core) sendPreprepare(request *istanbul.Request) { } } -func (c *core) handlePreprepare(msg *message, src istanbul.Validator) error { +func (c *core) handlePreprepare(msg *message, src common.Address) error { logger := c.logger.NewWith("from", src, "state", c.state) // Decode PRE-PREPARE @@ -72,13 +73,14 @@ func (c *core) handlePreprepare(msg *message, src istanbul.Validator) error { if err := c.checkMessage(msgPreprepare, preprepare.View); err != nil { if err == errOldMessage { // Get validator set for the given proposal - valSet := c.backend.ParentValidators(preprepare.Proposal).Copy() - previousProposer := c.backend.GetProposer(preprepare.Proposal.Number().Uint64() - 1) - valSet.CalcProposer(previousProposer, preprepare.View.Round.Uint64()) + councilState, getCouncilError := c.backend.GetCommitteeStateByRound(preprepare.View.Sequence.Uint64(), preprepare.View.Round.Uint64()) + if getCouncilError != nil { + return getCouncilError + } // Broadcast COMMIT if it is an existing block // 1. The proposer needs to be a proposer matches the given (Sequence + Round) // 2. The given block must exist - if valSet.IsProposer(src.Address()) && c.backend.HasPropsal(preprepare.Proposal.Hash(), preprepare.Proposal.Number()) { + if councilState.IsProposer(src) && c.backend.HasPropsal(preprepare.Proposal.Hash(), preprepare.Proposal.Number()) { c.sendCommitForOldBlock(preprepare.View, preprepare.Proposal.Hash(), preprepare.Proposal.ParentHash()) return nil } @@ -87,7 +89,7 @@ func (c *core) handlePreprepare(msg *message, src istanbul.Validator) error { } // Check if the message comes from current proposer - if !c.valSet.IsProposer(src.Address()) { + if !c.currentCommittee.IsProposer(src) { logger.Warn("Ignore preprepare messages from non-proposer") return errNotFromProposer } @@ -100,7 +102,7 @@ func (c *core) handlePreprepare(msg *message, src istanbul.Validator) error { c.stopFuturePreprepareTimer() c.futurePreprepareTimer = time.AfterFunc(duration, func() { c.sendEvent(backlogEvent{ - src: src.Address(), + src: src, msg: msg, Hash: msg.Hash, }) @@ -128,7 +130,7 @@ func (c *core) handlePreprepare(msg *message, src istanbul.Validator) error { if vrank != nil { vrank.Log() } - vrank = NewVrank(*c.currentView(), c.valSet.SubList(preprepare.Proposal.ParentHash(), c.currentView())) + vrank = NewVrank(*c.currentView(), c.currentCommittee.Committee()) } else { // Send round change c.sendNextRoundChange("handlePreprepare. HashLocked, but received hash is different from locked hash") @@ -144,7 +146,7 @@ func (c *core) handlePreprepare(msg *message, src istanbul.Validator) error { if vrank != nil { vrank.Log() } - vrank = NewVrank(*c.currentView(), c.valSet.SubList(preprepare.Proposal.ParentHash(), c.currentView())) + vrank = NewVrank(*c.currentView(), c.currentCommittee.Committee()) } } diff --git a/consensus/istanbul/core/request.go b/consensus/istanbul/core/request.go index 8fcbfd638..941172793 100644 --- a/consensus/istanbul/core/request.go +++ b/consensus/istanbul/core/request.go @@ -56,9 +56,10 @@ func (c *core) checkRequestMsg(request *istanbul.Request) error { return errInvalidMessage } - if c := c.current.sequence.Cmp(request.Proposal.Number()); c > 0 { + cmp := c.current.sequence.Cmp(request.Proposal.Number()) + if cmp > 0 { // request.Proposal.Number < c.current.sequence return errOldMessage - } else if c < 0 { + } else if cmp < 0 { // request.Proposer.Number > c.current.Sequence return errFutureMessage } else { return nil diff --git a/consensus/istanbul/core/roundchange.go b/consensus/istanbul/core/roundchange.go index 24859c051..2f050893e 100644 --- a/consensus/istanbul/core/roundchange.go +++ b/consensus/istanbul/core/roundchange.go @@ -84,8 +84,8 @@ func (c *core) sendRoundChange(round *big.Int) { }) } -func (c *core) handleRoundChange(msg *message, src istanbul.Validator) error { - logger := c.logger.NewWith("state", c.state, "from", src.Address().Hex()) +func (c *core) handleRoundChange(msg *message, src common.Address) error { + logger := c.logger.NewWith("state", c.state, "from", src.Hex()) // Decode ROUND CHANGE message var rc *istanbul.Subject @@ -115,8 +115,8 @@ func (c *core) handleRoundChange(msg *message, src istanbul.Validator) error { } var numCatchUp, numStartNewRound int - n := RequiredMessageCount(c.valSet) - f := int(c.valSet.F()) + n := c.currentCommittee.RequiredMessageCount() + f := c.currentCommittee.F() // N ROUND CHANGE messages can start new round. numStartNewRound = n // F + 1 ROUND CHANGE messages can start catch up the round. @@ -153,16 +153,16 @@ func (c *core) handleRoundChange(msg *message, src istanbul.Validator) error { // ---------------------------------------------------------------------------- -func newRoundChangeSet(valSet istanbul.ValidatorSet) *roundChangeSet { +func newRoundChangeSet(councilState *istanbul.BlockValSet) *roundChangeSet { return &roundChangeSet{ - validatorSet: valSet, + valSet: councilState, roundChanges: make(map[uint64]*messageSet), mu: new(sync.Mutex), } } type roundChangeSet struct { - validatorSet istanbul.ValidatorSet + valSet *istanbul.BlockValSet roundChanges map[uint64]*messageSet mu *sync.Mutex } @@ -174,7 +174,7 @@ func (rcs *roundChangeSet) Add(r *big.Int, msg *message) (int, error) { round := r.Uint64() if rcs.roundChanges[round] == nil { - rcs.roundChanges[round] = newMessageSet(rcs.validatorSet) + rcs.roundChanges[round] = newMessageSet(rcs.valSet) } err := rcs.roundChanges[round].Add(msg) if err != nil { diff --git a/consensus/istanbul/core/roundstate.go b/consensus/istanbul/core/roundstate.go index fc08b4229..75ebe0fa8 100644 --- a/consensus/istanbul/core/roundstate.go +++ b/consensus/istanbul/core/roundstate.go @@ -35,13 +35,13 @@ import ( // newRoundState creates a new roundState instance with the given view and validatorSet // lockedHash and preprepare are for round change when lock exists, // we need to keep a reference of preprepare in order to propose locked proposal when there is a lock and itself is the proposer -func newRoundState(view *istanbul.View, validatorSet istanbul.ValidatorSet, lockedHash common.Hash, preprepare *istanbul.Preprepare, pendingRequest *istanbul.Request, hasBadProposal func(hash common.Hash) bool) *roundState { +func newRoundState(view *istanbul.View, valSet *istanbul.BlockValSet, lockedHash common.Hash, preprepare *istanbul.Preprepare, pendingRequest *istanbul.Request, hasBadProposal func(hash common.Hash) bool) *roundState { return &roundState{ round: view.Round, sequence: view.Sequence, Preprepare: preprepare, - Prepares: newMessageSet(validatorSet), - Commits: newMessageSet(validatorSet), + Prepares: newMessageSet(valSet), + Commits: newMessageSet(valSet), lockedHash: lockedHash, mu: new(sync.RWMutex), pendingRequest: pendingRequest, diff --git a/consensus/istanbul/core/vrank.go b/consensus/istanbul/core/vrank.go index 15a63fed3..8450ee830 100644 --- a/consensus/istanbul/core/vrank.go +++ b/consensus/istanbul/core/vrank.go @@ -28,13 +28,14 @@ import ( "github.com/kaiachain/kaia/common" "github.com/kaiachain/kaia/consensus/istanbul" + "github.com/kaiachain/kaia/kaiax/valset" "github.com/rcrowley/go-metrics" ) type Vrank struct { startTime time.Time view istanbul.View - committee istanbul.Validators + committee valset.AddressList threshold time.Duration firstCommit int64 quorumCommit int64 @@ -65,7 +66,7 @@ const ( vrankNotArrivedPlaceholder = -1 ) -func NewVrank(view istanbul.View, committee istanbul.Validators) *Vrank { +func NewVrank(view istanbul.View, committee valset.AddressList) *Vrank { threshold, _ := time.ParseDuration(vrankDefaultThreshold) return &Vrank{ startTime: time.Now(), @@ -84,10 +85,10 @@ func (v *Vrank) TimeSinceStart() time.Duration { return time.Now().Sub(v.startTime) } -func (v *Vrank) AddCommit(msg *istanbul.Subject, src istanbul.Validator) { +func (v *Vrank) AddCommit(msg *istanbul.Subject, src common.Address) { if v.isTargetCommit(msg, src) { t := v.TimeSinceStart() - v.commitArrivalTimeMap[src.Address()] = t + v.commitArrivalTimeMap[src] = t } } @@ -177,14 +178,14 @@ func (v *Vrank) updateMetrics() { } } -func (v *Vrank) isTargetCommit(msg *istanbul.Subject, src istanbul.Validator) bool { +func (v *Vrank) isTargetCommit(msg *istanbul.Subject, src common.Address) bool { if msg.View == nil || msg.View.Sequence == nil || msg.View.Round == nil { return false } if msg.View.Cmp(&v.view) != 0 { return false } - _, ok := v.commitArrivalTimeMap[src.Address()] + _, ok := v.commitArrivalTimeMap[src] if ok { return false } @@ -215,14 +216,14 @@ func assessBatch(ts []time.Duration, threshold time.Duration) []uint8 { // serialize serializes arrivalTime hashmap into array. // If committee is sorted, we can simply figure out the validator position in the output array // by sorting the output of `kaia.getCommittee()` -func serialize(committee istanbul.Validators, arrivalTimeMap map[common.Address]time.Duration) []time.Duration { - sortedCommittee := make(istanbul.Validators, len(committee)) +func serialize(committee valset.AddressList, arrivalTimeMap map[common.Address]time.Duration) []time.Duration { + sortedCommittee := make(valset.AddressList, len(committee)) copy(sortedCommittee[:], committee[:]) sort.Sort(sortedCommittee) serialized := make([]time.Duration, len(sortedCommittee)) for i, v := range sortedCommittee { - val, ok := arrivalTimeMap[v.Address()] + val, ok := arrivalTimeMap[v] if ok { serialized[i] = val } else { diff --git a/consensus/istanbul/core/vrank_test.go b/consensus/istanbul/core/vrank_test.go index 9c814c8e1..3012c85af 100644 --- a/consensus/istanbul/core/vrank_test.go +++ b/consensus/istanbul/core/vrank_test.go @@ -27,33 +27,24 @@ import ( "github.com/kaiachain/kaia/common" "github.com/kaiachain/kaia/consensus/istanbul" - "github.com/kaiachain/kaia/consensus/istanbul/validator" + "github.com/kaiachain/kaia/kaiax/valset" "github.com/stretchr/testify/assert" ) -func genCommitteeFromAddrs(addrs []common.Address) istanbul.Validators { - committee := []istanbul.Validator{} - for _, addr := range addrs { - committee = append(committee, validator.New(addr)) - } - return committee -} - func TestVrank(t *testing.T) { var ( - N = 6 - quorum = 4 - addrs, _ = genValidators(N) - committee = genCommitteeFromAddrs(addrs) - view = istanbul.View{Sequence: big.NewInt(0), Round: big.NewInt(0)} - msg = &istanbul.Subject{View: &view} - vrank = NewVrank(view, committee) + N = 6 + quorum = 4 + committee, _ = genValidators(N) + view = istanbul.View{Sequence: big.NewInt(0), Round: big.NewInt(0)} + msg = &istanbul.Subject{View: &view} + vrank = NewVrank(view, committee) expectedAssessList []uint8 expectedLateCommits []time.Duration ) - sort.Sort(committee) + sort.Sort(valset.AddressList(committee)) for i := 0; i < quorum; i++ { vrank.AddCommit(msg, committee[i]) expectedAssessList = append(expectedAssessList, vrankArrivedEarly) @@ -62,7 +53,7 @@ func TestVrank(t *testing.T) { for i := quorum; i < N; i++ { vrank.AddCommit(msg, committee[i]) expectedAssessList = append(expectedAssessList, vrankArrivedLate) - expectedLateCommits = append(expectedLateCommits, vrank.commitArrivalTimeMap[committee[i].Address()]) + expectedLateCommits = append(expectedLateCommits, vrank.commitArrivalTimeMap[committee[i]]) } bitmap, late := vrank.Bitmap(), vrank.LateCommits() @@ -81,17 +72,16 @@ func TestVrankAssessBatch(t *testing.T) { func TestVrankSerialize(t *testing.T) { var ( - N = 6 - addrs, _ = genValidators(N) - committee = genCommitteeFromAddrs(addrs) - timeMap = make(map[common.Address]time.Duration) - expected = make([]time.Duration, len(addrs)) + N = 6 + committee, _ = genValidators(N) + timeMap = make(map[common.Address]time.Duration) + expected = make([]time.Duration, len(committee)) ) - sort.Sort(committee) + sort.Sort(valset.AddressList(committee)) for i, val := range committee { t := time.Duration((i * 100) * int(time.Millisecond)) - timeMap[val.Address()] = t + timeMap[val] = t expected[i] = t } diff --git a/consensus/istanbul/doc.go b/consensus/istanbul/doc.go index 2206ef1f1..3a1655e1d 100644 --- a/consensus/istanbul/doc.go +++ b/consensus/istanbul/doc.go @@ -31,7 +31,7 @@ Various interfaces, constants and utility functions for Istanbul consensus engin - `errors.go`: Defines three errors used in Istanbul engine - `events.go`: Defines events which are used for Istanbul engine communication - `types.go`: Defines message structs such as Proposal, Request, View, Preprepare, Subject and ConsensusMsg - - `utils.go`: Provides three utility functions: RLPHash, GetSignatureAddress and CheckValidatorSignature + - `utils.go`: Provides three utility functions: RLPHash, GetSignatureAddress - `validator.go`: Defines Validator, ValidatorSet interfaces and Validators, ProposalSelector types */ package istanbul diff --git a/consensus/istanbul/mocks/backend_mock.go b/consensus/istanbul/mocks/backend_mock.go index 8bb04726a..b8c54192f 100644 --- a/consensus/istanbul/mocks/backend_mock.go +++ b/consensus/istanbul/mocks/backend_mock.go @@ -53,17 +53,17 @@ func (mr *MockBackendMockRecorder) Address() *gomock.Call { } // Broadcast mocks base method -func (m *MockBackend) Broadcast(arg0 common.Hash, arg1 istanbul.ValidatorSet, arg2 []byte) error { +func (m *MockBackend) Broadcast(arg0 common.Hash, arg1 []byte) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Broadcast", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "Broadcast", arg0, arg1) ret0, _ := ret[0].(error) return ret0 } // Broadcast indicates an expected call of Broadcast -func (mr *MockBackendMockRecorder) Broadcast(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockBackendMockRecorder) Broadcast(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Broadcast", reflect.TypeOf((*MockBackend)(nil).Broadcast), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Broadcast", reflect.TypeOf((*MockBackend)(nil).Broadcast), arg0, arg1) } // CheckSignature mocks base method @@ -108,6 +108,36 @@ func (mr *MockBackendMockRecorder) EventMux() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EventMux", reflect.TypeOf((*MockBackend)(nil).EventMux)) } +// GetCommitteeState mocks base method +func (m *MockBackend) GetCommitteeState(arg0 uint64) (*istanbul.RoundCommitteeState, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCommitteeState", arg0) + ret0, _ := ret[0].(*istanbul.RoundCommitteeState) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCommitteeState indicates an expected call of GetCommitteeState +func (mr *MockBackendMockRecorder) GetCommitteeState(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCommitteeState", reflect.TypeOf((*MockBackend)(nil).GetCommitteeState), arg0) +} + +// GetCommitteeStateByRound mocks base method +func (m *MockBackend) GetCommitteeStateByRound(arg0, arg1 uint64) (*istanbul.RoundCommitteeState, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCommitteeStateByRound", arg0, arg1) + ret0, _ := ret[0].(*istanbul.RoundCommitteeState) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCommitteeStateByRound indicates an expected call of GetCommitteeStateByRound +func (mr *MockBackendMockRecorder) GetCommitteeStateByRound(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCommitteeStateByRound", reflect.TypeOf((*MockBackend)(nil).GetCommitteeStateByRound), arg0, arg1) +} + // GetProposer mocks base method func (m *MockBackend) GetProposer(arg0 uint64) common.Address { m.ctrl.T.Helper() @@ -136,46 +166,45 @@ func (mr *MockBackendMockRecorder) GetRewardBase() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRewardBase", reflect.TypeOf((*MockBackend)(nil).GetRewardBase)) } -// GetSubGroupSize mocks base method -func (m *MockBackend) GetSubGroupSize() uint64 { +// GetValidatorSet mocks base method +func (m *MockBackend) GetValidatorSet(arg0 uint64) (*istanbul.BlockValSet, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetSubGroupSize") - ret0, _ := ret[0].(uint64) - return ret0 + ret := m.ctrl.Call(m, "GetValidatorSet", arg0) + ret0, _ := ret[0].(*istanbul.BlockValSet) + ret1, _ := ret[1].(error) + return ret0, ret1 } -// GetSubGroupSize indicates an expected call of GetSubGroupSize -func (mr *MockBackendMockRecorder) GetSubGroupSize() *gomock.Call { +// GetValidatorSet indicates an expected call of GetValidatorSet +func (mr *MockBackendMockRecorder) GetValidatorSet(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubGroupSize", reflect.TypeOf((*MockBackend)(nil).GetSubGroupSize)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetValidatorSet", reflect.TypeOf((*MockBackend)(nil).GetValidatorSet), arg0) } // Gossip mocks base method -func (m *MockBackend) Gossip(arg0 istanbul.ValidatorSet, arg1 []byte) error { +func (m *MockBackend) Gossip(arg0 []byte) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Gossip", arg0, arg1) + ret := m.ctrl.Call(m, "Gossip", arg0) ret0, _ := ret[0].(error) return ret0 } // Gossip indicates an expected call of Gossip -func (mr *MockBackendMockRecorder) Gossip(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockBackendMockRecorder) Gossip(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Gossip", reflect.TypeOf((*MockBackend)(nil).Gossip), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Gossip", reflect.TypeOf((*MockBackend)(nil).Gossip), arg0) } // GossipSubPeer mocks base method -func (m *MockBackend) GossipSubPeer(arg0 common.Hash, arg1 istanbul.ValidatorSet, arg2 []byte) map[common.Address]bool { +func (m *MockBackend) GossipSubPeer(arg0 common.Hash, arg1 []byte) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GossipSubPeer", arg0, arg1, arg2) - ret0, _ := ret[0].(map[common.Address]bool) - return ret0 + m.ctrl.Call(m, "GossipSubPeer", arg0, arg1) } // GossipSubPeer indicates an expected call of GossipSubPeer -func (mr *MockBackendMockRecorder) GossipSubPeer(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockBackendMockRecorder) GossipSubPeer(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GossipSubPeer", reflect.TypeOf((*MockBackend)(nil).GossipSubPeer), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GossipSubPeer", reflect.TypeOf((*MockBackend)(nil).GossipSubPeer), arg0, arg1) } // HasBadProposal mocks base method @@ -235,20 +264,6 @@ func (mr *MockBackendMockRecorder) NodeType() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NodeType", reflect.TypeOf((*MockBackend)(nil).NodeType)) } -// ParentValidators mocks base method -func (m *MockBackend) ParentValidators(arg0 istanbul.Proposal) istanbul.ValidatorSet { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ParentValidators", arg0) - ret0, _ := ret[0].(istanbul.ValidatorSet) - return ret0 -} - -// ParentValidators indicates an expected call of ParentValidators -func (mr *MockBackendMockRecorder) ParentValidators(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParentValidators", reflect.TypeOf((*MockBackend)(nil).ParentValidators), arg0) -} - // SetCurrentView mocks base method func (m *MockBackend) SetCurrentView(arg0 *istanbul.View) { m.ctrl.T.Helper() @@ -276,20 +291,6 @@ func (mr *MockBackendMockRecorder) Sign(arg0 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Sign", reflect.TypeOf((*MockBackend)(nil).Sign), arg0) } -// Validators mocks base method -func (m *MockBackend) Validators(arg0 istanbul.Proposal) istanbul.ValidatorSet { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Validators", arg0) - ret0, _ := ret[0].(istanbul.ValidatorSet) - return ret0 -} - -// Validators indicates an expected call of Validators -func (mr *MockBackendMockRecorder) Validators(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Validators", reflect.TypeOf((*MockBackend)(nil).Validators), arg0) -} - // Verify mocks base method func (m *MockBackend) Verify(arg0 istanbul.Proposal) (time.Duration, error) { m.ctrl.T.Helper() diff --git a/consensus/istanbul/utils.go b/consensus/istanbul/utils.go index a68606c49..a71043c57 100644 --- a/consensus/istanbul/utils.go +++ b/consensus/istanbul/utils.go @@ -50,19 +50,3 @@ func GetSignatureAddress(data []byte, sig []byte) (common.Address, error) { } return crypto.PubkeyToAddress(*pubkey), nil } - -func CheckValidatorSignature(valSet ValidatorSet, data []byte, sig []byte) (common.Address, error) { - // 1. Get signature address - signer, err := GetSignatureAddress(data, sig) - if err != nil { - logger.Error("Failed to get signer address", "err", err) - return common.Address{}, err - } - - // 2. Check validator - if _, val := valSet.GetByAddress(signer); val != nil { - return val.Address(), nil - } - - return common.Address{}, ErrUnauthorizedAddress -} diff --git a/consensus/istanbul/validator.go b/consensus/istanbul/validator.go index aba173737..852c7fff5 100644 --- a/consensus/istanbul/validator.go +++ b/consensus/istanbul/validator.go @@ -23,10 +23,12 @@ package istanbul import ( + "math" "strings" "github.com/kaiachain/kaia/common" "github.com/kaiachain/kaia/kaiax/staking" + "github.com/kaiachain/kaia/kaiax/valset" "github.com/kaiachain/kaia/params" ) @@ -129,3 +131,85 @@ type ValidatorSet interface { // ---------------------------------------------------------------------------- type ProposalSelector func(ValidatorSet, common.Address, uint64) Validator + +type BlockValSet struct { + council valset.AddressList // council = demoted + qualified + qualified valset.AddressList + demoted valset.AddressList +} + +func NewBlockValSet(council, qualified valset.AddressList) *BlockValSet { + return &BlockValSet{council, qualified, council.Subtract(qualified)} +} +func (cs *BlockValSet) Council() valset.AddressList { return cs.council } +func (cs *BlockValSet) Qualified() valset.AddressList { return cs.qualified } +func (cs *BlockValSet) Demoted() valset.AddressList { return cs.demoted } +func (cs *BlockValSet) IsQualifiedMember(addr common.Address) bool { + return cs.qualified.GetIdxByAddress(addr) >= 0 +} +func (cs *BlockValSet) IsCouncilMember(addr common.Address) bool { + return cs.council.GetIdxByAddress(addr) >= 0 +} +func (cs *BlockValSet) CheckValidatorSignature(data []byte, sig []byte) (common.Address, error) { + // 1. Get signature address + signer, err := GetSignatureAddress(data, sig) + if err != nil { + logger.Error("Failed to get signer address", "err", err) + return common.Address{}, err + } + + // 2. Check validator + if cs.IsQualifiedMember(signer) { + return signer, nil + } + + return common.Address{}, ErrUnauthorizedAddress +} + +type RoundCommitteeState struct { + *BlockValSet + committeeSize uint64 + committee valset.AddressList + proposer common.Address +} + +func NewRoundCommitteeState(set *BlockValSet, committeeSize uint64, committee valset.AddressList, proposer common.Address) *RoundCommitteeState { + return &RoundCommitteeState{set, committeeSize, committee, proposer} +} +func (cs *RoundCommitteeState) ValSet() *BlockValSet { return cs.BlockValSet } +func (cs *RoundCommitteeState) CommitteeSize() uint64 { return cs.committeeSize } +func (cs *RoundCommitteeState) Committee() valset.AddressList { return cs.committee } +func (cs *RoundCommitteeState) NonCommittee() valset.AddressList { + return cs.qualified.Subtract(cs.committee) +} +func (cs *RoundCommitteeState) Proposer() common.Address { return cs.proposer } +func (cs *RoundCommitteeState) IsCommitteeMember(addr common.Address) bool { + return cs.committee.GetIdxByAddress(addr) >= 0 +} +func (cs *RoundCommitteeState) IsProposer(addr common.Address) bool { return cs.proposer == addr } + +// RequiredMessageCount returns a minimum required number of consensus messages to proceed +func (cs *RoundCommitteeState) RequiredMessageCount() int { + var size int + if len(cs.qualified) > int(cs.committeeSize) { + size = int(cs.committeeSize) + } else { + size = len(cs.qualified) + } + // For less than 4 validators, quorum size equals validator count. + if size < 4 { + return size + } + // Adopted QBFT quorum implementation + // https://github.com/Consensys/quorum/blob/master/consensus/istanbul/qbft/core/core.go#L312 + return int(math.Ceil(float64(2*size) / 3)) +} + +// F returns a maximum endurable number of byzantine fault nodes +func (cs *RoundCommitteeState) F() int { + if len(cs.qualified) > int(cs.committeeSize) { + return int(math.Ceil(float64(cs.committeeSize)/3)) - 1 + } else { + return int(math.Ceil(float64(len(cs.qualified))/3)) - 1 + } +}