diff --git a/consensus/XDPoS/XDPoS.go b/consensus/XDPoS/XDPoS.go index a9e20b4c4216..e58f0d0affec 100644 --- a/consensus/XDPoS/XDPoS.go +++ b/consensus/XDPoS/XDPoS.go @@ -26,6 +26,7 @@ import ( "github.com/XinFinOrg/XDPoSChain/consensus" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/engines/engine_v1" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/engines/engine_v2" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/engines/engine_v2_subnet" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" "github.com/XinFinOrg/XDPoSChain/consensus/clique" "github.com/XinFinOrg/XDPoSChain/core/state" @@ -45,6 +46,8 @@ const ( func (x *XDPoS) SigHash(header *types.Header) (hash common.Hash) { switch x.config.BlockConsensusVersion(header.Number) { + case params.ConsensusEngineVersion2Subnet: + return x.EngineV2Subnet.SignHash(header) case params.ConsensusEngineVersion2: return x.EngineV2.SignHash(header) default: // Default "v1" @@ -71,8 +74,9 @@ type XDPoS struct { GetLendingService func() utils.LendingService // The exact consensus engine with different versions - EngineV1 *engine_v1.XDPoS_v1 - EngineV2 *engine_v2.XDPoS_v2 + EngineV1 *engine_v1.XDPoS_v1 + EngineV2 *engine_v2.XDPoS_v2 + EngineV2Subnet *engine_v2_subnet.XDPoS_v2 } // Subscribe to consensus engines forensics events. Currently only exist for engine v2 @@ -157,6 +161,9 @@ func NewFaker(db ethdb.Database, chainConfig *params.ChainConfig) *XDPoS { // Reset parameters after checkpoint due to config may change func (x *XDPoS) UpdateParams(header *types.Header) { switch x.config.BlockConsensusVersion(header.Number) { + case params.ConsensusEngineVersion2Subnet: + x.EngineV2Subnet.UpdateParams(header) + return case params.ConsensusEngineVersion2: x.EngineV2.UpdateParams(header) return @@ -167,6 +174,8 @@ func (x *XDPoS) UpdateParams(header *types.Header) { func (x *XDPoS) Initial(chain consensus.ChainReader, header *types.Header) error { switch x.config.BlockConsensusVersion(header.Number) { + case params.ConsensusEngineVersion2Subnet: + return x.EngineV2Subnet.Initial(chain, header) case params.ConsensusEngineVersion2: return x.EngineV2.Initial(chain, header) default: // Default "v1" @@ -190,6 +199,8 @@ func (x *XDPoS) APIs(chain consensus.ChainReader) []rpc.API { // from the signature in the header's extra-data section. func (x *XDPoS) Author(header *types.Header) (common.Address, error) { switch x.config.BlockConsensusVersion(header.Number) { + case params.ConsensusEngineVersion2Subnet: + return x.EngineV2Subnet.Author(header) case params.ConsensusEngineVersion2: return x.EngineV2.Author(header) default: // Default "v1" @@ -200,6 +211,8 @@ func (x *XDPoS) Author(header *types.Header) (common.Address, error) { // VerifyHeader checks whether a header conforms to the consensus rules. func (x *XDPoS) VerifyHeader(chain consensus.ChainReader, header *types.Header, fullVerify bool) error { switch x.config.BlockConsensusVersion(header.Number) { + case params.ConsensusEngineVersion2Subnet: + return x.EngineV2Subnet.VerifyHeader(chain, header, fullVerify) case params.ConsensusEngineVersion2: return x.EngineV2.VerifyHeader(chain, header, fullVerify) default: // Default "v1" @@ -222,6 +235,8 @@ func (x *XDPoS) VerifyHeaders(chain consensus.ChainReader, headers []*types.Head for i, header := range headers { switch x.config.BlockConsensusVersion(header.Number) { + case params.ConsensusEngineVersion2Subnet: + v2headers = append(v2headers, header) case params.ConsensusEngineVersion2: v2headers = append(v2headers, header) v2fullVerifies = append(v2fullVerifies, fullVerifies[i]) @@ -245,6 +260,8 @@ func (x *XDPoS) VerifyHeaders(chain consensus.ChainReader, headers []*types.Head // uncles as this consensus mechanism doesn't permit uncles. func (x *XDPoS) VerifyUncles(chain consensus.ChainReader, block *types.Block) error { switch x.config.BlockConsensusVersion(block.Number()) { + case params.ConsensusEngineVersion2Subnet: + return x.EngineV2Subnet.VerifyUncles(chain, block) case params.ConsensusEngineVersion2: return x.EngineV2.VerifyUncles(chain, block) default: // Default "v1" @@ -256,6 +273,8 @@ func (x *XDPoS) VerifyUncles(chain consensus.ChainReader, block *types.Block) er // in the header satisfies the consensus protocol requirements. func (x *XDPoS) VerifySeal(chain consensus.ChainReader, header *types.Header) error { switch x.config.BlockConsensusVersion(header.Number) { + case params.ConsensusEngineVersion2Subnet: + return nil case params.ConsensusEngineVersion2: return nil default: // Default "v1" @@ -267,6 +286,9 @@ func (x *XDPoS) VerifySeal(chain consensus.ChainReader, header *types.Header) er // header for running the transactions on top. func (x *XDPoS) Prepare(chain consensus.ChainReader, header *types.Header) error { switch x.config.BlockConsensusVersion(header.Number) { + case params.ConsensusEngineVersion2Subnet: + log.Warn("[TESTLOG][Prepare] Using EngineV2Subnet") + return x.EngineV2Subnet.Prepare(chain, header) case params.ConsensusEngineVersion2: return x.EngineV2.Prepare(chain, header) default: // Default "v1" @@ -278,6 +300,8 @@ func (x *XDPoS) Prepare(chain consensus.ChainReader, header *types.Header) error // rewards given, and returns the final block. func (x *XDPoS) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, parentState *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { switch x.config.BlockConsensusVersion(header.Number) { + case params.ConsensusEngineVersion2Subnet: + return x.EngineV2Subnet.Finalize(chain, header, state, parentState, txs, uncles, receipts) case params.ConsensusEngineVersion2: return x.EngineV2.Finalize(chain, header, state, parentState, txs, uncles, receipts) default: // Default "v1" @@ -289,6 +313,8 @@ func (x *XDPoS) Finalize(chain consensus.ChainReader, header *types.Header, stat // the local signing credentials. func (x *XDPoS) Seal(chain consensus.ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) { switch x.config.BlockConsensusVersion(block.Number()) { + case params.ConsensusEngineVersion2Subnet: + return x.EngineV2Subnet.Seal(chain, block, stop) case params.ConsensusEngineVersion2: return x.EngineV2.Seal(chain, block, stop) default: // Default "v1" @@ -301,6 +327,8 @@ func (x *XDPoS) Seal(chain consensus.ChainReader, block *types.Block, stop <-cha // current signer. func (x *XDPoS) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int { switch x.config.BlockConsensusVersion(parent.Number) { + case params.ConsensusEngineVersion2Subnet: + return x.EngineV2Subnet.CalcDifficulty(chain, time, parent) case params.ConsensusEngineVersion2: return x.EngineV2.CalcDifficulty(chain, time, parent) default: // Default "v1" @@ -310,6 +338,8 @@ func (x *XDPoS) CalcDifficulty(chain consensus.ChainReader, time uint64, parent func (x *XDPoS) HandleProposedBlock(chain consensus.ChainReader, header *types.Header) error { switch x.config.BlockConsensusVersion(header.Number) { + case params.ConsensusEngineVersion2Subnet: + return x.EngineV2Subnet.ProposedBlockHandler(chain, header) case params.ConsensusEngineVersion2: return x.EngineV2.ProposedBlockHandler(chain, header) default: // Default "v1" @@ -327,6 +357,7 @@ func (x *XDPoS) Authorize(signer common.Address, signFn clique.SignerFn) { // Authorize each consensus individually x.EngineV1.Authorize(signer, signFn) x.EngineV2.Authorize(signer, signFn) + x.EngineV2Subnet.Authorize(signer, signFn) } func (x *XDPoS) GetPeriod() uint64 { @@ -335,6 +366,8 @@ func (x *XDPoS) GetPeriod() uint64 { func (x *XDPoS) IsAuthorisedAddress(chain consensus.ChainReader, header *types.Header, address common.Address) bool { switch x.config.BlockConsensusVersion(header.Number) { + case params.ConsensusEngineVersion2Subnet: + return x.EngineV2Subnet.IsAuthorisedAddress(chain, header, address) case params.ConsensusEngineVersion2: return x.EngineV2.IsAuthorisedAddress(chain, header, address) default: // Default "v1" @@ -344,6 +377,8 @@ func (x *XDPoS) IsAuthorisedAddress(chain consensus.ChainReader, header *types.H func (x *XDPoS) GetMasternodes(chain consensus.ChainReader, header *types.Header) []common.Address { switch x.config.BlockConsensusVersion(header.Number) { + case params.ConsensusEngineVersion2Subnet: + return x.EngineV2Subnet.GetMasternodes(chain, header) case params.ConsensusEngineVersion2: return x.EngineV2.GetMasternodes(chain, header) default: // Default "v1" @@ -358,6 +393,8 @@ func (x *XDPoS) GetMasternodesByNumber(chain consensus.ChainReader, blockNumber return []common.Address{} } switch x.config.BlockConsensusVersion(big.NewInt(int64(blockNumber))) { + case params.ConsensusEngineVersion2Subnet: + return x.EngineV2Subnet.GetMasternodes(chain, blockHeader) case params.ConsensusEngineVersion2: return x.EngineV2.GetMasternodes(chain, blockHeader) default: // Default "v1" @@ -367,6 +404,9 @@ func (x *XDPoS) GetMasternodesByNumber(chain consensus.ChainReader, blockNumber func (x *XDPoS) YourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (bool, error) { switch x.config.BlockConsensusVersion(big.NewInt(parent.Number.Int64() + 1)) { + case params.ConsensusEngineVersion2Subnet: + log.Warn("[TESTLOG][YourTurn] Using EngineV2Subnet") + return x.EngineV2Subnet.YourTurn(chain, parent, signer) case params.ConsensusEngineVersion2: return x.EngineV2.YourTurn(chain, parent, signer) default: // Default "v1" @@ -384,6 +424,8 @@ func (x *XDPoS) GetValidator(creator common.Address, chain consensus.ChainReader func (x *XDPoS) UpdateMasternodes(chain consensus.ChainReader, header *types.Header, ms []utils.Masternode) error { // fmt.Println("UpdateMasternodes") switch x.config.BlockConsensusVersion(header.Number) { + case params.ConsensusEngineVersion2Subnet: + return x.EngineV2Subnet.UpdateMasternodes(chain, header, ms) case params.ConsensusEngineVersion2: return x.EngineV2.UpdateMasternodes(chain, header, ms) default: // Default "v1" @@ -393,6 +435,8 @@ func (x *XDPoS) UpdateMasternodes(chain consensus.ChainReader, header *types.Hea func (x *XDPoS) RecoverSigner(header *types.Header) (common.Address, error) { switch x.config.BlockConsensusVersion(header.Number) { + case params.ConsensusEngineVersion2Subnet: + return common.Address{}, nil case params.ConsensusEngineVersion2: return common.Address{}, nil default: // Default "v1" @@ -402,6 +446,8 @@ func (x *XDPoS) RecoverSigner(header *types.Header) (common.Address, error) { func (x *XDPoS) RecoverValidator(header *types.Header) (common.Address, error) { switch x.config.BlockConsensusVersion(header.Number) { + case params.ConsensusEngineVersion2Subnet: + return common.Address{}, nil case params.ConsensusEngineVersion2: return common.Address{}, nil default: // Default "v1" @@ -412,6 +458,8 @@ func (x *XDPoS) RecoverValidator(header *types.Header) (common.Address, error) { // Get master nodes over extra data of previous checkpoint block. func (x *XDPoS) GetMasternodesFromCheckpointHeader(checkpointHeader *types.Header) []common.Address { switch x.config.BlockConsensusVersion(checkpointHeader.Number) { + case params.ConsensusEngineVersion2Subnet: + return x.EngineV2Subnet.GetMasternodesFromEpochSwitchHeader(checkpointHeader) case params.ConsensusEngineVersion2: return x.EngineV2.GetMasternodesFromEpochSwitchHeader(checkpointHeader) default: // Default "v1" @@ -422,6 +470,8 @@ func (x *XDPoS) GetMasternodesFromCheckpointHeader(checkpointHeader *types.Heade // Check is epoch switch (checkpoint) block func (x *XDPoS) IsEpochSwitch(header *types.Header) (bool, uint64, error) { switch x.config.BlockConsensusVersion(header.Number) { + case params.ConsensusEngineVersion2Subnet: + return x.EngineV2Subnet.IsEpochSwitch(header) case params.ConsensusEngineVersion2: return x.EngineV2.IsEpochSwitch(header) default: // Default "v1" @@ -431,6 +481,8 @@ func (x *XDPoS) IsEpochSwitch(header *types.Header) (bool, uint64, error) { func (x *XDPoS) GetCurrentEpochSwitchBlock(chain consensus.ChainReader, blockNumber *big.Int) (uint64, uint64, error) { switch x.config.BlockConsensusVersion(blockNumber) { + case params.ConsensusEngineVersion2Subnet: + return x.EngineV2Subnet.GetCurrentEpochSwitchBlock(chain, blockNumber) case params.ConsensusEngineVersion2: return x.EngineV2.GetCurrentEpochSwitchBlock(chain, blockNumber) default: // Default "v1" @@ -440,6 +492,8 @@ func (x *XDPoS) GetCurrentEpochSwitchBlock(chain consensus.ChainReader, blockNum func (x *XDPoS) CalculateMissingRounds(chain consensus.ChainReader, header *types.Header) (*utils.PublicApiMissedRoundsMetadata, error) { switch x.config.BlockConsensusVersion(header.Number) { + case params.ConsensusEngineVersion2Subnet: + return x.EngineV2Subnet.CalculateMissingRounds(chain, header) case params.ConsensusEngineVersion2: return x.EngineV2.CalculateMissingRounds(chain, header) default: // Default "v1" @@ -449,11 +503,22 @@ func (x *XDPoS) CalculateMissingRounds(chain consensus.ChainReader, header *type // Same DB across all consensus engines func (x *XDPoS) GetDb() ethdb.Database { + // TODO: check if engine switch required return x.db } func (x *XDPoS) GetSnapshot(chain consensus.ChainReader, header *types.Header) (*utils.PublicApiSnapshot, error) { switch x.config.BlockConsensusVersion(header.Number) { + case params.ConsensusEngineVersion2Subnet: + sp, err := x.EngineV2Subnet.GetSnapshot(chain, header) + if err != nil { + return nil, err + } + return &utils.PublicApiSnapshot{ + Number: sp.Number, + Hash: sp.Hash, + Signers: sp.GetMappedCandidates(), + }, err case params.ConsensusEngineVersion2: sp, err := x.EngineV2.GetSnapshot(chain, header) if err != nil { @@ -483,6 +548,8 @@ func (x *XDPoS) GetSnapshot(chain consensus.ChainReader, header *types.Header) ( func (x *XDPoS) GetAuthorisedSignersFromSnapshot(chain consensus.ChainReader, header *types.Header) ([]common.Address, error) { switch x.config.BlockConsensusVersion(header.Number) { + case params.ConsensusEngineVersion2Subnet: + return x.EngineV2Subnet.GetSignersFromSnapshot(chain, header) case params.ConsensusEngineVersion2: return x.EngineV2.GetSignersFromSnapshot(chain, header) default: // Default "v1" @@ -492,6 +559,12 @@ func (x *XDPoS) GetAuthorisedSignersFromSnapshot(chain consensus.ChainReader, he func (x *XDPoS) FindParentBlockToAssign(chain consensus.ChainReader, currentBlock *types.Block) *types.Block { switch x.config.BlockConsensusVersion(currentBlock.Number()) { + case params.ConsensusEngineVersion2Subnet: + block := x.EngineV2Subnet.FindParentBlockToAssign(chain) + if block == nil { + return currentBlock + } + return block case params.ConsensusEngineVersion2: block := x.EngineV2.FindParentBlockToAssign(chain) if block == nil { @@ -509,6 +582,7 @@ Caching // Cache signing transaction data into BlockSingers cache object func (x *XDPoS) CacheNoneTIPSigningTxs(header *types.Header, txs []*types.Transaction, receipts []*types.Receipt) []*types.Transaction { + // TODO: check if engine switch required signTxs := []*types.Transaction{} for _, tx := range txs { if tx.IsSigningTransaction() { @@ -540,6 +614,7 @@ func (x *XDPoS) CacheNoneTIPSigningTxs(header *types.Header, txs []*types.Transa // Cache func (x *XDPoS) CacheSigningTxs(hash common.Hash, txs []*types.Transaction) []*types.Transaction { + // TODO: check if engine switch required signTxs := []*types.Transaction{} for _, tx := range txs { if tx.IsSigningTransaction() { @@ -552,10 +627,12 @@ func (x *XDPoS) CacheSigningTxs(hash common.Hash, txs []*types.Transaction) []*t } func (x *XDPoS) GetCachedSigningTxs(hash common.Hash) ([]*types.Transaction, bool) { + // TODO: check if engine switch required return x.signingTxsCache.Get(hash) } func (x *XDPoS) GetEpochSwitchInfoBetween(chain consensus.ChainReader, begin, end *types.Header) ([]*types.EpochSwitchInfo, error) { + // TODO: enginev2subnet beginBlockVersion := x.config.BlockConsensusVersion(begin.Number) endBlockVersion := x.config.BlockConsensusVersion(end.Number) if beginBlockVersion == params.ConsensusEngineVersion2 && endBlockVersion == params.ConsensusEngineVersion2 { diff --git a/consensus/XDPoS/engines/engine_v2_subnet/difficulty.go b/consensus/XDPoS/engines/engine_v2_subnet/difficulty.go new file mode 100644 index 000000000000..1b423bc881ea --- /dev/null +++ b/consensus/XDPoS/engines/engine_v2_subnet/difficulty.go @@ -0,0 +1,14 @@ +package engine_v2_subnet + +import ( + "math/big" + + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/consensus" + "github.com/XinFinOrg/XDPoSChain/core/types" +) + +// TODO: what should be new difficulty +func (x *XDPoS_v2) calcDifficulty(chain consensus.ChainReader, parent *types.Header, signer common.Address) *big.Int { + return big.NewInt(1) +} diff --git a/consensus/XDPoS/engines/engine_v2_subnet/engine.go b/consensus/XDPoS/engines/engine_v2_subnet/engine.go new file mode 100644 index 000000000000..8827bd60c498 --- /dev/null +++ b/consensus/XDPoS/engines/engine_v2_subnet/engine.go @@ -0,0 +1,1019 @@ +package engine_v2_subnet + +import ( + "encoding/json" + "errors" + "fmt" + "math/big" + "os" + "path/filepath" + "sync" + "time" + + "github.com/XinFinOrg/XDPoSChain/accounts" + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/common/countdown" + "github.com/XinFinOrg/XDPoSChain/common/lru" + "github.com/XinFinOrg/XDPoSChain/consensus" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" + "github.com/XinFinOrg/XDPoSChain/consensus/clique" + "github.com/XinFinOrg/XDPoSChain/core/state" + "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/ethdb" + "github.com/XinFinOrg/XDPoSChain/log" + "github.com/XinFinOrg/XDPoSChain/params" + "github.com/XinFinOrg/XDPoSChain/trie" +) + +type XDPoS_v2 struct { + chainConfig *params.ChainConfig // Chain & network configuration + + config *params.XDPoSConfig // Consensus engine configuration parameters + db ethdb.Database // Database to store and retrieve snapshot checkpoints + isInitilised bool // status of v2 variables + whosTurn common.Address // Record waiting for who to mine + + snapshots *lru.Cache[common.Hash, *SnapshotV2] // Snapshots for gap block + signatures *utils.SigLRU // Signatures of recent blocks to speed up mining + epochSwitches *lru.Cache[common.Hash, *types.EpochSwitchInfo] // infos of epoch: master nodes, epoch switch block info, parent of that info + verifiedHeaders *lru.Cache[common.Hash, struct{}] + + // only contains epoch switch block info + // input: round, output: infos of epoch switch block and next epoch switch block info + round2epochBlockInfo *lru.Cache[types.Round, *types.BlockInfo] + + signer common.Address // Ethereum address of the signing key + signFn clique.SignerFn // Signer function to authorize hashes with + lock sync.RWMutex // Protects the signer fields + signLock sync.RWMutex // Protects the signer fields + + BroadcastCh chan interface{} + minePeriodCh chan int + newRoundCh chan types.Round + + timeoutWorker *countdown.CountdownTimer // Timer to generate broadcast timeout msg if threashold reached + timeoutCount int // number of timeout being sent + + timeoutPool *utils.Pool + votePool *utils.Pool + syncInfoPool *utils.Pool + currentRound types.Round + highestSelfMinedRound types.Round + highestVotedRound types.Round + highestQuorumCert *types.QuorumCert + // lockQuorumCert in XDPoS Consensus 2.0, used in voting rule + lockQuorumCert *types.QuorumCert + highestTimeoutCert *types.TimeoutCert + highestCommitBlock *types.BlockInfo + + HookReward func(chain consensus.ChainReader, state *state.StateDB, parentState *state.StateDB, header *types.Header) (map[string]interface{}, error) + HookPenalty func(chain consensus.ChainReader, number *big.Int, parentHash common.Hash, candidates []common.Address) ([]common.Address, error) + + ForensicsProcessor *Forensics + + votePoolCollectionTime time.Time +} + +func New(chainConfig *params.ChainConfig, db ethdb.Database, minePeriodCh chan int, newRoundCh chan types.Round) *XDPoS_v2 { + config := chainConfig.XDPoS + // Setup timeoutTimer + duration := time.Duration(config.V2.CurrentConfig.TimeoutPeriod) * time.Second + timeoutTimer, err := countdown.NewExpCountDown(duration, config.V2.CurrentConfig.ExpTimeoutConfig.Base, config.V2.CurrentConfig.ExpTimeoutConfig.MaxExponent) + if err != nil { + log.Crit("create exp countdown", "err", err) + } + + timeoutPool := utils.NewPool() + votePool := utils.NewPool() + syncInfoPool := utils.NewPool() + engine := &XDPoS_v2{ + chainConfig: chainConfig, + + config: config, + db: db, + isInitilised: false, + + signatures: lru.NewCache[common.Hash, common.Address](utils.InMemorySnapshots), + + verifiedHeaders: lru.NewCache[common.Hash, struct{}](utils.InMemorySnapshots), + snapshots: lru.NewCache[common.Hash, *SnapshotV2](utils.InMemorySnapshots), + epochSwitches: lru.NewCache[common.Hash, *types.EpochSwitchInfo](int(utils.InMemorySnapshots)), + timeoutWorker: timeoutTimer, + BroadcastCh: make(chan interface{}), + minePeriodCh: minePeriodCh, + newRoundCh: newRoundCh, + + round2epochBlockInfo: lru.NewCache[types.Round, *types.BlockInfo](utils.InMemorySnapshots), + + timeoutPool: timeoutPool, + votePool: votePool, + syncInfoPool: syncInfoPool, + + highestSelfMinedRound: types.Round(0), + + highestTimeoutCert: &types.TimeoutCert{ + Round: types.Round(0), + Signatures: []types.Signature{}, + }, + highestQuorumCert: &types.QuorumCert{ + ProposedBlockInfo: &types.BlockInfo{ + Hash: common.Hash{}, + Round: types.Round(0), + Number: big.NewInt(0), + }, + Signatures: []types.Signature{}, + GapNumber: 0, + }, + highestVotedRound: types.Round(0), + highestCommitBlock: nil, + ForensicsProcessor: NewForensics(), + } + // Add callback to the timer + timeoutTimer.OnTimeoutFn = engine.OnCountdownTimeout + + engine.periodicJob() + config.V2.BuildConfigIndex() + + return engine +} + +func (x *XDPoS_v2) UpdateParams(header *types.Header) { + _, round, _, err := x.getExtraFields(header) + if err != nil { + log.Error("[UpdateParams] retrieve round failed", "block", header.Number.Uint64(), "err", err) + } + x.config.V2.UpdateConfig(uint64(round)) + + // Setup timeoutTimer + duration := time.Duration(x.config.V2.CurrentConfig.TimeoutPeriod) * time.Second + err = x.timeoutWorker.SetParams(duration, x.config.V2.CurrentConfig.ExpTimeoutConfig.Base, x.config.V2.CurrentConfig.ExpTimeoutConfig.MaxExponent) + if err != nil { + log.Error("[UpdateParams] set params failed", "err", err) + } + // avoid deadlock + go func() { + x.minePeriodCh <- x.config.V2.CurrentConfig.MinePeriod + }() +} + +/* + V2 Block + +SignerFn is a signer callback function to request a hash to be signed by a +backing account. +type SignerFn func(accounts.Account, []byte) ([]byte, error) + +sigHash returns the hash which is used as input for the delegated-proof-of-stake +signing. It is the hash of the entire header apart from the 65 byte signature +contained at the end of the extra data. +*/ +func (x *XDPoS_v2) SignHash(header *types.Header) (hash common.Hash) { + return sigHash(header) +} + +// Initial V2 related parameters +func (x *XDPoS_v2) Initial(chain consensus.ChainReader, header *types.Header) error { + return x.initial(chain, header) +} + +func (x *XDPoS_v2) initial(chain consensus.ChainReader, header *types.Header) error { + log.Warn("[initial] initial v2 related parameters") + + if x.highestQuorumCert.ProposedBlockInfo.Hash != (common.Hash{}) { // already initialized + log.Info("[initial] Already initialized", "x.highestQuorumCert.ProposedBlockInfo.Hash", x.highestQuorumCert.ProposedBlockInfo.Hash) + x.isInitilised = true + return nil + } + + var quorumCert *types.QuorumCert + var err error + + if header.Number.Int64() == x.config.V2.SwitchBlock.Int64() { + log.Info("[initial] highest QC for consensus v2 first block") + blockInfo := &types.BlockInfo{ + Hash: header.Hash(), + Round: types.Round(0), + Number: header.Number, + } + quorumCert = &types.QuorumCert{ + ProposedBlockInfo: blockInfo, + Signatures: nil, + GapNumber: header.Number.Uint64() - x.config.Gap, + } + // prevent overflow + if header.Number.Uint64() < x.config.Gap { + quorumCert.GapNumber = 0 + } + // can not call processQC because round is equal to default + x.currentRound = 1 + x.highestQuorumCert = quorumCert + + } else { + log.Info("[initial] highest QC from current header") + quorumCert, _, _, err = x.getExtraFields(header) + if err != nil { + return err + } + err = x.processQC(chain, quorumCert) + if err != nil { + return err + } + } + + // Initial first v2 snapshot + lastGapNum := x.config.V2.SwitchBlock.Uint64() - x.config.Gap + // prevent overflow + if x.config.V2.SwitchBlock.Uint64() < x.config.Gap { + lastGapNum = 0 + } + lastGapHeader := chain.GetHeaderByNumber(lastGapNum) + + snap, _ := loadSnapshot(x.db, lastGapHeader.Hash()) + + if snap == nil { + checkpointHeader := chain.GetHeaderByNumber(x.config.V2.SwitchBlock.Uint64()) + + log.Info("[initial] init first snapshot") + _, _, masternodes, err := x.getExtraFields(checkpointHeader) + if err != nil { + log.Error("[initial] Error while get masternodes", "error", err) + return err + } + + if len(masternodes) == 0 { + log.Error("[initial] masternodes are empty", "v2switch", x.config.V2.SwitchBlock.Uint64()) + return fmt.Errorf("masternodes are empty v2 switch number: %d", x.config.V2.SwitchBlock.Uint64()) + } + + snap := newSnapshot(lastGapNum, lastGapHeader.Hash(), masternodes) + x.snapshots.Add(snap.Hash, snap) + err = storeSnapshot(snap, x.db) + if err != nil { + log.Error("[initial] Error while store snapshot", "error", err) + return err + } + } + + // Initial timeout + log.Warn("[initial] miner wait period", "period", x.config.V2.CurrentConfig.MinePeriod) + // avoid deadlock + go func() { + x.minePeriodCh <- x.config.V2.CurrentConfig.MinePeriod + }() + + // Kick-off the countdown timer + x.timeoutWorker.Reset(chain, 0, 0) + x.isInitilised = true + + log.Warn("[initial] finish initialisation") + + return nil +} + +// Check if it's my turn to mine a block. Note: The second return value `preIndex` is useless in V2 engine +func (x *XDPoS_v2) YourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (bool, error) { + x.lock.RLock() + defer x.lock.RUnlock() + + if !x.isInitilised { + err := x.initial(chain, parent) + if err != nil { + log.Error("[YourTurn] Error while initialising last v2 variables", "ParentBlockHash", parent.Hash(), "Error", err) + return false, err + } + } + + waitedTime := time.Now().Unix() - int64(parent.Time) + minePeriod := x.config.V2.Config(uint64(x.currentRound)).MinePeriod + if waitedTime < int64(minePeriod) { + log.Trace("[YourTurn] wait after mine period", "minePeriod", minePeriod, "waitedTime", waitedTime) + return false, nil + } + + round := x.currentRound + isMyTurn, err := x.yourturn(chain, round, parent, signer) + if err != nil { + log.Warn("[Yourturn] Error while checking if i am qualified to mine", "round", round, "error", err) + } + + return isMyTurn, err +} + +// Prepare implements consensus.Engine, preparing all the consensus fields of the +// header for running the transactions on top. +func (x *XDPoS_v2) Prepare(chain consensus.ChainReader, header *types.Header) error { + + x.lock.RLock() + currentRound := x.currentRound + highestQC := x.highestQuorumCert + x.lock.RUnlock() + + if header.ParentHash != highestQC.ProposedBlockInfo.Hash { + log.Warn("[Prepare] parent hash and QC hash does not match", "blockNum", header.Number, "QCNumber", highestQC.ProposedBlockInfo.Number, "blockHash", header.ParentHash, "QCHash", highestQC.ProposedBlockInfo.Hash) + return consensus.ErrNotReadyToPropose + } + + extra := types.ExtraFields_v2{ + Round: currentRound, + QuorumCert: highestQC, + } + + extraBytes, err := extra.EncodeToBytes() + if err != nil { + return err + } + header.Extra = extraBytes + + header.Nonce = types.BlockNonce{} + + number := header.Number.Uint64() + parent := chain.GetHeader(header.ParentHash, number-1) + + log.Info("Preparing new block!", "Number", number, "Parent Hash", parent.Hash()) + if parent == nil { + return consensus.ErrUnknownAncestor + } + + x.signLock.RLock() + signer := x.signer + x.signLock.RUnlock() + + isMyTurn, err := x.yourturn(chain, currentRound, parent, signer) + if err != nil { + log.Error("[Prepare] Error while checking if it's still my turn to mine", "currentRound", currentRound, "ParentHash", parent.Hash().Hex(), "ParentNumber", parent.Number.Uint64(), "error", err) + return err + } + if !isMyTurn { + return consensus.ErrNotReadyToMine + } + // Set the correct difficulty + header.Difficulty = x.calcDifficulty(chain, parent, signer) + log.Debug("CalcDifficulty ", "number", header.Number, "difficulty", header.Difficulty) + + isEpochSwitchBlock, _, err := x.IsEpochSwitch(header) + if err != nil { + log.Error("[Prepare] Error while trying to determine if header is an epoch switch during Prepare", "header", header, "Error", err) + return err + } + if isEpochSwitchBlock { + masterNodes, penalties, err := x.calcMasternodes(chain, header.Number, header.ParentHash, currentRound) + if err != nil { + return err + } + for _, v := range masterNodes { + header.Validators = append(header.Validators, v[:]...) + } + for _, v := range penalties { + header.Penalties = append(header.Penalties, v[:]...) + } + } + + // Mix digest is reserved for now, set to empty + header.MixDigest = common.Hash{} + + // Ensure the timestamp has the correct delay + // TODO: Proper deal with time + // TODO: if timestamp > current time, how to deal with future timestamp + header.Time = parent.Time + x.config.Period + if timeNow := uint64(time.Now().Unix()); header.Time < timeNow { + header.Time = timeNow + } + + if header.Coinbase != signer { + log.Error("[Prepare] The mined blocker header coinbase address mismatch with wallet address", "headerCoinbase", header.Coinbase.Hex(), "WalletAddress", signer.Hex()) + return consensus.ErrCoinbaseMismatch + } + + return nil +} + +// Finalize implements consensus.Engine, ensuring no uncles are set, nor block +// rewards given, and returns the final block. +func (x *XDPoS_v2) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, parentState *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { + // set block reward + + isEpochSwitch, _, err := x.IsEpochSwitch(header) + if err != nil { + log.Error("[Finalize] IsEpochSwitch bug!", "err", err) + return nil, err + } + if x.HookReward != nil && isEpochSwitch { + rewards, err := x.HookReward(chain, state, parentState, header) + if err != nil { + return nil, err + } + if len(common.StoreRewardFolder) > 0 { + data, err := json.Marshal(rewards) + if err == nil { + err = os.WriteFile(filepath.Join(common.StoreRewardFolder, header.Number.String()+"."+header.Hash().Hex()), data, 0644) + } + if err != nil { + log.Error("Error when save reward info ", "number", header.Number, "hash", header.Hash().Hex(), "err", err) + } + } + } + + // the state remains as is and uncles are dropped + header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) + header.UncleHash = types.CalcUncleHash(nil) + + // Assemble and return the final block for sealing + return types.NewBlock(header, &types.Body{Transactions: txs}, receipts, trie.NewStackTrie(nil)), nil +} + +// Authorize injects a private key into the consensus engine to mint new blocks with. +func (x *XDPoS_v2) Authorize(signer common.Address, signFn clique.SignerFn) { + x.signLock.Lock() + defer x.signLock.Unlock() + + x.signer = signer + x.signFn = signFn +} + +func (x *XDPoS_v2) Author(header *types.Header) (common.Address, error) { + return ecrecover(header, x.signatures) +} + +// Seal implements consensus.Engine, attempting to create a sealed block using +// the local signing credentials. +func (x *XDPoS_v2) Seal(chain consensus.ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) { + header := block.Header() + + // Sealing the genesis block is not supported + number := header.Number.Uint64() + if number == 0 { + return nil, utils.ErrUnknownBlock + } + + // Don't hold the signer fields for the entire sealing procedure + x.signLock.RLock() + signer, signFn := x.signer, x.signFn + x.signLock.RUnlock() + + select { + case <-stop: + return nil, nil + default: + } + + // Sign all the things! + signature, err := signFn(accounts.Account{Address: signer}, sigHash(header).Bytes()) + if err != nil { + return nil, err + } + header.Validator = signature + + // Mark the highestSelfMinedRound to make sure we only mine once per round + var decodedExtraField types.ExtraFields_v2 + err = utils.DecodeBytesExtraFields(header.Extra, &decodedExtraField) + if err != nil { + log.Error("[Seal] Error when decode extra field to get the round number from v2 block during sealing", "Hash", header.Hash().Hex(), "Number", header.Number.Uint64(), "Error", err) + return nil, err + } + x.highestSelfMinedRound = decodedExtraField.Round + + return block.WithSeal(header), nil +} + +// CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty +// that a new block should have based on the previous blocks in the chain and the +// current signer. +func (x *XDPoS_v2) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int { + return x.calcDifficulty(chain, parent, x.signer) +} + +func (x *XDPoS_v2) IsAuthorisedAddress(chain consensus.ChainReader, header *types.Header, address common.Address) bool { + snap, err := x.GetSnapshot(chain, header) + if err != nil { + log.Error("[IsAuthorisedAddress] Can't get snapshot with at ", "number", header.Number, "hash", header.Hash().Hex(), "err", err) + return false + } + for _, mn := range snap.NextEpochCandidates { + if mn == address { + return true + } + } + return false +} + +func (x *XDPoS_v2) GetSnapshot(chain consensus.ChainReader, header *types.Header) (*SnapshotV2, error) { + number := header.Number.Uint64() + log.Trace("get snapshot", "number", number) + snap, err := x.getSnapshot(chain, number, false) + if err != nil { + return nil, err + } + return snap, nil +} + +func (x *XDPoS_v2) UpdateMasternodes(chain consensus.ChainReader, header *types.Header, ms []utils.Masternode) error { + number := header.Number.Uint64() + log.Trace("[UpdateMasternodes]") + + masterNodes := []common.Address{} + for _, m := range ms { + masterNodes = append(masterNodes, m.Address) + } + + x.lock.RLock() + snap := newSnapshot(number, header.Hash(), masterNodes) + log.Info("[UpdateMasternodes] take snapshot", "number", number, "hash", header.Hash()) + x.lock.RUnlock() + + err := storeSnapshot(snap, x.db) + if err != nil { + log.Error("[UpdateMasternodes] Error while store snapshot", "hash", header.Hash(), "currentRound", x.currentRound, "error", err) + return err + } + x.snapshots.Add(snap.Hash, snap) + + log.Info("[UpdateMasternodes] New set of masternodes has been updated to snapshot", "number", snap.Number, "hash", snap.Hash) + for i, n := range ms { + log.Info("masternode", "index", i, "address", n.Address.String()) + } + + return nil +} + +// VerifyUncles implements consensus.Engine, always returning an error for any +// uncles as this consensus mechanism doesn't permit uncles. +func (x *XDPoS_v2) VerifyUncles(chain consensus.ChainReader, block *types.Block) error { + if len(block.Uncles()) > 0 { + return errors.New("uncles not allowed in XDPoS_v2") + } + return nil +} + +func (x *XDPoS_v2) VerifyHeader(chain consensus.ChainReader, header *types.Header, fullVerify bool) error { + err := x.verifyHeader(chain, header, nil, fullVerify) + if err != nil { + log.Debug("[VerifyHeader] Fail to verify header", "fullVerify", fullVerify, "blockNum", header.Number, "blockHash", header.Hash(), "error", err) + } + return err +} + +// Verify a list of headers +func (x *XDPoS_v2) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, fullVerifies []bool, abort <-chan struct{}, results chan<- error) { + go func() { + for i, header := range headers { + err := x.verifyHeader(chain, header, headers[:i], fullVerifies[i]) + if err != nil { + log.Warn("[VerifyHeaders] Fail to verify header", "fullVerify", fullVerifies[i], "blockNum", header.Number, "blockHash", header.Hash(), "error", err) + } + select { + case <-abort: + return + case results <- err: + } + } + }() +} + +/* +Proposed Block workflow +*/ +func (x *XDPoS_v2) ProposedBlockHandler(chain consensus.ChainReader, blockHeader *types.Header) error { + x.lock.Lock() + defer x.lock.Unlock() + + // Get QC and Round from Extra + quorumCert, round, _, err := x.getExtraFields(blockHeader) + if err != nil { + return err + } + + // Generate blockInfo + blockInfo := &types.BlockInfo{ + Hash: blockHeader.Hash(), + Round: round, + Number: blockHeader.Number, + } + err = x.processQC(chain, quorumCert) + if err != nil { + log.Error("[ProposedBlockHandler] Fail to processQC", "QC proposed blockInfo round number", quorumCert.ProposedBlockInfo.Round, "QC proposed blockInfo hash", quorumCert.ProposedBlockInfo.Hash) + return err + } + + allow := x.allowedToSend(chain, blockHeader, "vote") + if !allow { + return nil + } + + verified, err := x.verifyVotingRule(chain, blockInfo, quorumCert) + if err != nil { + return err + } + if verified { + return x.sendVote(chain, blockInfo) + } + + return nil +} + +/* + QC & TC Utils +*/ + +// To be used by different message verification. Verify local DB block info against the received block information(i.e hash, blockNum, round) +func (x *XDPoS_v2) VerifyBlockInfo(blockChainReader consensus.ChainReader, blockInfo *types.BlockInfo, blockHeader *types.Header) error { + /* + 1. Check if is able to get header by hash from the chain + 2. Check the header from step 1 matches what's in the blockInfo. This includes the block number and the round + */ + if blockHeader == nil { + blockHeader = blockChainReader.GetHeaderByHash(blockInfo.Hash) + if blockHeader == nil { + log.Warn("[VerifyBlockInfo] No such header in the chain", "BlockInfoHash", blockInfo.Hash.Hex(), "BlockInfoNum", blockInfo.Number, "BlockInfoRound", blockInfo.Round, "currentHeaderNum", blockChainReader.CurrentHeader().Number) + return fmt.Errorf("[VerifyBlockInfo] header doesn't exist for the received blockInfo at hash: %v", blockInfo.Hash.Hex()) + } + } else { + // If blockHeader present, then its value shall consistent with what's provided in the blockInfo + if blockHeader.Hash() != blockInfo.Hash { + log.Warn("[VerifyBlockInfo] BlockHeader and blockInfo mismatch", "BlockInfoHash", blockInfo.Hash.Hex(), "BlockHeaderHash", blockHeader.Hash()) + return errors.New("[VerifyBlockInfo] Provided blockheader does not match what's in the blockInfo") + } + } + + if blockHeader.Number.Cmp(blockInfo.Number) != 0 { + log.Warn("[VerifyBlockInfo] Block Number mismatch", "BlockInfoHash", blockInfo.Hash.Hex(), "BlockInfoNum", blockInfo.Number, "BlockInfoRound", blockInfo.Round, "blockHeaderNum", blockHeader.Number) + return fmt.Errorf("[VerifyBlockInfo] chain header number does not match for the received blockInfo at hash: %v", blockInfo.Hash.Hex()) + } + + // Switch block is a v1 block, there is no valid extra to decode, nor its round + if blockInfo.Number.Cmp(x.config.V2.SwitchBlock) == 0 { + if blockInfo.Round != 0 { + log.Error("[VerifyBlockInfo] Switch block round is not 0", "BlockInfoHash", blockInfo.Hash.Hex(), "BlockInfoNum", blockInfo.Number, "BlockInfoRound", blockInfo.Round, "blockHeaderNum", blockHeader.Number) + return errors.New("[VerifyBlockInfo] switch block round have to be 0") + } + return nil + } + // Check round + + _, round, _, err := x.getExtraFields(blockHeader) + if err != nil { + log.Error("[VerifyBlockInfo] Fail to decode extra field", "BlockInfoHash", blockInfo.Hash.Hex(), "BlockInfoNum", blockInfo.Number, "BlockInfoRound", blockInfo.Round, "blockHeaderNum", blockHeader.Number) + return err + } + if round != blockInfo.Round { + log.Warn("[VerifyBlockInfo] Block extra round mismatch with blockInfo", "BlockInfoHash", blockInfo.Hash.Hex(), "BlockInfoNum", blockInfo.Number, "BlockInfoRound", blockInfo.Round, "blockHeaderNum", blockHeader.Number, "blockRound", round) + return fmt.Errorf("[VerifyBlockInfo] chain block's round does not match from blockInfo at hash: %v and block round: %v, blockInfo Round: %v", blockInfo.Hash.Hex(), round, blockInfo.Round) + } + + return nil +} + +func (x *XDPoS_v2) verifyQC(blockChainReader consensus.ChainReader, quorumCert *types.QuorumCert, parentHeader *types.Header) error { + if quorumCert == nil { + log.Warn("[verifyQC] QC is Nil") + return utils.ErrInvalidQC + } + + epochInfo, err := x.getEpochSwitchInfo(blockChainReader, parentHeader, quorumCert.ProposedBlockInfo.Hash) + if err != nil { + log.Error("[verifyQC] Error when getting epoch switch Info to verify QC", "Error", err) + return errors.New("fail to verify QC due to failure in getting epoch switch info") + } + + signatures, duplicates := UniqueSignatures(quorumCert.Signatures) + if len(duplicates) != 0 { + for _, d := range duplicates { + log.Warn("[verifyQC] duplicated signature in QC", "duplicate", common.Bytes2Hex(d)) + } + } + + qcRound := quorumCert.ProposedBlockInfo.Round + certThreshold := x.config.V2.Config(uint64(qcRound)).CertThreshold + if (qcRound > 0) && (signatures == nil || float64(len(signatures)) < float64(epochInfo.MasternodesLen)*certThreshold) { + //First V2 Block QC, QC Signatures is initial nil + log.Warn("[verifyHeader] Invalid QC Signature is nil or less then config", "QCNumber", quorumCert.ProposedBlockInfo.Number, "LenSignatures", len(signatures), "CertThreshold", float64(epochInfo.MasternodesLen)*certThreshold) + return utils.ErrInvalidQCSignatures + } + start := time.Now() + + var wg sync.WaitGroup + wg.Add(len(signatures)) + var haveError error + + for _, signature := range signatures { + go func(sig types.Signature) { + defer wg.Done() + verified, _, err := x.verifyMsgSignature(types.VoteSigHash(&types.VoteForSign{ + ProposedBlockInfo: quorumCert.ProposedBlockInfo, + GapNumber: quorumCert.GapNumber, + }), sig, epochInfo.Masternodes) + if err != nil { + log.Error("[verifyQC] Error while verfying QC message signatures", "Error", err) + haveError = errors.New("error while verfying QC message signatures") + return + } + if !verified { + log.Warn("[verifyQC] Signature not verified doing QC verification", "QC", quorumCert) + haveError = errors.New("fail to verify QC due to signature mis-match") + return + } + }(signature) + } + wg.Wait() + elapsed := time.Since(start) + log.Debug("[verifyQC] time verify message signatures of qc", "elapsed", elapsed) + if haveError != nil { + return haveError + } + epochSwitchNumber := epochInfo.EpochSwitchBlockInfo.Number.Uint64() + gapNumber := epochSwitchNumber - epochSwitchNumber%x.config.Epoch - x.config.Gap + // prevent overflow + if epochSwitchNumber-epochSwitchNumber%x.config.Epoch < x.config.Gap { + gapNumber = 0 + } + if gapNumber != quorumCert.GapNumber { + log.Error("[verifyQC] QC gap number mismatch", "epochSwitchNumber", epochSwitchNumber, "BlockNum", quorumCert.ProposedBlockInfo.Number, "BlockInfoHash", quorumCert.ProposedBlockInfo.Hash, "Gap", quorumCert.GapNumber, "GapShouldBe", gapNumber) + return fmt.Errorf("gap number mismatch QC Gap %d, shouldBe %d", quorumCert.GapNumber, gapNumber) + } + + return x.VerifyBlockInfo(blockChainReader, quorumCert.ProposedBlockInfo, parentHeader) +} + +// Update local QC variables including highestQC & lockQuorumCert, as well as commit the blocks that satisfy the algorithm requirements +func (x *XDPoS_v2) processQC(blockChainReader consensus.ChainReader, incomingQuorumCert *types.QuorumCert) error { + log.Debug("[processQC][Before]", "HighQC", x.highestQuorumCert.ProposedBlockInfo.Round) + // 1. Update HighestQC + if incomingQuorumCert.ProposedBlockInfo.Round > x.highestQuorumCert.ProposedBlockInfo.Round { + log.Debug("[processQC] update x.highestQuorumCert", "blockNum", incomingQuorumCert.ProposedBlockInfo.Number, "round", incomingQuorumCert.ProposedBlockInfo.Round, "hash", incomingQuorumCert.ProposedBlockInfo.Hash) + x.highestQuorumCert = incomingQuorumCert + } + // 2. Get QC from header and update lockQuorumCert(lockQuorumCert is the parent of highestQC) + proposedBlockHeader := blockChainReader.GetHeaderByHash(incomingQuorumCert.ProposedBlockInfo.Hash) + if proposedBlockHeader == nil { + log.Error("[processQC] Block not found using the QC", "quorumCert.ProposedBlockInfo.Hash", incomingQuorumCert.ProposedBlockInfo.Hash, "incomingQuorumCert.ProposedBlockInfo.Number", incomingQuorumCert.ProposedBlockInfo.Number) + return fmt.Errorf("block not found, number: %v, hash: %v", incomingQuorumCert.ProposedBlockInfo.Number, incomingQuorumCert.ProposedBlockInfo.Hash) + } + if proposedBlockHeader.Number.Cmp(x.config.V2.SwitchBlock) > 0 { + // Extra field contain parent information + proposedBlockQuorumCert, round, _, err := x.getExtraFields(proposedBlockHeader) + if err != nil { + return err + } + if x.lockQuorumCert == nil || proposedBlockQuorumCert.ProposedBlockInfo.Round > x.lockQuorumCert.ProposedBlockInfo.Round { + x.lockQuorumCert = proposedBlockQuorumCert + } + + proposedBlockRound := &round + // 3. Update commit block info + _, err = x.commitBlocks(blockChainReader, proposedBlockHeader, proposedBlockRound, incomingQuorumCert) + if err != nil { + log.Error("[processQC] Error while to commitBlocks", "proposedBlockRound", proposedBlockRound) + return err + } + } + // 4. Set new round + if incomingQuorumCert.ProposedBlockInfo.Round >= x.currentRound { + x.setNewRound(blockChainReader, incomingQuorumCert.ProposedBlockInfo.Round+1) + } + log.Debug("[processQC][After]", "HighQC", x.highestQuorumCert.ProposedBlockInfo.Round) + return nil +} + +/* +1. Set currentRound = QC round + 1 (or TC round +1) +2. Reset timer +3. Reset vote and timeout Pools +4. Send signal to miner +*/ +func (x *XDPoS_v2) setNewRound(blockChainReader consensus.ChainReader, round types.Round) { + log.Info("[setNewRound] new round and reset pools and workers", "round", round) + x.currentRound = round + x.timeoutCount = 0 + x.timeoutWorker.Reset(blockChainReader, x.currentRound, x.highestQuorumCert.ProposedBlockInfo.Round) + // don't need to clean pool, we have other process to clean and it's not good to clean here, some edge case may break + // for example round gets bump during collecting vote, so we have to keep vote. + + // send signal to newRoundCh, but if full don't send + select { + case x.newRoundCh <- round: + default: + } +} + +func (x *XDPoS_v2) broadcastToBftChannel(msg interface{}) { + go func() { + x.BroadcastCh <- msg + }() +} + +func (x *XDPoS_v2) getSyncInfo() *types.SyncInfo { + return &types.SyncInfo{ + HighestQuorumCert: x.highestQuorumCert, + HighestTimeoutCert: x.highestTimeoutCert, + } +} + +// Find parent and grandparent, check round number, if so, commit grandparent(grandGrandParent of currentBlock) +func (x *XDPoS_v2) commitBlocks(blockChainReader consensus.ChainReader, proposedBlockHeader *types.Header, proposedBlockRound *types.Round, incomingQc *types.QuorumCert) (bool, error) { + // XDPoS v1.0 switch to v2.0, skip commit + if big.NewInt(0).Sub(proposedBlockHeader.Number, big.NewInt(2)).Cmp(x.config.V2.SwitchBlock) <= 0 { + return false, nil + } + // Find the last two parent block and check their rounds are the continuous + parentBlock := blockChainReader.GetHeaderByHash(proposedBlockHeader.ParentHash) + + _, round, _, err := x.getExtraFields(parentBlock) + if err != nil { + log.Error("Fail to execute first DecodeBytesExtraFields for committing block", "ProposedBlockHash", proposedBlockHeader.Hash()) + return false, err + } + if *proposedBlockRound-1 != round { + log.Info("[commitBlocks] Rounds not continuous(parent) found when committing block", "proposedBlockRound", *proposedBlockRound, "decodedExtraField.Round", round, "proposedBlockHeaderHash", proposedBlockHeader.Hash()) + return false, nil + } + + // If parent round is continuous, we check grandparent + grandParentBlock := blockChainReader.GetHeaderByHash(parentBlock.ParentHash) + _, round, _, err = x.getExtraFields(grandParentBlock) + if err != nil { + log.Error("Fail to execute second DecodeBytesExtraFields for committing block", "parentBlockHash", parentBlock.Hash()) + return false, err + } + if *proposedBlockRound-2 != round { + log.Info("[commitBlocks] Rounds not continuous(grand parent) found when committing block", "proposedBlockRound", *proposedBlockRound, "decodedExtraField.Round", round, "proposedBlockHeaderHash", proposedBlockHeader.Hash()) + return false, nil + } + + if x.highestCommitBlock != nil && (x.highestCommitBlock.Round >= round || x.highestCommitBlock.Number.Cmp(grandParentBlock.Number) == 1) { + return false, nil + } + + // Process Commit + x.highestCommitBlock = &types.BlockInfo{ + Number: grandParentBlock.Number, + Hash: grandParentBlock.Hash(), + Round: round, + } + log.Info("Successfully commit and confirm block from continuous 3 blocks", "num", x.highestCommitBlock.Number, "round", x.highestCommitBlock.Round, "hash", x.highestCommitBlock.Hash) + // Perform forensics related operation + headerQcToBeCommitted := []types.Header{*parentBlock, *proposedBlockHeader} + go x.ForensicsProcessor.ForensicsMonitoring(blockChainReader, x, headerQcToBeCommitted, *incomingQc) + return true, nil +} + +// Get master nodes over extra data of epoch switch block. +func (x *XDPoS_v2) GetMasternodesFromEpochSwitchHeader(epochSwitchHeader *types.Header) []common.Address { + if epochSwitchHeader == nil { + log.Error("[GetMasternodesFromEpochSwitchHeader] use nil epoch switch block to get master nodes") + return []common.Address{} + } + masternodes := make([]common.Address, len(epochSwitchHeader.Validators)/common.AddressLength) + for i := 0; i < len(masternodes); i++ { + copy(masternodes[i][:], epochSwitchHeader.Validators[i*common.AddressLength:]) + } + + return masternodes +} + +// Given header, get master node from the epoch switch block of that epoch +func (x *XDPoS_v2) GetMasternodes(chain consensus.ChainReader, header *types.Header) []common.Address { + epochSwitchInfo, err := x.getEpochSwitchInfo(chain, header, header.Hash()) + if err != nil { + log.Error("[GetMasternodes] Adaptor v2 getEpochSwitchInfo has error", "err", err) + return []common.Address{} + } + return epochSwitchInfo.Masternodes +} + +// Given header, get master node from the epoch switch block of that epoch +func (x *XDPoS_v2) GetPenalties(chain consensus.ChainReader, header *types.Header) []common.Address { + epochSwitchInfo, err := x.getEpochSwitchInfo(chain, header, header.Hash()) + if err != nil { + log.Error("[GetPenalties] Adaptor v2 getEpochSwitchInfo has error", "err", err) + return []common.Address{} + } + return epochSwitchInfo.Penalties +} + +func (x *XDPoS_v2) GetStandbynodes(chain consensus.ChainReader, header *types.Header) []common.Address { + epochSwitchInfo, err := x.getEpochSwitchInfo(chain, header, header.Hash()) + if err != nil { + log.Error("[GetStandbynodes] Adaptor v2 getEpochSwitchInfo has error", "err", err) + return []common.Address{} + } + return epochSwitchInfo.Standbynodes +} + +// Calculate masternodes for a block number and parent hash. In V2, truncating candidates[:MaxMasternodes] is done in this function. +func (x *XDPoS_v2) calcMasternodes(chain consensus.ChainReader, blockNum *big.Int, parentHash common.Hash, round types.Round) ([]common.Address, []common.Address, error) { + // using new max masterndoes + maxMasternodes := x.config.V2.Config(uint64(round)).MaxMasternodes + snap, err := x.getSnapshot(chain, blockNum.Uint64(), false) + if err != nil { + log.Error("[calcMasternodes] Adaptor v2 getSnapshot has error", "err", err) + return nil, nil, err + } + candidates := snap.NextEpochCandidates + + if blockNum.Uint64() == x.config.V2.SwitchBlock.Uint64()+1 { + log.Info("[calcMasternodes] examing first v2 block") + if len(candidates) > maxMasternodes { + candidates = candidates[:maxMasternodes] + } + return candidates, []common.Address{}, nil + } + + if x.HookPenalty == nil { + log.Info("[calcMasternodes] no hook penalty defined") + if len(candidates) > maxMasternodes { + candidates = candidates[:maxMasternodes] + } + return candidates, []common.Address{}, nil + } + + penalties, err := x.HookPenalty(chain, blockNum, parentHash, candidates) + if err != nil { + log.Error("[calcMasternodes] Adaptor v2 HookPenalty has error", "err", err) + return nil, nil, err + } + masternodes := common.RemoveItemFromArray(candidates, penalties) + if len(masternodes) > maxMasternodes { + masternodes = masternodes[:maxMasternodes] + } + + return masternodes, penalties, nil +} + +// Given hash, get master node from the epoch switch block of the epoch +func (x *XDPoS_v2) GetMasternodesByHash(chain consensus.ChainReader, hash common.Hash) []common.Address { + epochSwitchInfo, err := x.getEpochSwitchInfo(chain, nil, hash) + if err != nil { + log.Error("[GetMasternodes] Adaptor v2 getEpochSwitchInfo has error, potentially bug", "err", err) + return []common.Address{} + } + return epochSwitchInfo.Masternodes +} + +// Given hash, get master node from the epoch switch block of the previous `limit` epoch +func (x *XDPoS_v2) GetPreviousPenaltyByHash(chain consensus.ChainReader, hash common.Hash, limit int) []common.Address { + currentEpochSwitchInfo, err := x.getEpochSwitchInfo(chain, nil, hash) + if err != nil { + log.Error("[GetPreviousPenaltyByHash] Adaptor v2 getPreviousEpochSwitchInfoByHash has error, potentially bug", "err", err) + return []common.Address{} + } + if limit == 0 { + return currentEpochSwitchInfo.Penalties + } + epochNum := x.config.V2.SwitchEpoch + uint64(currentEpochSwitchInfo.EpochSwitchBlockInfo.Round)/x.config.Epoch + if epochNum < uint64(limit) { + // avoid negative number + log.Error("[GetPreviousPenaltyByHash] Adaptor v2 getPreviousEpochSwitchInfoByHash has error, too large limit", "limit", limit) + return []common.Address{} + } + _, header, err := x.binarySearchBlockByEpochNumber(chain, epochNum-uint64(limit), currentEpochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64()-x.config.Epoch*uint64(limit), currentEpochSwitchInfo.EpochSwitchParentBlockInfo.Number.Uint64()) + if err != nil { + log.Error("[GetPreviousPenaltyByHash] Adaptor v2 getPreviousEpochSwitchInfoByHash has error, potentially bug", "err", err) + return []common.Address{} + } + return common.ExtractAddressFromBytes(header.Penalties) +} + +func (x *XDPoS_v2) FindParentBlockToAssign(chain consensus.ChainReader) *types.Block { + parent := chain.GetBlock(x.highestQuorumCert.ProposedBlockInfo.Hash, x.highestQuorumCert.ProposedBlockInfo.Number.Uint64()) + if parent == nil { + log.Error("[FindParentBlockToAssign] Can not find parent block from highestQC proposedBlockInfo", "x.highestQuorumCert.ProposedBlockInfo.Hash", x.highestQuorumCert.ProposedBlockInfo.Hash, "x.highestQuorumCert.ProposedBlockInfo.Number", x.highestQuorumCert.ProposedBlockInfo.Number.Uint64()) + } + return parent +} + +func (x *XDPoS_v2) allowedToSend(chain consensus.ChainReader, blockHeader *types.Header, sendType string) bool { + // Don't hold the signFn for the whole signing operation + x.signLock.RLock() + signer := x.signer + x.signLock.RUnlock() + // Check if the node can send this sendType + masterNodes := x.GetMasternodes(chain, blockHeader) + for i, mn := range masterNodes { + if signer == mn { + log.Debug("[allowedToSend] Yes, I'm allowed to send", "sendType", sendType, "MyAddress", signer.Hex(), "Index in master node list", i) + return true + } + } + for _, mn := range masterNodes { + log.Debug("[allowedToSend] Master node list", "masterNodeAddress", mn.Hash()) + } + log.Debug("[allowedToSend] Not in the Masternode list, not suppose to send message", "sendType", sendType, "MyAddress", signer.Hex()) + return false +} + +// Periodlly execution(Attached to engine initialisation during "new"). Used for pool cleaning etc +func (x *XDPoS_v2) periodicJob() { + go func() { + ticker := time.NewTicker(utils.PeriodicJobPeriod * time.Second) + defer ticker.Stop() + for { + <-ticker.C + x.hygieneVotePool() + x.hygieneTimeoutPool() + x.hygieneSyncInfoPool() + } + }() +} + +func (x *XDPoS_v2) GetLatestCommittedBlockInfo() *types.BlockInfo { + return x.highestCommitBlock +} diff --git a/consensus/XDPoS/engines/engine_v2_subnet/epochSwitch.go b/consensus/XDPoS/engines/engine_v2_subnet/epochSwitch.go new file mode 100644 index 000000000000..683b6b567786 --- /dev/null +++ b/consensus/XDPoS/engines/engine_v2_subnet/epochSwitch.go @@ -0,0 +1,219 @@ +package engine_v2_subnet + +import ( + "fmt" + "math/big" + + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/consensus" + "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/log" +) + +// get epoch switch of the previous `limit` epoch +func (x *XDPoS_v2) getPreviousEpochSwitchInfoByHash(chain consensus.ChainReader, hash common.Hash, limit int) (*types.EpochSwitchInfo, error) { + epochSwitchInfo, err := x.getEpochSwitchInfo(chain, nil, hash) + if err != nil { + log.Error("[getPreviousEpochSwitchInfoByHash] Adaptor v2 getEpochSwitchInfo has error, potentially bug", "err", err) + return nil, err + } + for i := 0; i < limit; i++ { + epochSwitchInfo, err = x.getEpochSwitchInfo(chain, nil, epochSwitchInfo.EpochSwitchParentBlockInfo.Hash) + if err != nil { + log.Error("[getPreviousEpochSwitchInfoByHash] Adaptor v2 getEpochSwitchInfo has error, potentially bug", "err", err) + return nil, err + } + } + return epochSwitchInfo, nil +} + +// Given header and its hash, get epoch switch info from the epoch switch block of that epoch, +// header is allow to be nil. +func (x *XDPoS_v2) getEpochSwitchInfo(chain consensus.ChainReader, header *types.Header, hash common.Hash) (*types.EpochSwitchInfo, error) { + epochSwitchInfo, ok := x.epochSwitches.Get(hash) + if ok && epochSwitchInfo != nil { + log.Debug("[getEpochSwitchInfo] cache hit", "number", epochSwitchInfo.EpochSwitchBlockInfo.Number, "hash", hash.Hex()) + return epochSwitchInfo, nil + } + h := header + if h == nil { + log.Debug("[getEpochSwitchInfo] header doesn't provide, get header by hash", "hash", hash.Hex()) + h = chain.GetHeaderByHash(hash) + if h == nil { + return nil, fmt.Errorf("[getEpochSwitchInfo] can not find header from db hash %v", hash.Hex()) + } + } + isEpochSwitch, _, err := x.IsEpochSwitch(h) + if err != nil { + return nil, err + } + if isEpochSwitch { + log.Debug("[getEpochSwitchInfo] header is epoch switch", "hash", hash.Hex(), "number", h.Number.Uint64()) + if h.Number.Uint64() == 0 { + log.Warn("[getEpochSwitchInfo] block 0, init epoch differently") + // handle genesis block differently as follows + masternodes := common.ExtractAddressFromBytes(h.Extra[32 : len(h.Extra)-65]) + penalties := []common.Address{} + standbynodes := []common.Address{} + epochSwitchInfo := &types.EpochSwitchInfo{ + Penalties: penalties, + Standbynodes: standbynodes, + Masternodes: masternodes, + MasternodesLen: len(masternodes), + EpochSwitchBlockInfo: &types.BlockInfo{ + Hash: hash, + Number: h.Number, + Round: 0, + }, + } + x.epochSwitches.Add(hash, epochSwitchInfo) + return epochSwitchInfo, nil + } + quorumCert, round, masternodes, err := x.getExtraFields(h) + if err != nil { + log.Error("[getEpochSwitchInfo] get extra field", "err", err, "number", h.Number.Uint64()) + return nil, err + } + snap, err := x.getSnapshot(chain, h.Number.Uint64(), false) + if err != nil { + log.Error("[getEpochSwitchInfo] Adaptor v2 getSnapshot has error", "err", err) + return nil, err + } + penalties := common.ExtractAddressFromBytes(h.Penalties) + candidates := snap.NextEpochCandidates + standbynodes := []common.Address{} + if len(masternodes) != len(candidates) { + standbynodes = candidates + standbynodes = common.RemoveItemFromArray(standbynodes, masternodes) + standbynodes = common.RemoveItemFromArray(standbynodes, penalties) + } + + epochSwitchInfo := &types.EpochSwitchInfo{ + Penalties: penalties, + Standbynodes: standbynodes, + Masternodes: masternodes, + MasternodesLen: len(masternodes), + EpochSwitchBlockInfo: &types.BlockInfo{ + Hash: hash, + Number: h.Number, + Round: round, + }, + } + if quorumCert != nil { + epochSwitchInfo.EpochSwitchParentBlockInfo = quorumCert.ProposedBlockInfo + } + + x.epochSwitches.Add(hash, epochSwitchInfo) + return epochSwitchInfo, nil + } + epochSwitchInfo, err = x.getEpochSwitchInfo(chain, nil, h.ParentHash) + if err != nil { + log.Error("[getEpochSwitchInfo] recursive error", "err", err, "hash", hash.Hex(), "number", h.Number.Uint64()) + return nil, err + } + log.Debug("[getEpochSwitchInfo] get epoch switch info recursively", "hash", hash.Hex(), "number", h.Number.Uint64()) + x.epochSwitches.Add(hash, epochSwitchInfo) + return epochSwitchInfo, nil +} + +// IsEpochSwitchAtRound() is used by miner to check whether it mines a block in the same epoch with parent +func (x *XDPoS_v2) isEpochSwitchAtRound(round types.Round, parentHeader *types.Header) (bool, uint64, error) { + epochNum := x.config.V2.SwitchEpoch + uint64(round)/x.config.Epoch + // if parent is last v1 block and this is first v2 block, this is treated as epoch switch + if parentHeader.Number.Cmp(x.config.V2.SwitchBlock) == 0 { + return true, epochNum, nil + } + + _, parentRound, _, err := x.getExtraFields(parentHeader) + if err != nil { + log.Error("[IsEpochSwitch] decode header error", "err", err, "header", parentHeader, "extra", common.Bytes2Hex(parentHeader.Extra)) + return false, 0, err + } + if round <= parentRound { + // this round is no larger than parentRound, should return false + return false, epochNum, nil + } + + epochStartRound := round - round%types.Round(x.config.Epoch) + return parentRound < epochStartRound, epochNum, nil +} + +func (x *XDPoS_v2) GetCurrentEpochSwitchBlock(chain consensus.ChainReader, blockNum *big.Int) (uint64, uint64, error) { + header := chain.GetHeaderByNumber(blockNum.Uint64()) + epochSwitchInfo, err := x.getEpochSwitchInfo(chain, header, header.Hash()) + if err != nil { + log.Error("[GetCurrentEpochSwitchBlock] Fail to get epoch switch info", "Num", header.Number, "Hash", header.Hash()) + return 0, 0, err + } + + currentCheckpointNumber := epochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64() + epochNum := x.config.V2.SwitchEpoch + uint64(epochSwitchInfo.EpochSwitchBlockInfo.Round)/x.config.Epoch + return currentCheckpointNumber, epochNum, nil +} + +func (x *XDPoS_v2) IsEpochSwitch(header *types.Header) (bool, uint64, error) { + // Return true directly if we are examing the last v1 block. This could happen if the calling function is examing parent block + if header.Number.Cmp(x.config.V2.SwitchBlock) == 0 { + log.Info("[IsEpochSwitch] examing last v1 block") + return true, header.Number.Uint64() / x.config.Epoch, nil + } + + quorumCert, round, _, err := x.getExtraFields(header) + if err != nil { + log.Error("[IsEpochSwitch] decode header error", "err", err, "header", header, "extra", common.Bytes2Hex(header.Extra)) + return false, 0, err + } + parentRound := quorumCert.ProposedBlockInfo.Round + epochStartRound := round - round%types.Round(x.config.Epoch) + epochNum := x.config.V2.SwitchEpoch + uint64(round)/x.config.Epoch + // if parent is last v1 block and this is first v2 block, this is treated as epoch switch + if quorumCert.ProposedBlockInfo.Number.Cmp(x.config.V2.SwitchBlock) == 0 { + log.Info("[IsEpochSwitch] true, parent equals V2.SwitchBlock", "round", round, "number", header.Number.Uint64(), "hash", header.Hash()) + return true, epochNum, nil + } + log.Debug("[IsEpochSwitch]", "is", parentRound < epochStartRound, "parentRound", parentRound, "round", round, "number", header.Number.Uint64(), "epochNum", epochNum, "hash", header.Hash().Hex()) + // if isEpochSwitch, add to cache + if parentRound < epochStartRound { + x.round2epochBlockInfo.Add(round, &types.BlockInfo{ + Hash: header.Hash(), + Number: header.Number, + Round: round, + }) + } + return parentRound < epochStartRound, epochNum, nil +} + +// GetEpochSwitchInfoBetween get epoch switch between begin and end headers +// Search backwardly from end number to begin number +func (x *XDPoS_v2) GetEpochSwitchInfoBetween(chain consensus.ChainReader, begin, end *types.Header) ([]*types.EpochSwitchInfo, error) { + infos := make([]*types.EpochSwitchInfo, 0) + // after the first iteration, it becomes nil since epoch switch info does not have header info + iteratorHeader := end + // after the first iteration, it becomes the parent hash of the epoch switch block + iteratorHash := end.Hash() + iteratorNum := end.Number + // when iterator is strictly > begin number, do the search + for iteratorNum.Cmp(begin.Number) > 0 { + epochSwitchInfo, err := x.getEpochSwitchInfo(chain, iteratorHeader, iteratorHash) + if err != nil { + log.Error("[GetEpochSwitchInfoBetween] Adaptor v2 getEpochSwitchInfo has error, potentially bug", "err", err) + return nil, err + } + iteratorHeader = nil + // V2 switch epoch switch info has nil parent + if epochSwitchInfo.EpochSwitchParentBlockInfo == nil { + break + } + iteratorHash = epochSwitchInfo.EpochSwitchParentBlockInfo.Hash + iteratorNum = epochSwitchInfo.EpochSwitchBlockInfo.Number + if iteratorNum.Cmp(begin.Number) >= 0 { + infos = append(infos, epochSwitchInfo) + } + + } + // reverse the array + for i := 0; i < len(infos)/2; i++ { + infos[i], infos[len(infos)-1-i] = infos[len(infos)-1-i], infos[i] + } + return infos, nil +} diff --git a/consensus/XDPoS/engines/engine_v2_subnet/forensics.go b/consensus/XDPoS/engines/engine_v2_subnet/forensics.go new file mode 100644 index 000000000000..0c2905a96617 --- /dev/null +++ b/consensus/XDPoS/engines/engine_v2_subnet/forensics.go @@ -0,0 +1,566 @@ +package engine_v2_subnet + +import ( + "encoding/json" + "errors" + "fmt" + "math/big" + "reflect" + "strconv" + "strings" + + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/consensus" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" + "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/crypto" + "github.com/XinFinOrg/XDPoSChain/event" + "github.com/XinFinOrg/XDPoSChain/log" +) + +const ( + NUM_OF_FORENSICS_QC = 3 +) + +// Forensics instance. Placeholder for future properties to be added +type Forensics struct { + HighestCommittedQCs []types.QuorumCert + forensicsFeed event.Feed + scope event.SubscriptionScope +} + +// Initiate a forensics process +func NewForensics() *Forensics { + return &Forensics{} +} + +// SubscribeForensicsEvent registers a subscription of ForensicsEvent and +// starts sending event to the given channel. +func (f *Forensics) SubscribeForensicsEvent(ch chan<- types.ForensicsEvent) event.Subscription { + return f.scope.Track(f.forensicsFeed.Subscribe(ch)) +} + +func (f *Forensics) ForensicsMonitoring(chain consensus.ChainReader, engine *XDPoS_v2, headerQcToBeCommitted []types.Header, incomingQC types.QuorumCert) error { + f.ProcessForensics(chain, engine, incomingQC) + return f.SetCommittedQCs(headerQcToBeCommitted, incomingQC) +} + +// Set the forensics committed QCs list. The order is from grandparent to current header. i.e it shall follow the QC in its header as follow [hcqc1, hcqc2, hcqc3] +func (f *Forensics) SetCommittedQCs(headers []types.Header, incomingQC types.QuorumCert) error { + // highestCommitQCs is an array, assign the parentBlockQc and its child as well as its grandchild QC into this array for forensics purposes. + if len(headers) != NUM_OF_FORENSICS_QC-1 { + log.Error("[SetCommittedQcs] Received input length not equal to 2", len(headers)) + return errors.New("received headers length not equal to 2 ") + } + + var committedQCs []types.QuorumCert + for i, h := range headers { + var decodedExtraField types.ExtraFields_v2 + // Decode the qc1 and qc2 + err := utils.DecodeBytesExtraFields(h.Extra, &decodedExtraField) + if err != nil { + log.Error("[SetCommittedQCs] Fail to decode extra when committing QC to forensics", "err", err, "index", i) + return err + } + if i != 0 { + if decodedExtraField.QuorumCert.ProposedBlockInfo.Hash != headers[i-1].Hash() { + log.Error("[SetCommittedQCs] Headers shall be on the same chain and in the right order", "parentHash", h.ParentHash.Hex(), "headers[i-1].Hash()", headers[i-1].Hash().Hex()) + return errors.New("headers shall be on the same chain and in the right order") + } else if i == len(headers)-1 { // The last header shall be pointed by the incoming QC + if incomingQC.ProposedBlockInfo.Hash != h.Hash() { + log.Error("[SetCommittedQCs] incomingQc is not pointing at the last header received", "hash", h.Hash().Hex(), "incomingQC.ProposedBlockInfo.Hash", incomingQC.ProposedBlockInfo.Hash.Hex()) + return errors.New("incomingQc is not pointing at the last header received") + } + } + } + + committedQCs = append(committedQCs, *decodedExtraField.QuorumCert) + } + f.HighestCommittedQCs = append(committedQCs, incomingQC) + return nil +} + +/* +Entry point for processing forensics. +Triggered once processQC is successfully. +Forensics runs in a separate go routine as its no system critical +Link to the flow diagram: https://hashlabs.atlassian.net/wiki/spaces/HASHLABS/pages/97878029/Forensics+Diagram+flow +*/ +func (f *Forensics) ProcessForensics(chain consensus.ChainReader, engine *XDPoS_v2, incomingQC types.QuorumCert) error { + return nil + log.Debug("Received a QC in forensics", "QC", incomingQC) + // Clone the values to a temporary variable + highestCommittedQCs := f.HighestCommittedQCs + if len(highestCommittedQCs) != NUM_OF_FORENSICS_QC { + log.Error("[ProcessForensics] HighestCommittedQCs value not set", "incomingQcProposedBlockHash", incomingQC.ProposedBlockInfo.Hash, "incomingQcProposedBlockNumber", incomingQC.ProposedBlockInfo.Number.Uint64(), "incomingQcProposedBlockRound", incomingQC.ProposedBlockInfo.Round) + return errors.New("HighestCommittedQCs value not set") + } + // Find the QC1 and QC2. We only care 2 parents in front of the incomingQC. The returned value contains QC1, QC2 and QC3(the incomingQC) + incomingQuorunCerts, err := f.findAncestorQCs(chain, incomingQC, 2) + if err != nil { + return err + } + isOnTheChain, err := f.checkQCsOnTheSameChain(chain, highestCommittedQCs, incomingQuorunCerts) + if err != nil { + return err + } + if isOnTheChain { + // Passed the checking, nothing suspicious. + log.Debug("[ProcessForensics] Passed forensics checking, nothing suspicious need to be reported", "incomingQcProposedBlockHash", incomingQC.ProposedBlockInfo.Hash, "incomingQcProposedBlockNumber", incomingQC.ProposedBlockInfo.Number.Uint64(), "incomingQcProposedBlockRound", incomingQC.ProposedBlockInfo.Round) + return nil + } + // Trigger the safety Alarm if failed + // First, find the QC in the two sets that have the same round + foundSameRoundQC, sameRoundHCQC, sameRoundQC := f.findQCsInSameRound(highestCommittedQCs, incomingQuorunCerts) + + if foundSameRoundQC { + f.SendForensicProof(chain, engine, sameRoundHCQC, sameRoundQC) + } else { + // Not found, need a more complex approach to find the two QC + ancestorQC, lowerRoundQCs, _, err := f.findAncestorQcThroughRound(chain, highestCommittedQCs, incomingQuorunCerts) + if err != nil { + log.Error("[ProcessForensics] Error while trying to find ancestor QC through round number", "err", err) + } + f.SendForensicProof(chain, engine, ancestorQC, lowerRoundQCs[NUM_OF_FORENSICS_QC-1]) + } + + return nil +} + +// Last step of forensics which sends out detailed proof to report service. +func (f *Forensics) SendForensicProof(chain consensus.ChainReader, engine *XDPoS_v2, firstQc types.QuorumCert, secondQc types.QuorumCert) error { + // Re-order the QC by its round number to make the function cleaner. + lowerRoundQC := firstQc + higherRoundQC := secondQc + + if secondQc.ProposedBlockInfo.Round < firstQc.ProposedBlockInfo.Round { + lowerRoundQC = secondQc + higherRoundQC = firstQc + } + + // Find common ancestor block + ancestorHash, ancestorToLowerRoundPath, ancestorToHigherRoundPath, err := f.FindAncestorBlockHash(chain, lowerRoundQC.ProposedBlockInfo, higherRoundQC.ProposedBlockInfo) + if err != nil { + log.Error("[SendForensicProof] Error while trying to find ancestor block hash", "err", err) + return err + } + + // Check if two QCs are across epoch, this is used as a indicator for the "prone to attack" scenario + lowerRoundQcEpochSwitchInfo, err := engine.getEpochSwitchInfo(chain, nil, lowerRoundQC.ProposedBlockInfo.Hash) + if err != nil { + log.Error("[SendForensicProof] Errir while trying to find lowerRoundQcEpochSwitchInfo", "lowerRoundQC.ProposedBlockInfo.Hash", lowerRoundQC.ProposedBlockInfo.Hash, "err", err) + return err + } + higherRoundQcEpochSwitchInfo, err := engine.getEpochSwitchInfo(chain, nil, higherRoundQC.ProposedBlockInfo.Hash) + if err != nil { + log.Error("[SendForensicProof] Errir while trying to find higherRoundQcEpochSwitchInfo", "higherRoundQC.ProposedBlockInfo.Hash", higherRoundQC.ProposedBlockInfo.Hash, "err", err) + return err + } + accrossEpoches := false + if lowerRoundQcEpochSwitchInfo.EpochSwitchBlockInfo.Hash != higherRoundQcEpochSwitchInfo.EpochSwitchBlockInfo.Hash { + accrossEpoches = true + } + + ancestorBlock := chain.GetHeaderByHash(ancestorHash) + + if ancestorBlock == nil { + log.Error("[SendForensicProof] Unable to find the ancestor block by its hash", "Hash", ancestorHash) + return errors.New("can't find ancestor block via hash") + } + + content, err := json.Marshal(&types.ForensicsContent{ + DivergingBlockHash: ancestorHash.Hex(), + AcrossEpoch: accrossEpoches, + DivergingBlockNumber: ancestorBlock.Number.Uint64(), + SmallerRoundInfo: &types.ForensicsInfo{ + HashPath: ancestorToLowerRoundPath, + QuorumCert: lowerRoundQC, + SignerAddresses: f.getQcSignerAddresses(lowerRoundQC), + }, + LargerRoundInfo: &types.ForensicsInfo{ + HashPath: ancestorToHigherRoundPath, + QuorumCert: higherRoundQC, + SignerAddresses: f.getQcSignerAddresses(higherRoundQC), + }, + }) + + if err != nil { + log.Error("[SendForensicProof] fail to json stringify forensics content", "err", err) + return err + } + + forensicsProof := &types.ForensicProof{ + Id: generateForensicsId(ancestorHash.Hex(), &lowerRoundQC, &higherRoundQC), + ForensicsType: "QC", + Content: string(content), + } + log.Info("Forensics proof report generated, sending to the stats server", "forensicsProof", forensicsProof) + go f.forensicsFeed.Send(types.ForensicsEvent{ForensicsProof: forensicsProof}) + return nil +} + +// Utils function to help find the n-th previous QC. It returns an array of QC in ascending order including the currentQc as the last item in the array +func (f *Forensics) findAncestorQCs(chain consensus.ChainReader, currentQc types.QuorumCert, distanceFromCurrrentQc int) ([]types.QuorumCert, error) { + var quorumCerts []types.QuorumCert + quorumCertificate := currentQc + // Append the initial value + quorumCerts = append(quorumCerts, quorumCertificate) + // Append the parents + for i := 0; i < distanceFromCurrrentQc; i++ { + parentHash := quorumCertificate.ProposedBlockInfo.Hash + parentHeader := chain.GetHeaderByHash(parentHash) + if parentHeader == nil { + log.Error("[findAncestorQCs] Forensics findAncestorQCs unable to find its parent block header", "ParentHash", parentHash.Hex()) + return nil, errors.New("unable to find parent block header in forensics") + } + var decodedExtraField types.ExtraFields_v2 + err := utils.DecodeBytesExtraFields(parentHeader.Extra, &decodedExtraField) + if err != nil { + log.Error("[findAncestorQCs] Error while trying to decode from parent block extra", "BlockNum", parentHeader.Number.Int64(), "ParentHash", parentHash.Hex()) + } + quorumCertificate = *decodedExtraField.QuorumCert + quorumCerts = append(quorumCerts, quorumCertificate) + } + // The quorumCerts is in the reverse order, we need to flip it + var quorumCertsInAscendingOrder []types.QuorumCert + for i := len(quorumCerts) - 1; i >= 0; i-- { + quorumCertsInAscendingOrder = append(quorumCertsInAscendingOrder, quorumCerts[i]) + } + return quorumCertsInAscendingOrder, nil +} + +// Check whether two provided QC set are on the same chain +func (f *Forensics) checkQCsOnTheSameChain(chain consensus.ChainReader, highestCommittedQCs []types.QuorumCert, incomingQCandItsParents []types.QuorumCert) (bool, error) { + // Re-order two sets of QCs by block Number + lowerBlockNumQCs := highestCommittedQCs + higherBlockNumQCs := incomingQCandItsParents + if incomingQCandItsParents[0].ProposedBlockInfo.Number.Cmp(highestCommittedQCs[0].ProposedBlockInfo.Number) == -1 { + lowerBlockNumQCs = incomingQCandItsParents + higherBlockNumQCs = highestCommittedQCs + } + + proposedBlockInfo := higherBlockNumQCs[0].ProposedBlockInfo + for i := 0; i < int((big.NewInt(0).Sub(higherBlockNumQCs[0].ProposedBlockInfo.Number, lowerBlockNumQCs[0].ProposedBlockInfo.Number)).Int64()); i++ { + parentHeader := chain.GetHeaderByHash(proposedBlockInfo.Hash) + var decodedExtraField types.ExtraFields_v2 + err := utils.DecodeBytesExtraFields(parentHeader.Extra, &decodedExtraField) + if err != nil { + log.Error("[checkQCsOnTheSameChain] Fail to decode extra when checking the two QCs set on the same chain", "err", err) + return false, err + } + proposedBlockInfo = decodedExtraField.QuorumCert.ProposedBlockInfo + } + // Check the final proposed blockInfo is the same as what we have from lowerBlockNumQCs[0] + if reflect.DeepEqual(proposedBlockInfo, lowerBlockNumQCs[0].ProposedBlockInfo) { + return true, nil + } + + return false, nil +} + +// Given the two QCs set, find if there are any QC that have the same round +func (f *Forensics) findQCsInSameRound(quorumCerts1 []types.QuorumCert, quorumCerts2 []types.QuorumCert) (bool, types.QuorumCert, types.QuorumCert) { + for _, quorumCert1 := range quorumCerts1 { + for _, quorumCert2 := range quorumCerts2 { + if quorumCert1.ProposedBlockInfo.Round == quorumCert2.ProposedBlockInfo.Round { + return true, quorumCert1, quorumCert2 + } + } + } + return false, types.QuorumCert{}, types.QuorumCert{} +} + +// Find the signer list from QC signatures +func (f *Forensics) getQcSignerAddresses(quorumCert types.QuorumCert) []string { + var signerList []string + + // The QC signatures are signed by votes special struct VoteForSign + quorumCertSignedHash := types.VoteSigHash(&types.VoteForSign{ + ProposedBlockInfo: quorumCert.ProposedBlockInfo, + GapNumber: quorumCert.GapNumber, + }) + for _, signature := range quorumCert.Signatures { + var signerAddress common.Address + pubkey, err := crypto.Ecrecover(quorumCertSignedHash.Bytes(), signature) + if err != nil { + log.Error("[getQcSignerAddresses] Fail to Ecrecover signer from the quorumCertSignedHash", "quorumCert.GapNumber", quorumCert.GapNumber, "quorumCert.ProposedBlockInfo", quorumCert.ProposedBlockInfo) + } + + copy(signerAddress[:], crypto.Keccak256(pubkey[1:])[12:]) + signerList = append(signerList, signerAddress.Hex()) + } + return signerList +} + +// Check whether the given QCs are on the same chain as the stored committed QCs(f.HighestCommittedQCs) regardless their orders +func (f *Forensics) findAncestorQcThroughRound(chain consensus.ChainReader, highestCommittedQCs []types.QuorumCert, incomingQCandItsParents []types.QuorumCert) (types.QuorumCert, []types.QuorumCert, []types.QuorumCert, error) { + /* + Re-order two sets of QCs by Round number + */ + lowerRoundQCs := highestCommittedQCs + higherRoundQCs := incomingQCandItsParents + if incomingQCandItsParents[0].ProposedBlockInfo.Round < highestCommittedQCs[0].ProposedBlockInfo.Round { + lowerRoundQCs = incomingQCandItsParents + higherRoundQCs = highestCommittedQCs + } + + // Find the ancestorFromIncomingQC1 that matches round number < lowerRoundQCs3 + ancestorQC := higherRoundQCs[0] + for ancestorQC.ProposedBlockInfo.Round >= lowerRoundQCs[NUM_OF_FORENSICS_QC-1].ProposedBlockInfo.Round { + proposedBlock := chain.GetHeaderByHash(ancestorQC.ProposedBlockInfo.Hash) + var decodedExtraField types.ExtraFields_v2 + err := utils.DecodeBytesExtraFields(proposedBlock.Extra, &decodedExtraField) + if err != nil { + log.Error("[findAncestorQcThroughRound] Error while trying to decode extra field", "ProposedBlockInfo.Hash", ancestorQC.ProposedBlockInfo.Hash) + return ancestorQC, lowerRoundQCs, higherRoundQCs, err + } + // Found the ancestor QC + if decodedExtraField.QuorumCert.ProposedBlockInfo.Round < lowerRoundQCs[NUM_OF_FORENSICS_QC-1].ProposedBlockInfo.Round { + return ancestorQC, lowerRoundQCs, higherRoundQCs, nil + } + ancestorQC = *decodedExtraField.QuorumCert + } + return ancestorQC, lowerRoundQCs, higherRoundQCs, errors.New("[findAncestorQcThroughRound] Could not find ancestor QC") +} + +func (f *Forensics) FindAncestorBlockHash(chain consensus.ChainReader, firstBlockInfo *types.BlockInfo, secondBlockInfo *types.BlockInfo) (common.Hash, []string, []string, error) { + // Re-arrange by block number + lowerBlockNumHash := firstBlockInfo.Hash + higherBlockNumberHash := secondBlockInfo.Hash + + var lowerBlockNumToAncestorHashPath []string + var higherBlockToAncestorNumHashPath []string + orderSwapped := false + + blockNumberDifference := big.NewInt(0).Sub(secondBlockInfo.Number, firstBlockInfo.Number).Int64() + if blockNumberDifference < 0 { + lowerBlockNumHash = secondBlockInfo.Hash + higherBlockNumberHash = firstBlockInfo.Hash + blockNumberDifference = -blockNumberDifference // and make it positive + orderSwapped = true + } + lowerBlockNumToAncestorHashPath = append(lowerBlockNumToAncestorHashPath, lowerBlockNumHash.Hex()) + higherBlockToAncestorNumHashPath = append(higherBlockToAncestorNumHashPath, higherBlockNumberHash.Hex()) + + // First, make their block number the same to start with + for i := 0; i < int(blockNumberDifference); i++ { + ph := chain.GetHeaderByHash(higherBlockNumberHash) + if ph == nil { + return common.Hash{}, lowerBlockNumToAncestorHashPath, higherBlockToAncestorNumHashPath, fmt.Errorf("unable to find parent block of hash %v", higherBlockNumberHash) + } + higherBlockNumberHash = ph.ParentHash + higherBlockToAncestorNumHashPath = append(higherBlockToAncestorNumHashPath, ph.ParentHash.Hex()) + } + + // Now, they are on the same starting line, we try find the common ancestor + for lowerBlockNumHash != higherBlockNumberHash { + lowerBlockNumHash = chain.GetHeaderByHash(lowerBlockNumHash).ParentHash + higherBlockNumberHash = chain.GetHeaderByHash(higherBlockNumberHash).ParentHash + // Append the path + lowerBlockNumToAncestorHashPath = append(lowerBlockNumToAncestorHashPath, lowerBlockNumHash.Hex()) + higherBlockToAncestorNumHashPath = append(higherBlockToAncestorNumHashPath, higherBlockNumberHash.Hex()) + } + + // Reverse the list order as it's from ancestor to X block path. + ancestorToLowerBlockNumHashPath := reverse(lowerBlockNumToAncestorHashPath) + ancestorToHigherBlockNumHashPath := reverse(higherBlockToAncestorNumHashPath) + // Swap back the order. We must return in the order that matches what we acceptted in the parameter of firstBlock & secondBlock + if orderSwapped { + return lowerBlockNumHash, ancestorToHigherBlockNumHashPath, ancestorToLowerBlockNumHashPath, nil + } + return lowerBlockNumHash, ancestorToLowerBlockNumHashPath, ancestorToHigherBlockNumHashPath, nil +} + +func generateForensicsId(divergingHash string, qc1 *types.QuorumCert, qc2 *types.QuorumCert) string { + keysList := []string{divergingHash, qc1.ProposedBlockInfo.Hash.Hex(), qc2.ProposedBlockInfo.Hash.Hex()} + return strings.Join(keysList[:], ":") +} + +func reverse(ss []string) []string { + last := len(ss) - 1 + for i := 0; i < len(ss)/2; i++ { + ss[i], ss[last-i] = ss[last-i], ss[i] + } + return ss +} + +func generateVoteEquivocationId(signer common.Address, round1, round2 types.Round) string { + return fmt.Sprintf("%x:%d:%d", signer, round1, round2) +} + +/* +Entry point for processing vote equivocation. +Triggered once handle vote is successfully. +Forensics runs in a separate go routine as its no system critical +Link to the flow diagram: https://hashlabs.atlassian.net/wiki/spaces/HASHLABS/pages/99516417/Vote+Equivocation+detection+specification +*/ +func (f *Forensics) ProcessVoteEquivocation(chain consensus.ChainReader, engine *XDPoS_v2, incomingVote *types.Vote) error { + return nil + log.Debug("Received a vote in forensics", "vote", incomingVote) + // Clone the values to a temporary variable + highestCommittedQCs := f.HighestCommittedQCs + if len(highestCommittedQCs) != NUM_OF_FORENSICS_QC { + log.Error("[ProcessVoteEquivocation] HighestCommittedQCs value not set", "incomingVoteProposedBlockHash", incomingVote.ProposedBlockInfo.Hash, "incomingVoteProposedBlockNumber", incomingVote.ProposedBlockInfo.Number.Uint64(), "incomingVoteProposedBlockRound", incomingVote.ProposedBlockInfo.Round) + return errors.New("HighestCommittedQCs value not set") + } + if incomingVote.ProposedBlockInfo.Round < highestCommittedQCs[NUM_OF_FORENSICS_QC-1].ProposedBlockInfo.Round { + log.Debug("Received a too old vote in forensics", "vote", incomingVote) + return nil + } + // is vote extending committed block + isOnTheChain, err := f.isExtendingFromAncestor(chain, incomingVote.ProposedBlockInfo, highestCommittedQCs[0].ProposedBlockInfo) + if err != nil { + return err + } + if isOnTheChain { + // Passed the checking, nothing suspicious. + log.Debug("[ProcessVoteEquivocation] Passed forensics checking, nothing suspecious need to be reported", "incomingVoteProposedBlockHash", incomingVote.ProposedBlockInfo.Hash, "incomingVoteProposedBlockNumber", incomingVote.ProposedBlockInfo.Number.Uint64(), "incomingVoteProposedBlockRound", incomingVote.ProposedBlockInfo.Round) + return nil + } + // Trigger the safety Alarm if failed + isVoteBlamed, parentQC, err := f.isVoteBlamed(chain, highestCommittedQCs, incomingVote) + if err != nil { + log.Error("[ProcessVoteEquivocation] Error while trying to call isVoteBlamed", "error", err) + return err + } + if isVoteBlamed { + signer, err := GetVoteSignerAddresses(incomingVote) + if err != nil { + log.Error("[ProcessVoteEquivocation] GetVoteSignerAddresses", "error", err) + } + qc := highestCommittedQCs[NUM_OF_FORENSICS_QC-1] + for _, signature := range qc.Signatures { + voteFromQC := &types.Vote{ProposedBlockInfo: qc.ProposedBlockInfo, Signature: signature, GapNumber: qc.GapNumber} + signerFromQC, err := GetVoteSignerAddresses(voteFromQC) + if err != nil { + log.Error("[ProcessVoteEquivocation] GetVoteSignerAddresses", "error", err) + return err + } + if signerFromQC == signer { + f.SendVoteEquivocationProof(incomingVote, voteFromQC, signer) + break + } + } + // if no same-signer vote, nothing to report + } else { + // use the parent QC to do forensics + f.ProcessForensics(chain, engine, *parentQC) + } + + return nil +} + +func (f *Forensics) isExtendingFromAncestor(blockChainReader consensus.ChainReader, currentBlock *types.BlockInfo, ancestorBlock *types.BlockInfo) (bool, error) { + blockNumDiff := int(big.NewInt(0).Sub(currentBlock.Number, ancestorBlock.Number).Int64()) + + nextBlockHash := currentBlock.Hash + for i := 0; i < blockNumDiff; i++ { + parentBlock := blockChainReader.GetHeaderByHash(nextBlockHash) + if parentBlock == nil { + return false, fmt.Errorf("could not find its parent block when checking whether currentBlock %v with hash %v is extending from the ancestorBlock %v", currentBlock.Number, currentBlock.Hash, ancestorBlock.Number) + } else { + nextBlockHash = parentBlock.ParentHash + } + log.Debug("[isExtendingFromAncestor] Found parent block", "CurrentBlockHash", currentBlock.Hash, "ParentHash", nextBlockHash) + } + + if nextBlockHash == ancestorBlock.Hash { + return true, nil + } + return false, nil +} + +func (f *Forensics) isVoteBlamed(chain consensus.ChainReader, highestCommittedQCs []types.QuorumCert, incomingVote *types.Vote) (bool, *types.QuorumCert, error) { + proposedBlock := chain.GetHeaderByHash(incomingVote.ProposedBlockInfo.Hash) + var decodedExtraField types.ExtraFields_v2 + err := utils.DecodeBytesExtraFields(proposedBlock.Extra, &decodedExtraField) + if err != nil { + log.Error("[findAncestorVoteThroughRound] Error while trying to decode extra field", "ProposedBlockInfo.Hash", incomingVote.ProposedBlockInfo.Hash) + return false, nil, err + } + // Found the parent QC, if its round < hcqc3's round, return true + if decodedExtraField.QuorumCert.ProposedBlockInfo.Round < highestCommittedQCs[NUM_OF_FORENSICS_QC-1].ProposedBlockInfo.Round { + return true, decodedExtraField.QuorumCert, nil + } + return false, decodedExtraField.QuorumCert, nil +} + +func (f *Forensics) DetectEquivocationInVotePool(vote *types.Vote, votePool *utils.Pool) { + return + poolKey := vote.PoolKey() + votePoolKeys := votePool.PoolObjKeysList() + signer, err := GetVoteSignerAddresses(vote) + if err != nil { + log.Error("[detectEquivocationInVotePool]", "err", err) + } + + for _, k := range votePoolKeys { + if k == poolKey { + continue + } + keyedRound, err := strconv.ParseInt(strings.Split(k, ":")[0], 10, 64) + if err != nil { + log.Error("[detectEquivocationInVotePool] Error while trying to get keyedRound inside pool", "Error", err) + continue + } + if types.Round(keyedRound) == vote.ProposedBlockInfo.Round { + votes := votePool.GetObjsByKey(k) + for _, v := range votes { + voteTransfered, ok := v.(*types.Vote) + if !ok { + log.Warn("[detectEquivocationInVotePool] obj type is not vote, potential a bug in votePool") + continue + } + signer2, err := GetVoteSignerAddresses(voteTransfered) + if err != nil { + log.Warn("[detectEquivocationInVotePool]", "err", err) + continue + } + if signer == signer2 { + f.SendVoteEquivocationProof(vote, voteTransfered, signer) + } + } + } + } +} + +func (f *Forensics) SendVoteEquivocationProof(vote1, vote2 *types.Vote, signer common.Address) error { + smallerRoundVote := vote1 + largerRoundVote := vote2 + if vote1.ProposedBlockInfo.Round > vote2.ProposedBlockInfo.Round { + smallerRoundVote = vote2 + largerRoundVote = vote1 + } + content, err := json.Marshal(&types.VoteEquivocationContent{ + SmallerRoundVote: smallerRoundVote, + LargerRoundVote: largerRoundVote, + Signer: signer, + }) + if err != nil { + log.Error("[SendVoteEquivocationProof] fail to json stringify forensics content", "err", err) + return err + } + forensicsProof := &types.ForensicProof{ + Id: generateVoteEquivocationId(signer, smallerRoundVote.ProposedBlockInfo.Round, largerRoundVote.ProposedBlockInfo.Round), + ForensicsType: "Vote", + Content: string(content), + } + log.Info("Forensics proof report generated, sending to the stats server", "forensicsProof", forensicsProof) + go f.forensicsFeed.Send(types.ForensicsEvent{ForensicsProof: forensicsProof}) + return nil +} + +func GetVoteSignerAddresses(vote *types.Vote) (common.Address, error) { + // The QC signatures are signed by votes special struct VoteForSign + signHash := types.VoteSigHash(&types.VoteForSign{ + ProposedBlockInfo: vote.ProposedBlockInfo, + GapNumber: vote.GapNumber, + }) + var signerAddress common.Address + pubkey, err := crypto.Ecrecover(signHash.Bytes(), vote.Signature) + if err != nil { + return signerAddress, fmt.Errorf("fail to Ecrecover signer from the vote: %v", vote) + } + copy(signerAddress[:], crypto.Keccak256(pubkey[1:])[12:]) + return signerAddress, nil +} diff --git a/consensus/XDPoS/engines/engine_v2_subnet/forensics_test.go b/consensus/XDPoS/engines/engine_v2_subnet/forensics_test.go new file mode 100644 index 000000000000..26beef48de84 --- /dev/null +++ b/consensus/XDPoS/engines/engine_v2_subnet/forensics_test.go @@ -0,0 +1,140 @@ +package engine_v2_subnet + +import ( + "crypto/ecdsa" + "fmt" + "math/big" + "math/rand" + "os" + "testing" + + "github.com/XinFinOrg/XDPoSChain/accounts" + "github.com/XinFinOrg/XDPoSChain/accounts/keystore" + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/crypto" + "github.com/stretchr/testify/assert" +) + +// Utils to help mocking the signing of signatures +var ( + signer1, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + signer2, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") + signer3, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") +) + +const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + +func SignHashByPK(pk *ecdsa.PrivateKey, itemToSign []byte) []byte { + signer, signFn, err := getSignerAndSignFn(pk) + if err != nil { + panic(err) + } + signedHash, err := signFn(accounts.Account{Address: signer}, itemToSign) + if err != nil { + panic(err) + } + return signedHash +} +func RandStringBytes(n int) string { + b := make([]byte, n) + for i := range b { + b[i] = letterBytes[rand.Intn(len(letterBytes))] + } + return string(b) +} + +func getSignerAndSignFn(pk *ecdsa.PrivateKey) (common.Address, func(account accounts.Account, hash []byte) ([]byte, error), error) { + veryLightScryptN := 2 + veryLightScryptP := 1 + dir, _ := os.MkdirTemp("", fmt.Sprintf("eth-getSignerAndSignFn-test-%v", RandStringBytes(5))) + defer os.RemoveAll(dir) + + ks := keystore.NewKeyStore(dir, veryLightScryptN, veryLightScryptP) + pass := "" // not used but required by API + a1, err := ks.ImportECDSA(pk, pass) + if err != nil { + return common.Address{}, nil, err + } + if err := ks.Unlock(a1, ""); err != nil { + return a1.Address, nil, err + } + return a1.Address, ks.SignHash, nil +} + +func TestFindQCsInSameRound(t *testing.T) { + forensics := &Forensics{} + gapNumber := 450 + + // If ONE in common + var sig []types.Signature + qc1 := &types.QuorumCert{ + ProposedBlockInfo: &types.BlockInfo{ + Hash: common.StringToHash("qc1"), + Round: types.Round(10), + Number: big.NewInt(910), + }, + Signatures: sig, + GapNumber: uint64(gapNumber), + } + + qc2 := &types.QuorumCert{ + ProposedBlockInfo: &types.BlockInfo{ + Hash: common.StringToHash("qc2"), + Round: types.Round(12), + Number: big.NewInt(910), + }, + Signatures: sig, + GapNumber: uint64(gapNumber), + } + + qc3 := &types.QuorumCert{ + ProposedBlockInfo: &types.BlockInfo{ + Hash: common.StringToHash("qc3"), + Round: types.Round(13), + Number: big.NewInt(910), + }, + Signatures: sig, + GapNumber: uint64(gapNumber), + } + + qc4 := &types.QuorumCert{ + ProposedBlockInfo: &types.BlockInfo{ + Hash: common.StringToHash("qc4"), + Round: types.Round(12), + Number: big.NewInt(910), + }, + Signatures: sig, + GapNumber: uint64(gapNumber), + } + + qc5 := &types.QuorumCert{ + ProposedBlockInfo: &types.BlockInfo{ + Hash: common.StringToHash("qc5"), + Round: types.Round(13), + Number: big.NewInt(910), + }, + Signatures: sig, + GapNumber: uint64(gapNumber), + } + + qc6 := &types.QuorumCert{ + ProposedBlockInfo: &types.BlockInfo{ + Hash: common.StringToHash("qc6"), + Round: types.Round(15), + Number: big.NewInt(910), + }, + Signatures: sig, + GapNumber: uint64(gapNumber), + } + + var qcSet1 []types.QuorumCert + var qcSet2 []types.QuorumCert + + found, first, second := forensics.findQCsInSameRound(append(qcSet1, *qc1, *qc2, *qc3), append(qcSet2, *qc4, *qc5, *qc6)) + assert.True(t, found) + assert.Equal(t, *qc2, first) + assert.Equal(t, *qc4, second) +} + +// TODO: Add test for FindAncestorBlockHash diff --git a/consensus/XDPoS/engines/engine_v2_subnet/mining.go b/consensus/XDPoS/engines/engine_v2_subnet/mining.go new file mode 100644 index 000000000000..179a00cc1b38 --- /dev/null +++ b/consensus/XDPoS/engines/engine_v2_subnet/mining.go @@ -0,0 +1,62 @@ +package engine_v2_subnet + +import ( + "errors" + "math/big" + + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/consensus" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" + "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/log" +) + +// Using parent and current round to find the finalised master node list(with penalties applied from last epoch) +func (x *XDPoS_v2) yourturn(chain consensus.ChainReader, round types.Round, parent *types.Header, signer common.Address) (bool, error) { + if round <= x.highestSelfMinedRound { + log.Warn("[yourturn] Already mined on this round", "Round", round, "highestSelfMinedRound", x.highestSelfMinedRound, "ParentHash", parent.Hash().Hex(), "ParentNumber", parent.Number) + return false, utils.ErrAlreadyMined + } + + isEpochSwitch, _, err := x.isEpochSwitchAtRound(round, parent) + if err != nil { + log.Error("[yourturn] check epoch switch at round failed", "Error", err) + return false, err + } + var masterNodes []common.Address + if isEpochSwitch { + masterNodes, _, err = x.calcMasternodes(chain, big.NewInt(0).Add(parent.Number, big.NewInt(1)), parent.Hash(), round) + if err != nil { + log.Error("[yourturn] Cannot calcMasternodes at gap num ", "err", err, "parent number", parent.Number) + return false, err + } + } else { + // this block and parent belong to the same epoch + masterNodes = x.GetMasternodes(chain, parent) + } + + if len(masterNodes) == 0 { + log.Error("[yourturn] Fail to find any master nodes from current block round epoch", "Hash", parent.Hash(), "CurrentRound", round, "Number", parent.Number) + return false, errors.New("masternodes not found") + } + + for i, s := range masterNodes { + log.Debug("[yourturn] Masternode:", "index", i, "address", s.String(), "parentBlockNum", parent.Number) + } + + curIndex := utils.Position(masterNodes, signer) + if curIndex == -1 { + log.Debug("[yourturn] I am not in masternodes list", "Hash", parent.Hash().Hex(), "signer", signer.Hex()) + return false, nil + } + + leaderIndex := uint64(round) % x.config.Epoch % uint64(len(masterNodes)) + x.whosTurn = masterNodes[leaderIndex] + if x.whosTurn != signer { + log.Info("[yourturn] Not my turn", "curIndex", curIndex, "leaderIndex", leaderIndex, "Hash", parent.Hash().Hex(), "whosTurn", x.whosTurn.Hex(), "myaddr", signer.Hex()) + return false, nil + } + + log.Info("[yourturn] Yes, it's my turn based on parent block", "ParentHash", parent.Hash().Hex(), "ParentBlockNumber", parent.Number.Uint64()) + return true, nil +} diff --git a/consensus/XDPoS/engines/engine_v2_subnet/snapshot.go b/consensus/XDPoS/engines/engine_v2_subnet/snapshot.go new file mode 100644 index 000000000000..28d8613cbde3 --- /dev/null +++ b/consensus/XDPoS/engines/engine_v2_subnet/snapshot.go @@ -0,0 +1,105 @@ +package engine_v2_subnet + +import ( + "encoding/json" + + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/consensus" + "github.com/XinFinOrg/XDPoSChain/ethdb" + "github.com/XinFinOrg/XDPoSChain/log" +) + +// Snapshot is the state of the smart contract validator list +// The validator list is used on next epoch candidates nodes +// If we don't have the snapshot, then we have to trace back the gap block smart contract state which is very costly +type SnapshotV2 struct { + Number uint64 `json:"number"` // Block number where the snapshot was created + Hash common.Hash `json:"hash"` // Block hash where the snapshot was created + + // candidates will get assigned on updateM1 + NextEpochCandidates []common.Address `json:"masterNodes"` // Set of authorized candidates nodes at this moment for next epoch +} + +// create new snapshot for next epoch to use +func newSnapshot(number uint64, hash common.Hash, candidates []common.Address) *SnapshotV2 { + snap := &SnapshotV2{ + Number: number, + Hash: hash, + NextEpochCandidates: candidates, + } + return snap +} + +// loadSnapshot loads an existing snapshot from the database. +func loadSnapshot(db ethdb.Database, hash common.Hash) (*SnapshotV2, error) { + blob, err := db.Get(append([]byte("XDPoS-V2-"), hash[:]...)) + if err != nil { + return nil, err + } + snap := new(SnapshotV2) + if err := json.Unmarshal(blob, snap); err != nil { + return nil, err + } + + return snap, nil +} + +// store inserts the SnapshotV2 into the database. +func storeSnapshot(s *SnapshotV2, db ethdb.Database) error { + blob, err := json.Marshal(s) + if err != nil { + return err + } + return db.Put(append([]byte("XDPoS-V2-"), s.Hash[:]...), blob) +} + +// retrieves candidates nodes list in map type +func (s *SnapshotV2) GetMappedCandidates() map[common.Address]struct{} { + ms := make(map[common.Address]struct{}) + for _, n := range s.NextEpochCandidates { + ms[n] = struct{}{} + } + return ms +} + +func (s *SnapshotV2) IsCandidates(address common.Address) bool { + for _, n := range s.NextEpochCandidates { + if n == address { + return true + } + } + return false +} + +// snapshot retrieves the authorization snapshot at a given point in time. +func (x *XDPoS_v2) getSnapshot(chain consensus.ChainReader, number uint64, isGapNumber bool) (*SnapshotV2, error) { + var gapBlockNum uint64 + if isGapNumber { + gapBlockNum = number + } else { + gapBlockNum = number - number%x.config.Epoch - x.config.Gap + //prevent overflow + if number-number%x.config.Epoch < x.config.Gap { + gapBlockNum = 0 + } + } + + gapBlockHash := chain.GetHeaderByNumber(gapBlockNum).Hash() + log.Debug("get snapshot from gap block", "number", gapBlockNum, "hash", gapBlockHash.Hex()) + + // If an in-memory SnapshotV2 was found, use that + if snap, ok := x.snapshots.Get(gapBlockHash); ok && snap != nil { + log.Trace("Loaded snapshot from memory", "number", gapBlockNum, "hash", gapBlockHash) + return snap, nil + } + // If an on-disk checkpoint snapshot can be found, use that + snap, err := loadSnapshot(x.db, gapBlockHash) + if err != nil { + log.Error("Cannot find snapshot from last gap block", "err", err, "number", gapBlockNum, "hash", gapBlockHash) + return nil, err + } + + log.Trace("Loaded snapshot from disk", "number", gapBlockNum, "hash", gapBlockHash) + x.snapshots.Add(snap.Hash, snap) + return snap, nil +} diff --git a/consensus/XDPoS/engines/engine_v2_subnet/snapshot_test.go b/consensus/XDPoS/engines/engine_v2_subnet/snapshot_test.go new file mode 100644 index 000000000000..526e05b381ae --- /dev/null +++ b/consensus/XDPoS/engines/engine_v2_subnet/snapshot_test.go @@ -0,0 +1,42 @@ +package engine_v2_subnet + +import ( + "fmt" + "testing" + + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/core/rawdb" + "github.com/XinFinOrg/XDPoSChain/ethdb/leveldb" +) + +func TestGetMasterNodes(t *testing.T) { + masterNodes := []common.Address{{0x4}, {0x3}, {0x2}, {0x1}} + snap := newSnapshot(1, common.Hash{}, masterNodes) + + for _, address := range masterNodes { + if _, ok := snap.GetMappedCandidates()[address]; !ok { + t.Error("should get master node from map", address.Hex(), snap.GetMappedCandidates()) + return + } + } +} + +func TestStoreLoadSnapshot(t *testing.T) { + snap := newSnapshot(1, common.Hash{0x1}, nil) + dir := t.TempDir() + db, err := leveldb.New(dir, 256, 0, "", false) + if err != nil { + panic(fmt.Sprintf("can't create temporary database: %v", err)) + } + lddb := rawdb.NewDatabase(db) + + err = storeSnapshot(snap, lddb) + if err != nil { + t.Error("store snapshot failed", err) + } + + restoredSnapshot, err := loadSnapshot(lddb, snap.Hash) + if err != nil || restoredSnapshot.Hash != snap.Hash { + t.Error("load snapshot failed", err) + } +} diff --git a/consensus/XDPoS/engines/engine_v2_subnet/syncInfo.go b/consensus/XDPoS/engines/engine_v2_subnet/syncInfo.go new file mode 100644 index 000000000000..57178ccd569b --- /dev/null +++ b/consensus/XDPoS/engines/engine_v2_subnet/syncInfo.go @@ -0,0 +1,184 @@ +package engine_v2_subnet + +import ( + "errors" + "fmt" + "strconv" + "strings" + "sync" + + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/consensus" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" + "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/log" +) + +// Verify syncInfo and trigger process QC or TC if successful +func (x *XDPoS_v2) VerifySyncInfoMessage(chain consensus.ChainReader, syncInfo *types.SyncInfo) (bool, error) { + qc := syncInfo.HighestQuorumCert + tc := syncInfo.HighestTimeoutCert + + if qc == nil { + log.Warn("[VerifySyncInfoMessage] SyncInfo message is missing QC", "highestQC", qc) + return false, nil + } + + if x.highestQuorumCert.ProposedBlockInfo.Round >= qc.ProposedBlockInfo.Round && (tc == nil || x.highestTimeoutCert.Round >= tc.Round) { + log.Debug("[VerifySyncInfoMessage] Local Round is larger or equal than syncinfo round", "highestQCRound", x.highestQuorumCert.ProposedBlockInfo.Round, "highestTCRound", x.highestTimeoutCert.Round, "incomingSyncInfoQCRound", qc.ProposedBlockInfo.Round, "incomingSyncInfoTCRound", tc.Round) + return false, nil + } + + snapshot, err := x.getSnapshot(chain, qc.GapNumber, true) + if err != nil { + log.Error("[VerifySyncInfoMessage] fail to get snapshot for a syncInfo message", "blockNum", qc.ProposedBlockInfo.Number, "blockHash", qc.ProposedBlockInfo.Hash, "error", err) + return false, err + } + + voteSigHash := types.VoteSigHash(&types.VoteForSign{ + ProposedBlockInfo: qc.ProposedBlockInfo, + GapNumber: qc.GapNumber, + }) + + if err := x.verifySignatures(voteSigHash, qc.Signatures, snapshot.NextEpochCandidates); err != nil { + log.Warn("[VerifySyncInfoMessage] SyncInfo message verification failed due to QC", "blockNum", qc.ProposedBlockInfo.Number, "gapNum", qc.GapNumber, "round", qc.ProposedBlockInfo.Round, "error", err) + return false, err + } + + if tc != nil { // tc is optional, when the node is starting up there is no TC at the memory + snapshot, err = x.getSnapshot(chain, tc.GapNumber, true) + if err != nil { + log.Error("[VerifySyncInfoMessage] Fail to get snapshot when verifying TC!", "tcGapNumber", tc.GapNumber) + return false, fmt.Errorf("[VerifySyncInfoMessage] Unable to get snapshot, %s", err) + } + + signedTimeoutObj := types.TimeoutSigHash(&types.TimeoutForSign{ + Round: tc.Round, + GapNumber: tc.GapNumber, + }) + + if err := x.verifySignatures(signedTimeoutObj, tc.Signatures, snapshot.NextEpochCandidates); err != nil { + log.Warn("[VerifySyncInfoMessage] SyncInfo message verification failed due to TC", "gapNum", tc.GapNumber, "round", tc.Round, "error", err) + return false, err + } + } + + return true, nil +} + +func (x *XDPoS_v2) SyncInfoHandler(chain consensus.ChainReader, syncInfo *types.SyncInfo) error { + x.lock.Lock() + defer x.lock.Unlock() + x.syncInfoPool.Add(syncInfo) // Add syncInfo to the pool, in case this is valid syncInfo but chain is not sync to latest height + return x.syncInfoHandler(chain, syncInfo) +} + +func (x *XDPoS_v2) syncInfoHandler(chain consensus.ChainReader, syncInfo *types.SyncInfo) error { + qc := syncInfo.HighestQuorumCert + tc := syncInfo.HighestTimeoutCert + + if x.highestQuorumCert.ProposedBlockInfo.Round >= qc.ProposedBlockInfo.Round && (tc == nil || x.highestTimeoutCert.Round >= tc.Round) { + log.Debug("[syncInfoHandler] Local Round is larger or equal than syncinfo round, skip process message", "highestQCRound", x.highestQuorumCert.ProposedBlockInfo.Round, "highestTCRound", x.highestTimeoutCert.Round, "incomingSyncInfoQCRound", qc.ProposedBlockInfo.Round, "incomingSyncInfoTCRound", tc.Round) + return nil + } + + if err := x.verifyQC(chain, qc, nil); err != nil { + return fmt.Errorf("[syncInfoHandler] Failed to verify QC, err %s", err) + } + if err := x.processQC(chain, qc); err != nil { + return fmt.Errorf("[syncInfoHandler] Failed to process QC, err %s", err) + } + + if tc != nil { + if x.highestTimeoutCert.Round >= tc.Round { + log.Debug("[syncInfoHandler] Round from incoming syncInfo message is equal or smaller then local TC round, skip process message", "highestTCRound", x.highestTimeoutCert.Round, "incomingSyncInfoTCRound", tc.Round) + return nil + } + if err := x.verifyTC(chain, tc); err != nil { + return fmt.Errorf("[syncInfoHandler] Failed to verify TC, err %s", err) + } + + if err := x.processTC(chain, tc); err != nil { + return fmt.Errorf("[syncInfoHandler] Failed to process TC, err %s", err) + } + } + + return nil +} + +func (x *XDPoS_v2) processSyncInfoPool(chain consensus.ChainReader) { + syncInfo := x.syncInfoPool.PoolObjKeysList() + for _, key := range syncInfo { + log.Debug("[processSyncInfoPool] Processing syncInfo message from pool", "key", key) + for _, obj := range x.syncInfoPool.Get()[key] { + if syncInfoObj, ok := obj.(*types.SyncInfo); ok { + if err := x.syncInfoHandler(chain, syncInfoObj); err != nil { + log.Error("[processSyncInfoPool] Failed to handle sync info", "error", err, "currenBlock", chain.CurrentHeader().Number.Uint64(), "x.currentRound", x.currentRound, "key", key) + // must be something wrong with this message, so continue process next object in the pool for same round + continue + } + } else { + log.Error("[processSyncInfoPool] Object in sync info pool is not of type SyncInfo", "objectType", fmt.Sprintf("%T", obj), "key", key) + continue + } + break // We only need to process the first object in the pool ideally + } + } +} + +func (x *XDPoS_v2) verifySignatures(messageHash common.Hash, signatures []types.Signature, candidates []common.Address) error { + + var wg sync.WaitGroup + wg.Add(len(signatures)) + var haveError error + + for _, signature := range signatures { + go func(sig types.Signature) { + defer wg.Done() + verified, _, err := x.verifyMsgSignature(messageHash, sig, candidates) + if err != nil { + log.Error("[verifySignatures] Error while verfying QC message signatures", "error", err) + haveError = errors.New("error while verfying QC message signatures") + return + } + if !verified { + log.Error("[verifySignatures] Signature not verified doing signature verification") + haveError = errors.New("fail to verify QC due to signature mismatch") + return + } + }(signature) + } + wg.Wait() + if haveError != nil { + return haveError + } + return nil +} + +func (x *XDPoS_v2) hygieneSyncInfoPool() { + x.lock.RLock() + round := x.currentRound + x.lock.RUnlock() + syncInfoPoolKeys := x.syncInfoPool.PoolObjKeysList() + + // Extract round number + for _, k := range syncInfoPoolKeys { + // Key format: qcRound:qcGapNum:qcBlockNum:timeoutRound:timeoutGapNum:qcBlockHash + qcRound, qcErr := strconv.ParseInt(strings.Split(k, ":")[0], 10, 64) + tcRound, tcErr := strconv.ParseInt(strings.Split(k, ":")[3], 10, 64) + if qcErr != nil || tcErr != nil { + log.Error("[hygieneSyncInfoPool] Error while trying to get keyedRound inside pool", "Error", qcErr, "tcError", tcErr, "Key", k) + continue + } + lowerBoundRound := int64(round) - utils.PoolHygieneRound + // Clean up any sync info round that is 10 rounds older + if qcRound < lowerBoundRound && (tcRound == 0 || tcRound < lowerBoundRound) { + log.Debug("[hygieneSyncInfoPool] Cleaned sync info pool at round", "Round", qcRound, "currentRound", round, "Key", k) + x.syncInfoPool.ClearByPoolKey(k) + } + } +} + +func (x *XDPoS_v2) ReceivedSyncInfo() map[string]map[common.Hash]utils.PoolObj { + return x.syncInfoPool.Get() +} diff --git a/consensus/XDPoS/engines/engine_v2_subnet/testing_utils.go b/consensus/XDPoS/engines/engine_v2_subnet/testing_utils.go new file mode 100644 index 000000000000..a155fe5b2959 --- /dev/null +++ b/consensus/XDPoS/engines/engine_v2_subnet/testing_utils.go @@ -0,0 +1,88 @@ +package engine_v2_subnet + +import ( + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/consensus" + "github.com/XinFinOrg/XDPoSChain/core/types" +) + +/* + Testing tools +*/ + +func (x *XDPoS_v2) SetNewRoundFaker(blockChainReader consensus.ChainReader, newRound types.Round, resetTimer bool) { + x.lock.Lock() + defer x.lock.Unlock() + // Reset a bunch of things + if resetTimer { + x.timeoutWorker.Reset(blockChainReader, 0, 0) + } + x.currentRound = newRound +} + +// for test only +func (x *XDPoS_v2) ProcessQCFaker(chain consensus.ChainReader, qc *types.QuorumCert) error { + x.lock.Lock() + defer x.lock.Unlock() + return x.processQC(chain, qc) +} + +// Utils for test to check currentRound value +func (x *XDPoS_v2) GetCurrentRoundFaker() types.Round { + x.lock.RLock() + defer x.lock.RUnlock() + return x.currentRound +} + +// Utils for test to get current Pool size +func (x *XDPoS_v2) GetVotePoolSizeFaker(vote *types.Vote) int { + return x.votePool.Size(vote.PoolKey()) +} + +// Utils for test to get Timeout Pool Size +func (x *XDPoS_v2) GetTimeoutPoolSizeFaker(timeout *types.Timeout) int { + return x.timeoutPool.Size(timeout.PoolKey()) +} + +// WARN: This function is designed for testing purpose only! +// Utils for test to check currentRound values +func (x *XDPoS_v2) GetPropertiesFaker() (types.Round, *types.QuorumCert, *types.QuorumCert, *types.TimeoutCert, types.Round, *types.BlockInfo) { + x.lock.RLock() + defer x.lock.RUnlock() + return x.currentRound, x.lockQuorumCert, x.highestQuorumCert, x.highestTimeoutCert, x.highestVotedRound, x.highestCommitBlock +} + +// WARN: This function is designed for testing purpose only! +// Utils for tests to set engine specific values +func (x *XDPoS_v2) SetPropertiesFaker(highestQC *types.QuorumCert, highestTC *types.TimeoutCert) { + x.highestQuorumCert = highestQC + x.highestTimeoutCert = highestTC +} + +func (x *XDPoS_v2) HygieneVotePoolFaker() { + x.hygieneVotePool() +} + +func (x *XDPoS_v2) GetVotePoolKeyListFaker() []string { + return x.votePool.PoolObjKeysList() +} + +func (x *XDPoS_v2) HygieneTimeoutPoolFaker() { + x.hygieneTimeoutPool() +} + +func (x *XDPoS_v2) GetTimeoutPoolKeyListFaker() []string { + return x.timeoutPool.PoolObjKeysList() +} + +// Fake the signer address, the signing function is incompatible +func (x *XDPoS_v2) AuthorizeFaker(signer common.Address) { + x.signLock.Lock() + defer x.signLock.Unlock() + + x.signer = signer +} + +func (x *XDPoS_v2) GetForensicsFaker() *Forensics { + return x.ForensicsProcessor +} diff --git a/consensus/XDPoS/engines/engine_v2_subnet/timeout.go b/consensus/XDPoS/engines/engine_v2_subnet/timeout.go new file mode 100644 index 000000000000..94e3f17fc8f3 --- /dev/null +++ b/consensus/XDPoS/engines/engine_v2_subnet/timeout.go @@ -0,0 +1,358 @@ +package engine_v2_subnet + +import ( + "errors" + "fmt" + "strconv" + "strings" + "sync" + "time" + + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/consensus" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" + "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/log" +) + +func (x *XDPoS_v2) VerifyTimeoutMessage(chain consensus.ChainReader, timeoutMsg *types.Timeout) (bool, error) { + if timeoutMsg.Round < x.currentRound { + log.Debug("[VerifyTimeoutMessage] Disqualified timeout message as the proposed round does not match currentRound", "timeoutHash", timeoutMsg.Hash(), "timeoutRound", timeoutMsg.Round, "currentRound", x.currentRound) + return false, nil + } + snap, err := x.getSnapshot(chain, timeoutMsg.GapNumber, true) + if err != nil || snap == nil { + log.Error("[VerifyTimeoutMessage] Fail to get snapshot when verifying timeout message!", "messageGapNumber", timeoutMsg.GapNumber, "err", err) + return false, err + } + if len(snap.NextEpochCandidates) == 0 { + log.Error("[VerifyTimeoutMessage] cannot find NextEpochCandidates from snapshot", "messageGapNumber", timeoutMsg.GapNumber) + return false, errors.New("empty master node lists from snapshot") + } + + verified, signer, err := x.verifyMsgSignature(types.TimeoutSigHash(&types.TimeoutForSign{ + Round: timeoutMsg.Round, + GapNumber: timeoutMsg.GapNumber, + }), timeoutMsg.Signature, snap.NextEpochCandidates) + + if err != nil { + log.Warn("[VerifyTimeoutMessage] cannot verify timeout signature", "err", err) + return false, err + } + + timeoutMsg.SetSigner(signer) + return verified, nil +} + +/* +Entry point for handling timeout message to process below: +*/ +func (x *XDPoS_v2) TimeoutHandler(blockChainReader consensus.ChainReader, timeout *types.Timeout) error { + x.lock.Lock() + defer x.lock.Unlock() + return x.timeoutHandler(blockChainReader, timeout) +} + +func (x *XDPoS_v2) timeoutHandler(blockChainReader consensus.ChainReader, timeout *types.Timeout) error { + // checkRoundNumber + if timeout.Round != x.currentRound { + return &utils.ErrIncomingMessageRoundNotEqualCurrentRound{ + Type: "timeout", + IncomingRound: timeout.Round, + CurrentRound: x.currentRound, + } + } + // Collect timeout, generate TC + numberOfTimeoutsInPool, pooledTimeouts := x.timeoutPool.Add(timeout) + log.Debug("[timeoutHandler] collect timeout", "number", numberOfTimeoutsInPool) + + epochInfo, err := x.getEpochSwitchInfo(blockChainReader, blockChainReader.CurrentHeader(), blockChainReader.CurrentHeader().Hash()) + if err != nil { + log.Error("[timeoutHandler] Error when getting epoch switch Info", "error", err) + return fmt.Errorf("fail on timeoutHandler due to failure in getting epoch switch info, %s", err) + } + + // Threshold reached + certThreshold := x.config.V2.Config(uint64(timeout.Round)).CertThreshold + isThresholdReached := float64(numberOfTimeoutsInPool) >= float64(epochInfo.MasternodesLen)*certThreshold + if isThresholdReached { + log.Info(fmt.Sprintf("Timeout pool threashold reached: %v, number of items in the pool: %v", isThresholdReached, numberOfTimeoutsInPool)) + err := x.onTimeoutPoolThresholdReached(blockChainReader, pooledTimeouts, timeout, timeout.GapNumber) + if err != nil { + return err + } + } + return nil +} + +/* +Function that will be called by timeoutPool when it reached threshold. +In the engine v2, we will need to: + 1. Genrate TC + 2. processTC() + 3. generateSyncInfo() +*/ +func (x *XDPoS_v2) onTimeoutPoolThresholdReached(blockChainReader consensus.ChainReader, pooledTimeouts map[common.Hash]utils.PoolObj, currentTimeoutMsg utils.PoolObj, gapNumber uint64) error { + signatures := []types.Signature{} + for _, v := range pooledTimeouts { + signatures = append(signatures, v.(*types.Timeout).Signature) + } + // Genrate TC + timeoutCert := &types.TimeoutCert{ + Round: currentTimeoutMsg.(*types.Timeout).Round, + Signatures: signatures, + GapNumber: gapNumber, + } + // Process TC + err := x.processTC(blockChainReader, timeoutCert) + if err != nil { + log.Error("[onTimeoutPoolThresholdReached] Error while processing TC in the Timeout handler after reaching pool threshold", "TcRound", timeoutCert.Round, "NumberOfTcSig", len(timeoutCert.Signatures), "GapNumber", gapNumber, "Error", err) + return err + } + + log.Info("[onTimeoutPoolThresholdReached] Successfully processed the timeout message and produced TC!", "TcRound", timeoutCert.Round, "NumberOfTcSig", len(timeoutCert.Signatures)) + return nil +} + +func (x *XDPoS_v2) getTCEpochInfo(chain consensus.ChainReader, timeoutCert *types.TimeoutCert) (*types.EpochSwitchInfo, error) { + + epochSwitchInfo, err := x.getEpochSwitchInfo(chain, (chain.CurrentHeader()), (chain.CurrentHeader()).Hash()) + if err != nil { + log.Error("[getTCEpochInfo] Error when getting epoch switch info", "error", err) + return nil, fmt.Errorf("fail on getTCEpochInfo due to failure in getting epoch switch info, %s", err) + } + + epochRound := epochSwitchInfo.EpochSwitchBlockInfo.Round + tempTCEpoch := x.config.V2.SwitchEpoch + uint64(epochRound)/x.config.Epoch + + epochBlockInfo := &types.BlockInfo{ + Hash: epochSwitchInfo.EpochSwitchBlockInfo.Hash, + Round: epochRound, + Number: epochSwitchInfo.EpochSwitchBlockInfo.Number, + } + log.Info("[getTCEpochInfo] Init epochInfo", "number", epochBlockInfo.Number, "round", epochRound, "tcRound", timeoutCert.Round, "tcEpoch", tempTCEpoch) + for epochBlockInfo.Round > timeoutCert.Round { + tempTCEpoch-- + epochBlockInfo, err = x.GetBlockByEpochNumber(chain, tempTCEpoch) + if err != nil { + log.Error("[getTCEpochInfo] Error when getting epoch block info by tc round", "error", err) + return nil, fmt.Errorf("fail on getTCEpochInfo due to failure in getting epoch block info tc round, %s", err) + } + log.Debug("[getTCEpochInfo] Loop to get right epochInfo", "number", epochBlockInfo.Number, "round", epochBlockInfo.Round, "tcRound", timeoutCert.Round, "tcEpoch", tempTCEpoch) + } + tcEpoch := tempTCEpoch + log.Info("[getTCEpochInfo] Final TC epochInfo", "number", epochBlockInfo.Number, "round", epochBlockInfo.Round, "tcRound", timeoutCert.Round, "tcEpoch", tcEpoch) + + epochInfo, err := x.getEpochSwitchInfo(chain, nil, epochBlockInfo.Hash) + if err != nil { + log.Error("[getTCEpochInfo] Error when getting epoch switch info", "error", err) + return nil, fmt.Errorf("fail on getTCEpochInfo due to failure in getting epoch switch info, %s", err) + } + return epochInfo, nil +} + +func (x *XDPoS_v2) verifyTC(chain consensus.ChainReader, timeoutCert *types.TimeoutCert) error { + if timeoutCert == nil || timeoutCert.Signatures == nil { + log.Warn("[verifyTC] TC or TC signatures is Nil") + return utils.ErrInvalidTC + } + + snap, err := x.getSnapshot(chain, timeoutCert.GapNumber, true) + if err != nil { + log.Error("[verifyTC] Fail to get snapshot when verifying TC!", "tcGapNumber", timeoutCert.GapNumber) + return fmt.Errorf("[verifyTC] Unable to get snapshot, %s", err) + } + if snap == nil || len(snap.NextEpochCandidates) == 0 { + log.Error("[verifyTC] Something wrong with the snapshot from gapNumber", "messageGapNumber", timeoutCert.GapNumber, "snapshot", snap) + return errors.New("empty master node lists from snapshot") + } + + signatures, duplicates := UniqueSignatures(timeoutCert.Signatures) + if len(duplicates) != 0 { + for _, d := range duplicates { + log.Warn("[verifyTC] duplicated signature in QC", "duplicate", common.Bytes2Hex(d)) + } + } + + epochInfo, err := x.getTCEpochInfo(chain, timeoutCert) + if err != nil { + return err + } + + certThreshold := x.config.V2.Config(uint64(timeoutCert.Round)).CertThreshold + if float64(len(signatures)) < float64(epochInfo.MasternodesLen)*certThreshold { + log.Warn("[verifyTC] Invalid TC Signature is less or empty", "tcRound", timeoutCert.Round, "tcGapNumber", timeoutCert.GapNumber, "tcSignLen", len(timeoutCert.Signatures), "certThreshold", float64(epochInfo.MasternodesLen)*certThreshold) + return utils.ErrInvalidTCSignatures + } + + var wg sync.WaitGroup + wg.Add(len(signatures)) + + var mutex sync.Mutex + var haveError error + + signedTimeoutObj := types.TimeoutSigHash(&types.TimeoutForSign{ + Round: timeoutCert.Round, + GapNumber: timeoutCert.GapNumber, + }) + + for _, signature := range signatures { + go func(sig types.Signature) { + defer wg.Done() + verified, _, err := x.verifyMsgSignature(signedTimeoutObj, sig, snap.NextEpochCandidates) + if err != nil || !verified { + log.Error("[verifyTC] Error or verification failure", "signature", sig, "error", err) + mutex.Lock() // Lock before accessing haveError + if haveError == nil { + if err != nil { + log.Error("[verifyTC] Error while verfying TC message signatures", "tcRound", timeoutCert.Round, "tcGapNumber", timeoutCert.GapNumber, "tcSignLen", len(signatures), "error", err) + haveError = fmt.Errorf("error while verifying TC message signatures, %s", err) + } else { + log.Warn("[verifyTC] Signature not verified doing TC verification", "tcRound", timeoutCert.Round, "tcGapNumber", timeoutCert.GapNumber, "tcSignLen", len(signatures)) + haveError = errors.New("fail to verify TC due to signature mis-match") + } + } + mutex.Unlock() // Unlock after modifying haveError + } + }(signature) + } + wg.Wait() + if haveError != nil { + return haveError + } + return nil +} + +/* +1. Update highestTC +2. Check TC round >= node's currentRound. If yes, call setNewRound +*/ +func (x *XDPoS_v2) processTC(blockChainReader consensus.ChainReader, timeoutCert *types.TimeoutCert) error { + if x.highestTimeoutCert.Round < timeoutCert.Round { + x.highestTimeoutCert = timeoutCert + } + if timeoutCert.Round >= x.currentRound { + x.setNewRound(blockChainReader, timeoutCert.Round+1) + } + return nil +} + +// Generate and send timeout into BFT channel. +/* + 1. timeout.round = currentRound + 2. Sign the signature + 3. send to broadcast channel +*/ +func (x *XDPoS_v2) sendTimeout(chain consensus.ChainReader) error { + // Construct the gapNumber + var gapNumber uint64 + currentBlockHeader := chain.CurrentHeader() + isEpochSwitch, epochNum, err := x.isEpochSwitchAtRound(x.currentRound, currentBlockHeader) + if err != nil { + log.Error("[sendTimeout] Error while checking if the currentBlock is epoch switch", "currentRound", x.currentRound, "currentBlockNum", currentBlockHeader.Number, "currentBlockHash", currentBlockHeader.Hash(), "epochNum", epochNum) + return err + } + + if isEpochSwitch { + // Notice this +1 is because we expect a block whos is the child of currentHeader + currentNumber := currentBlockHeader.Number.Uint64() + 1 + gapNumber = currentNumber - currentNumber%x.config.Epoch - x.config.Gap + // prevent overflow + if currentNumber-currentNumber%x.config.Epoch < x.config.Gap { + gapNumber = 0 + } + log.Debug("[sendTimeout] is epoch switch when sending out timeout message", "currentNumber", currentNumber, "gapNumber", gapNumber) + } else { + epochSwitchInfo, err := x.getEpochSwitchInfo(chain, currentBlockHeader, currentBlockHeader.Hash()) + if err != nil { + log.Error("[sendTimeout] Error when trying to get current epoch switch info for a non-epoch block", "currentRound", x.currentRound, "currentBlockNum", currentBlockHeader.Number, "currentBlockHash", currentBlockHeader.Hash(), "epochNum", epochNum) + return err + } + gapNumber = epochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64() - epochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64()%x.config.Epoch - x.config.Gap + // prevent overflow + if epochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64()-epochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64()%x.config.Epoch < x.config.Gap { + gapNumber = 0 + } + log.Debug("[sendTimeout] non-epoch-switch block found its epoch block and calculated the gapNumber", "epochSwitchInfo.EpochSwitchBlockInfo.Number", epochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64(), "gapNumber", gapNumber) + } + + signedHash, err := x.signSignature(types.TimeoutSigHash(&types.TimeoutForSign{ + Round: x.currentRound, + GapNumber: gapNumber, + })) + if err != nil { + log.Error("[sendTimeout] signSignature when sending out TC", "Error", err, "round", x.currentRound, "gap", gapNumber) + return err + } + timeoutMsg := &types.Timeout{ + Round: x.currentRound, + Signature: signedHash, + GapNumber: gapNumber, + } + + timeoutMsg.SetSigner(x.signer) + log.Warn("[sendTimeout] Timeout message generated, ready to send!", "timeoutMsgRound", timeoutMsg.Round, "timeoutMsgGapNumber", timeoutMsg.GapNumber, "whosTurn", x.whosTurn) + err = x.timeoutHandler(chain, timeoutMsg) + if err != nil { + log.Error("TimeoutHandler error", "TimeoutRound", timeoutMsg.Round, "Error", err) + return err + } + x.broadcastToBftChannel(timeoutMsg) + return nil +} + +/* +Function that will be called by timer when countdown reaches its threshold. +In the engine v2, we would need to broadcast timeout messages to other peers +*/ +func (x *XDPoS_v2) OnCountdownTimeout(time time.Time, chain interface{}) error { + x.lock.Lock() + defer x.lock.Unlock() + + // Check if we are within the master node list + allow := x.allowedToSend(chain.(consensus.ChainReader), chain.(consensus.ChainReader).CurrentHeader(), "timeout") + if !allow { + return nil + } + x.processSyncInfoPool(chain.(consensus.ChainReader)) + + err := x.sendTimeout(chain.(consensus.ChainReader)) + if err != nil { + log.Error("Error while sending out timeout message at time: ", "time", time, "err", err) + return err + } + + x.timeoutCount++ + if x.timeoutCount%x.config.V2.CurrentConfig.TimeoutSyncThreshold == 0 { + syncInfo := x.getSyncInfo() + log.Info("[OnCountdownTimeout] Timeout sync threshold reached, send syncInfo message", "QC round", syncInfo.HighestQuorumCert.ProposedBlockInfo.Round, "QC num", syncInfo.HighestQuorumCert.ProposedBlockInfo.Number, "QC sigs", len(syncInfo.HighestQuorumCert.Signatures), "TC round", syncInfo.HighestTimeoutCert.Round, "TC sigs", len(syncInfo.HighestTimeoutCert.Signatures)) + x.broadcastToBftChannel(syncInfo) + } + + return nil +} + +func (x *XDPoS_v2) hygieneTimeoutPool() { + x.lock.RLock() + currentRound := x.currentRound + x.lock.RUnlock() + timeoutPoolKeys := x.timeoutPool.PoolObjKeysList() + + // Extract round number + for _, k := range timeoutPoolKeys { + keyedRound, err := strconv.ParseInt(strings.Split(k, ":")[0], 10, 64) + if err != nil { + log.Error("[hygieneTimeoutPool] Error while trying to get keyedRound inside pool", "Error", err) + continue + } + // Clean up any timeouts round that is 10 rounds older + if keyedRound < int64(currentRound)-utils.PoolHygieneRound { + log.Debug("[hygieneTimeoutPool] Cleaned timeout pool at round", "Round", keyedRound, "CurrentRound", currentRound, "Key", k) + x.timeoutPool.ClearByPoolKey(k) + } + } +} + +func (x *XDPoS_v2) ReceivedTimeouts() map[string]map[common.Hash]utils.PoolObj { + return x.timeoutPool.Get() +} diff --git a/consensus/XDPoS/engines/engine_v2_subnet/utils.go b/consensus/XDPoS/engines/engine_v2_subnet/utils.go new file mode 100644 index 000000000000..71bba1885308 --- /dev/null +++ b/consensus/XDPoS/engines/engine_v2_subnet/utils.go @@ -0,0 +1,348 @@ +package engine_v2_subnet + +import ( + "errors" + "fmt" + "math/big" + + "github.com/XinFinOrg/XDPoSChain/accounts" + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/consensus" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" + "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/crypto" + "github.com/XinFinOrg/XDPoSChain/log" + "github.com/XinFinOrg/XDPoSChain/rlp" + "golang.org/x/crypto/sha3" +) + +func sigHash(header *types.Header) (hash common.Hash) { + hasher := sha3.NewLegacyKeccak256() + + enc := []interface{}{ + header.ParentHash, + header.UncleHash, + header.Coinbase, + header.Root, + header.TxHash, + header.ReceiptHash, + header.Bloom, + header.Difficulty, + header.Number, + header.GasLimit, + header.GasUsed, + header.Time, + header.Extra, + header.MixDigest, + header.Nonce, + header.Validators, + header.Penalties, + } + if header.BaseFee != nil { + enc = append(enc, header.BaseFee) + } + rlp.Encode(hasher, enc) + hasher.Sum(hash[:0]) + return hash +} + +func ecrecover(header *types.Header, sigcache *utils.SigLRU) (common.Address, error) { + // If the signature's already cached, return that + hash := header.Hash() + if address, known := sigcache.Get(hash); known { + return address, nil + } + + // Recover the public key and the Ethereum address + pubkey, err := crypto.Ecrecover(sigHash(header).Bytes(), header.Validator) + if err != nil { + return common.Address{}, err + } + var signer common.Address + copy(signer[:], crypto.Keccak256(pubkey[1:])[12:]) + + sigcache.Add(hash, signer) + return signer, nil + +} + +// Get masternodes address from checkpoint Header. Only used for v1 last block +func decodeMasternodesFromHeaderExtra(checkpointHeader *types.Header) []common.Address { + masternodes := make([]common.Address, (len(checkpointHeader.Extra)-utils.ExtraVanity-utils.ExtraSeal)/common.AddressLength) + for i := 0; i < len(masternodes); i++ { + copy(masternodes[i][:], checkpointHeader.Extra[utils.ExtraVanity+i*common.AddressLength:]) + } + return masternodes +} + +func UniqueSignatures(signatureSlice []types.Signature) ([]types.Signature, []types.Signature) { + keys := make(map[string]bool) + list := []types.Signature{} + duplicates := []types.Signature{} + for _, signature := range signatureSlice { + hexOfSig := common.Bytes2Hex(signature) + if _, value := keys[hexOfSig]; !value { + keys[hexOfSig] = true + list = append(list, signature) + } else { + duplicates = append(duplicates, signature) + } + } + return list, duplicates +} + +func (x *XDPoS_v2) signSignature(signingHash common.Hash) (types.Signature, error) { + // Don't hold the signFn for the whole signing operation + x.signLock.RLock() + signer, signFn := x.signer, x.signFn + x.signLock.RUnlock() + + signedHash, err := signFn(accounts.Account{Address: signer}, signingHash.Bytes()) + if err != nil { + return nil, fmt.Errorf("error %v while signing hash", err) + } + return signedHash, nil +} + +func (x *XDPoS_v2) verifyMsgSignature(signedHashToBeVerified common.Hash, signature types.Signature, masternodes []common.Address) (bool, common.Address, error) { + var signerAddress common.Address + if len(masternodes) == 0 { + return false, signerAddress, errors.New("empty masternode list detected when verifying message signatures") + } + // Recover the public key and the Ethereum address + pubkey, err := crypto.Ecrecover(signedHashToBeVerified.Bytes(), signature) + if err != nil { + return false, signerAddress, fmt.Errorf("error while verifying message: %v", err) + } + + copy(signerAddress[:], crypto.Keccak256(pubkey[1:])[12:]) + for _, mn := range masternodes { + if mn == signerAddress { + return true, signerAddress, nil + } + } + + log.Warn("[verifyMsgSignature] signer is not part of masternode list", "signer", signerAddress, "masternodes", masternodes) + return false, signerAddress, nil +} + +func (x *XDPoS_v2) getExtraFields(header *types.Header) (*types.QuorumCert, types.Round, []common.Address, error) { + + var masternodes []common.Address + + // last v1 block + if header.Number.Cmp(x.config.V2.SwitchBlock) == 0 { + masternodes = decodeMasternodesFromHeaderExtra(header) + return nil, types.Round(0), masternodes, nil + } + + // v2 block + masternodes = x.GetMasternodesFromEpochSwitchHeader(header) + var decodedExtraField types.ExtraFields_v2 + err := utils.DecodeBytesExtraFields(header.Extra, &decodedExtraField) + if err != nil { + log.Error("[getExtraFields] error on decode extra fields", "err", err, "extra", header.Extra) + return nil, types.Round(0), masternodes, err + } + return decodedExtraField.QuorumCert, decodedExtraField.Round, masternodes, nil +} + +func (x *XDPoS_v2) GetRoundNumber(header *types.Header) (types.Round, error) { + // If not v2 yet, return 0 + if header.Number.Cmp(x.config.V2.SwitchBlock) <= 0 { + return types.Round(0), nil + } else { + var decodedExtraField types.ExtraFields_v2 + err := utils.DecodeBytesExtraFields(header.Extra, &decodedExtraField) + if err != nil { + return types.Round(0), err + } + return decodedExtraField.Round, nil + } +} + +func (x *XDPoS_v2) GetSignersFromSnapshot(chain consensus.ChainReader, header *types.Header) ([]common.Address, error) { + snap, err := x.getSnapshot(chain, header.Number.Uint64(), false) + if err != nil { + return nil, err + } + return snap.NextEpochCandidates, err +} + +func (x *XDPoS_v2) CalculateMissingRounds(chain consensus.ChainReader, header *types.Header) (*utils.PublicApiMissedRoundsMetadata, error) { + var missedRounds []utils.MissedRoundInfo + switchInfo, err := x.getEpochSwitchInfo(chain, header, header.Hash()) + if err != nil { + return nil, err + } + masternodes := switchInfo.Masternodes + + // Loop through from the epoch switch block to the current "header" block + nextHeader := header + for nextHeader.Number.Cmp(switchInfo.EpochSwitchBlockInfo.Number) > 0 { + parentHeader := chain.GetHeaderByHash(nextHeader.ParentHash) + parentRound, err := x.GetRoundNumber(parentHeader) + if err != nil { + return nil, err + } + currRound, err := x.GetRoundNumber(nextHeader) + if err != nil { + return nil, err + } + // This indicates that an increment in the round number is missing during the block production process. + if parentRound+1 != currRound { + // We need to iterate from the parentRound to the currRound to determine which miner did not perform mining. + for i := parentRound + 1; i < currRound; i++ { + leaderIndex := uint64(i) % x.config.Epoch % uint64(len(masternodes)) + whosTurn := masternodes[leaderIndex] + missedRounds = append( + missedRounds, + utils.MissedRoundInfo{ + Round: i, + Miner: whosTurn, + CurrentBlockHash: nextHeader.Hash(), + CurrentBlockNum: nextHeader.Number, + ParentBlockHash: parentHeader.Hash(), + ParentBlockNum: parentHeader.Number, + }, + ) + } + } + // Assign the pointer to the next one + nextHeader = parentHeader + } + missedRoundsMetadata := &utils.PublicApiMissedRoundsMetadata{ + EpochRound: switchInfo.EpochSwitchBlockInfo.Round, + EpochBlockNumber: switchInfo.EpochSwitchBlockInfo.Number, + MissedRounds: missedRounds, + } + + return missedRoundsMetadata, nil +} + +func (x *XDPoS_v2) getBlockByEpochNumberInCache(chain consensus.ChainReader, estRound types.Round) *types.BlockInfo { + epochSwitchInCache := make([]*types.BlockInfo, 0) + for r := estRound; r < estRound+types.Round(x.config.Epoch); r++ { + blockInfo, ok := x.round2epochBlockInfo.Get(r) + if ok && blockInfo != nil { + epochSwitchInCache = append(epochSwitchInCache, blockInfo) + } + } + if len(epochSwitchInCache) == 1 { + return epochSwitchInCache[0] + } else if len(epochSwitchInCache) == 0 { + return nil + } + // when multiple cache hits, need to find the one in main chain + for _, blockInfo := range epochSwitchInCache { + header := chain.GetHeaderByNumber(blockInfo.Number.Uint64()) + if header == nil { + continue + } + if header.Hash() == blockInfo.Hash { + return blockInfo + } + } + return nil +} + +func (x *XDPoS_v2) binarySearchBlockByEpochNumber(chain consensus.ChainReader, targetEpochNum uint64, start, end uint64) (*types.BlockInfo, *types.Header, error) { + // `end` must be larger than the target and `start` could be the target + for start < end { + header := chain.GetHeaderByNumber((start + end) / 2) + if header == nil { + return nil, nil, errors.New("header nil in binary search") + } + isEpochSwitch, epochNum, err := x.IsEpochSwitch(header) + if err != nil { + return nil, nil, err + } + if epochNum == targetEpochNum { + _, round, _, err := x.getExtraFields(header) + if err != nil { + return nil, nil, err + } + if isEpochSwitch { + return &types.BlockInfo{ + Hash: header.Hash(), + Round: round, + Number: header.Number, + }, header, nil + } else { + end = header.Number.Uint64() + // trick to shorten the search + estStart := uint64(0) + // if statement to avoid negative underflow + roundModEpoch := uint64(round) % x.config.Epoch + if end >= roundModEpoch { + estStart = end - roundModEpoch + } + + if start < estStart { + start = estStart + } + } + } else if epochNum > targetEpochNum { + end = header.Number.Uint64() + } else if epochNum < targetEpochNum { + // if start keeps the same, means no result and the search is over + nextStart := header.Number.Uint64() + if nextStart == start { + break + } + start = nextStart + } + } + return nil, nil, errors.New("no epoch switch header in binary search (all rounds in this epoch are missed, which is very rare)") +} + +func (x *XDPoS_v2) GetBlockByEpochNumber(chain consensus.ChainReader, targetEpochNum uint64) (*types.BlockInfo, error) { + currentHeader := chain.CurrentHeader() + epochSwitchInfo, err := x.getEpochSwitchInfo(chain, currentHeader, currentHeader.Hash()) + if err != nil { + return nil, err + } + epochNum := x.config.V2.SwitchEpoch + uint64(epochSwitchInfo.EpochSwitchBlockInfo.Round)/x.config.Epoch + // if current epoch is this epoch, we early return the result + if targetEpochNum == epochNum { + return epochSwitchInfo.EpochSwitchBlockInfo, nil + } + if targetEpochNum > epochNum { + return nil, errors.New("input epoch number > current epoch number") + } + if targetEpochNum < x.config.V2.SwitchEpoch { + return nil, errors.New("input epoch number < v2 begin epoch number") + } + // the block's round should be in [estRound,estRound+Epoch-1] + estRound := types.Round((targetEpochNum - x.config.V2.SwitchEpoch) * x.config.Epoch) + // check the round2epochBlockInfo cache + blockInfo := x.getBlockByEpochNumberInCache(chain, estRound) + if blockInfo != nil { + return blockInfo, nil + } + // if cache miss, we do search + epoch := big.NewInt(int64(x.config.Epoch)) + estblockNumDiff := new(big.Int).Mul(epoch, big.NewInt(int64(epochNum-targetEpochNum))) + estBlockNum := new(big.Int).Sub(epochSwitchInfo.EpochSwitchBlockInfo.Number, estblockNumDiff) + if estBlockNum.Cmp(x.config.V2.SwitchBlock) == -1 { + estBlockNum.Set(x.config.V2.SwitchBlock) + } + // if the targrt is close, we search brute-forcily + closeEpochNum := uint64(2) + if closeEpochNum >= epochNum-targetEpochNum { + estBlockHeader := chain.GetHeaderByNumber(estBlockNum.Uint64()) + epochSwitchInfos, err := x.GetEpochSwitchInfoBetween(chain, estBlockHeader, currentHeader) + if err != nil { + return nil, err + } + for _, info := range epochSwitchInfos { + epochNum := x.config.V2.SwitchEpoch + uint64(info.EpochSwitchBlockInfo.Round)/x.config.Epoch + if epochNum == targetEpochNum { + return info.EpochSwitchBlockInfo, nil + } + } + } + // else, we use binary search + blockInfo, _, err = x.binarySearchBlockByEpochNumber(chain, targetEpochNum, estBlockNum.Uint64(), epochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64()) + return blockInfo, err +} diff --git a/consensus/XDPoS/engines/engine_v2_subnet/verifyHeader.go b/consensus/XDPoS/engines/engine_v2_subnet/verifyHeader.go new file mode 100644 index 000000000000..e75bae8bec75 --- /dev/null +++ b/consensus/XDPoS/engines/engine_v2_subnet/verifyHeader.go @@ -0,0 +1,185 @@ +package engine_v2_subnet + +import ( + "bytes" + "math/big" + "time" + + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/common/hexutil" + "github.com/XinFinOrg/XDPoSChain/consensus" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" + "github.com/XinFinOrg/XDPoSChain/consensus/misc/eip1559" + "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/log" +) + +// Verify individual header +func (x *XDPoS_v2) verifyHeader(chain consensus.ChainReader, header *types.Header, parents []*types.Header, fullVerify bool) error { + if !x.isInitilised { + if err := x.initial(chain, header); err != nil { + return err + } + } + + _, check := x.verifiedHeaders.Get(header.Hash()) + if check { + return nil + } + + if header.Number == nil { + return utils.ErrUnknownBlock + } + + if len(header.Validator) == 0 { + // This should never happen, if it does, then it means the peer is sending us invalid data. + return consensus.ErrNoValidatorSignatureV2 + } + + if fullVerify { + // Don't waste time checking blocks from the future + if header.Time > uint64(time.Now().Unix()) { + return consensus.ErrFutureBlock + } + } + + // Ensure that the block's timestamp isn't too close to it's parent + var parent *types.Header + number := header.Number.Uint64() + + if len(parents) > 0 { + parent = parents[len(parents)-1] + } else { + parent = chain.GetHeader(header.ParentHash, number-1) + } + if parent == nil || parent.Number.Uint64() != number-1 || parent.Hash() != header.ParentHash { + return consensus.ErrUnknownAncestor + } + + // Verify this is truly a v2 block first + quorumCert, round, _, err := x.getExtraFields(header) + if err != nil { + log.Warn("[verifyHeader] decode extra field error", "err", err) + return utils.ErrInvalidV2Extra + } + + minePeriod := uint64(x.config.V2.Config(uint64(round)).MinePeriod) + if parent.Number.Uint64() > x.config.V2.SwitchBlock.Uint64() && parent.Time+minePeriod > header.Time { + log.Warn("[verifyHeader] Fail to verify header due to invalid timestamp", "ParentTime", parent.Time, "MinePeriod", minePeriod, "HeaderTime", header.Time, "Hash", header.Hash().Hex()) + return utils.ErrInvalidTimestamp + } + + if round <= quorumCert.ProposedBlockInfo.Round { + return utils.ErrRoundInvalid + } + + err = x.verifyQC(chain, quorumCert, parent) + if err != nil { + log.Warn("[verifyHeader] fail to verify QC", "QCNumber", quorumCert.ProposedBlockInfo.Number, "QCsigLength", len(quorumCert.Signatures)) + return err + } + // Nonces must be 0x00..0 or 0xff..f, zeroes enforced on checkpoints + if !bytes.Equal(header.Nonce[:], utils.NonceAuthVote) && !bytes.Equal(header.Nonce[:], utils.NonceDropVote) { + return utils.ErrInvalidVote + } + // Ensure that the mix digest is zero as we don't have fork protection currently + if header.MixDigest != (common.Hash{}) { + return utils.ErrInvalidMixDigest + } + // Ensure that the block doesn't contain any uncles which are meaningless in XDPoS_v1 + if header.UncleHash != utils.UncleHash { + return utils.ErrInvalidUncleHash + } + // Verify the header's EIP-1559 attributes. + if err := eip1559.VerifyEip1559Header(chain.Config(), header); err != nil { + return err + } + if header.Difficulty.Cmp(big.NewInt(1)) != 0 { + return utils.ErrInvalidDifficulty + } + + var masterNodes []common.Address + isEpochSwitch, _, err := x.IsEpochSwitch(header) // Verify v2 block that is on the epoch switch + if err != nil { + log.Error("[verifyHeader] error when checking if header is epoch switch header", "Hash", header.Hash(), "Number", header.Number, "Error", err) + return err + } + if isEpochSwitch { + if !bytes.Equal(header.Nonce[:], utils.NonceDropVote) { + return utils.ErrInvalidCheckpointVote + } + if len(header.Validators) == 0 { + return utils.ErrEmptyEpochSwitchValidators + } + if len(header.Validators)%common.AddressLength != 0 { + return utils.ErrInvalidCheckpointSigners + } + + localMasterNodes, localPenalties, err := x.calcMasternodes(chain, header.Number, header.ParentHash, round) + masterNodes = localMasterNodes + if err != nil { + log.Error("[verifyHeader] Fail to calculate master nodes list with penalty", "Number", header.Number, "Hash", header.Hash()) + return err + } + + validatorsAddress := common.ExtractAddressFromBytes(header.Validators) + if !utils.CompareSignersLists(localMasterNodes, validatorsAddress) { + for i, addr := range localMasterNodes { + log.Warn("[verifyHeader] localMasterNodes", "i", i, "addr", addr.Hex()) + } + for i, addr := range validatorsAddress { + log.Warn("[verifyHeader] validatorsAddress", "i", i, "addr", addr.Hex()) + } + return utils.ErrValidatorsNotLegit + } + + penaltiesAddress := common.ExtractAddressFromBytes(header.Penalties) + if !utils.CompareSignersLists(localPenalties, penaltiesAddress) { + for i, addr := range localPenalties { + log.Warn("[verifyHeader] localPenalties", "i", i, "addr", addr.Hex()) + } + for i, addr := range penaltiesAddress { + log.Warn("[verifyHeader] penaltiesAddress", "i", i, "addr", addr.Hex()) + } + return utils.ErrPenaltiesNotLegit + } + + } else { + if len(header.Validators) != 0 { + log.Warn("[verifyHeader] Validators shall not have values in non-epochSwitch block", "Hash", header.Hash(), "Number", header.Number, "header.Validators", header.Validators) + return utils.ErrInvalidFieldInNonEpochSwitch + } + if len(header.Penalties) != 0 { + log.Warn("[verifyHeader] Penalties shall not have values in non-epochSwitch block", "Hash", header.Hash(), "Number", header.Number, "header.Penalties", header.Penalties) + return utils.ErrInvalidFieldInNonEpochSwitch + } + masterNodes = x.GetMasternodes(chain, header) + } + + verified, validatorAddress, err := x.verifyMsgSignature(sigHash(header), header.Validator, masterNodes) + if err != nil { + for index, mn := range masterNodes { + log.Error("[verifyHeader] masternode list during validator verification", "Masternode Address", mn.Hex(), "index", index) + } + log.Error("[verifyHeader] Error while verifying header validator signature", "BlockNumber", header.Number, "Hash", header.Hash().Hex(), "validator in hex", hexutil.Encode(header.Validator)) + return err + } + if !verified { + log.Warn("[verifyHeader] Fail to verify the block validator as the validator address not within the masternode list", "BlockNumber", header.Number, "Hash", header.Hash().Hex(), "validatorAddress", validatorAddress.Hex()) + return utils.ErrValidatorNotWithinMasternodes + } + if validatorAddress != header.Coinbase { + log.Warn("[verifyHeader] Header validator and coinbase address not match", "BlockNumber", header.Number, "Hash", header.Hash().Hex(), "validatorAddress", validatorAddress.Hex(), "coinbase", header.Coinbase.Hex()) + return utils.ErrCoinbaseAndValidatorMismatch + } + // Check the proposer is the leader + curIndex := utils.Position(masterNodes, validatorAddress) + leaderIndex := uint64(round) % x.config.Epoch % uint64(len(masterNodes)) + if masterNodes[leaderIndex] != validatorAddress { + log.Warn("[verifyHeader] Invalid blocker proposer, not its turn", "curIndex", curIndex, "leaderIndex", leaderIndex, "Hash", header.Hash().Hex(), "masterNodes[leaderIndex]", masterNodes[leaderIndex], "validatorAddress", validatorAddress) + return utils.ErrNotItsTurn + } + + x.verifiedHeaders.Add(header.Hash(), struct{}{}) + return nil +} diff --git a/consensus/XDPoS/engines/engine_v2_subnet/vote.go b/consensus/XDPoS/engines/engine_v2_subnet/vote.go new file mode 100644 index 000000000000..01a548de2eae --- /dev/null +++ b/consensus/XDPoS/engines/engine_v2_subnet/vote.go @@ -0,0 +1,332 @@ +package engine_v2_subnet + +import ( + "errors" + "fmt" + "math/big" + "strconv" + "strings" + "sync" + "time" + + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/consensus" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" + "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/log" +) + +func (x *XDPoS_v2) VerifyVoteMessage(chain consensus.ChainReader, vote *types.Vote) (bool, error) { + if vote.ProposedBlockInfo.Round < x.currentRound { + log.Debug("[VerifyVoteMessage] Disqualified vote message as the proposed round does not match currentRound", "voteHash", vote.Hash(), "voteProposedBlockInfoRound", vote.ProposedBlockInfo.Round, "currentRound", x.currentRound) + return false, nil + } + + snapshot, err := x.getSnapshot(chain, vote.GapNumber, true) + if err != nil { + log.Error("[VerifyVoteMessage] fail to get snapshot for a vote message", "blockNum", vote.ProposedBlockInfo.Number, "blockHash", vote.ProposedBlockInfo.Hash, "voteHash", vote.Hash(), "error", err.Error()) + return false, err + } + verified, signer, err := x.verifyMsgSignature(types.VoteSigHash(&types.VoteForSign{ + ProposedBlockInfo: vote.ProposedBlockInfo, + GapNumber: vote.GapNumber, + }), vote.Signature, snapshot.NextEpochCandidates) + if err != nil { + for i, mn := range snapshot.NextEpochCandidates { + log.Warn("[VerifyVoteMessage] Master node list item", "index", i, "Master node", mn.Hex()) + } + log.Warn("[VerifyVoteMessage] Error while verifying vote message", "votedBlockNum", vote.ProposedBlockInfo.Number.Uint64(), "votedBlockHash", vote.ProposedBlockInfo.Hash.Hex(), "voteHash", vote.Hash(), "error", err.Error()) + return false, err + } + vote.SetSigner(signer) + + return verified, nil +} + +// Consensus entry point for processing vote message to produce QC +func (x *XDPoS_v2) VoteHandler(chain consensus.ChainReader, voteMsg *types.Vote) error { + x.lock.Lock() + defer x.lock.Unlock() + return x.voteHandler(chain, voteMsg) +} + +// Once Hot stuff voting rule has verified, this node can then send vote +func (x *XDPoS_v2) sendVote(chainReader consensus.ChainReader, blockInfo *types.BlockInfo) error { + // First step: Update the highest Voted round + // Second step: Generate the signature by using node's private key(The signature is the blockInfo signature) + // Third step: Construct the vote struct with the above signature & blockinfo struct + // Forth step: Send the vote to broadcast channel + + epochSwitchInfo, err := x.getEpochSwitchInfo(chainReader, nil, blockInfo.Hash) + if err != nil { + log.Error("getEpochSwitchInfo when sending out Vote", "BlockInfoHash", blockInfo.Hash, "Error", err) + return err + } + epochSwitchNumber := epochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64() + gapNumber := epochSwitchNumber - epochSwitchNumber%x.config.Epoch - x.config.Gap + // prevent overflow + if epochSwitchNumber-epochSwitchNumber%x.config.Epoch < x.config.Gap { + gapNumber = 0 + } + signedHash, err := x.signSignature(types.VoteSigHash(&types.VoteForSign{ + ProposedBlockInfo: blockInfo, + GapNumber: gapNumber, + })) + if err != nil { + log.Error("signSignature when sending out Vote", "BlockInfoHash", blockInfo.Hash, "Error", err) + return err + } + + x.highestVotedRound = x.currentRound + voteMsg := &types.Vote{ + ProposedBlockInfo: blockInfo, + Signature: signedHash, + GapNumber: gapNumber, + } + + err = x.voteHandler(chainReader, voteMsg) + if err != nil { + log.Error("sendVote error", "BlockInfoHash", blockInfo.Hash, "Error", err) + return err + } + x.broadcastToBftChannel(voteMsg) + return nil +} + +func (x *XDPoS_v2) voteHandler(chain consensus.ChainReader, voteMsg *types.Vote) error { + // checkRoundNumber + if (voteMsg.ProposedBlockInfo.Round != x.currentRound) && (voteMsg.ProposedBlockInfo.Round != x.currentRound+1) { + return &utils.ErrIncomingMessageRoundTooFarFromCurrentRound{ + Type: "vote", + IncomingRound: voteMsg.ProposedBlockInfo.Round, + CurrentRound: x.currentRound, + } + } + + if x.votePoolCollectionTime.IsZero() { + log.Info("[voteHandler] set vote pool time", "round", x.currentRound) + x.votePoolCollectionTime = time.Now() + } + + // Collect vote + numberOfVotesInPool, pooledVotes := x.votePool.Add(voteMsg) + log.Trace("[voteHandler] New vote", "signer", voteMsg.GetSigner().Hex(), "proposedBlockInfoRound", voteMsg.ProposedBlockInfo.Round, "proposedBlockInfoNumber", voteMsg.ProposedBlockInfo.Number.Uint64(), "proposedBlockInfoHash", voteMsg.ProposedBlockInfo.Hash.Hex()) + log.Debug("[voteHandler] collect votes", "number", numberOfVotesInPool) + + go x.ForensicsProcessor.DetectEquivocationInVotePool(voteMsg, x.votePool) + go x.ForensicsProcessor.ProcessVoteEquivocation(chain, x, voteMsg) + + epochInfo, err := x.getEpochSwitchInfo(chain, nil, voteMsg.ProposedBlockInfo.Hash) + if err != nil { + return &utils.ErrIncomingMessageBlockNotFound{ + Type: "vote", + IncomingBlockHash: voteMsg.ProposedBlockInfo.Hash, + IncomingBlockNumber: voteMsg.ProposedBlockInfo.Number, + Err: err, + } + } + + certThreshold := x.config.V2.Config(uint64(voteMsg.ProposedBlockInfo.Round)).CertThreshold + thresholdReached := float64(numberOfVotesInPool) >= float64(epochInfo.MasternodesLen)*certThreshold + if thresholdReached { + log.Info(fmt.Sprintf("[voteHandler] Vote pool threashold reached: %v, number of items in the pool: %v", thresholdReached, numberOfVotesInPool)) + + // Check if the block already exist, otherwise we try luck with the next vote + proposedBlockHeader := chain.GetHeaderByHash(voteMsg.ProposedBlockInfo.Hash) + if proposedBlockHeader == nil { + log.Info("[voteHandler] The proposed block from vote message does not exist yet, wait for the next vote to try again", "blockNum", voteMsg.ProposedBlockInfo.Number, "Hash", voteMsg.ProposedBlockInfo.Hash, "Round", voteMsg.ProposedBlockInfo.Round) + return nil + } + + err := x.VerifyBlockInfo(chain, voteMsg.ProposedBlockInfo, nil) + if err != nil { + return err + } + + x.verifyVotes(chain, pooledVotes, proposedBlockHeader) + + err = x.onVotePoolThresholdReached(chain, pooledVotes, voteMsg, proposedBlockHeader) + if err != nil { + return err + } + elapsed := time.Since(x.votePoolCollectionTime) + log.Info("[voteHandler] time cost from receive first vote under QC create", "elapsed", elapsed) + x.votePoolCollectionTime = time.Time{} + } + + return nil +} + +func (x *XDPoS_v2) verifyVotes(chain consensus.ChainReader, votes map[common.Hash]utils.PoolObj, header *types.Header) { + masternodes := x.GetMasternodes(chain, header) + start := time.Now() + emptySigner := common.Address{} + // Filter out non-Master nodes signatures + var wg sync.WaitGroup + wg.Add(len(votes)) + for h, vote := range votes { + go func(hash common.Hash, v *types.Vote) { + defer wg.Done() + signerAddress := v.GetSigner() + if signerAddress != emptySigner { + // verify that signer belongs to the final masternodes, we have not do so in previous steps + if len(masternodes) == 0 { + log.Error("[verifyVotes] empty masternode list detected when verifying message signatures") + } + for _, mn := range masternodes { + if mn == signerAddress { + return + } + } + // if signer does not belong to final masternodes, we remove the signer + v.SetSigner(emptySigner) + log.Debug("[verifyVotes] find a vote does not belong to final masternodes", "signer", signerAddress) + return + } + signedVote := types.VoteSigHash(&types.VoteForSign{ + ProposedBlockInfo: v.ProposedBlockInfo, + GapNumber: v.GapNumber, + }) + verified, masterNode, err := x.verifyMsgSignature(signedVote, v.Signature, masternodes) + if err != nil { + log.Warn("[verifyVotes] error while verifying vote signature", "error", err.Error()) + return + } + + if !verified { + log.Warn("[verifyVotes] non-verified vote signature", "verified", verified) + return + } + v.SetSigner(masterNode) + }(h, vote.(*types.Vote)) + } + wg.Wait() + elapsed := time.Since(start) + log.Debug("[verifyVotes] verify message signatures of vote pool took", "elapsed", elapsed) +} + +/* +Function that will be called by votePool when it reached threshold. +In the engine v2, we will need to generate and process QC +*/ +func (x *XDPoS_v2) onVotePoolThresholdReached(chain consensus.ChainReader, pooledVotes map[common.Hash]utils.PoolObj, currentVoteMsg utils.PoolObj, proposedBlockHeader *types.Header) error { + // The signature list may contain empty entey. we only care the ones with values + var validSignatures []types.Signature + emptySigner := common.Address{} + for _, vote := range pooledVotes { + if vote.GetSigner() != emptySigner { + validSignatures = append(validSignatures, vote.(*types.Vote).Signature) + } + } + + epochInfo, err := x.getEpochSwitchInfo(chain, nil, currentVoteMsg.(*types.Vote).ProposedBlockInfo.Hash) + if err != nil { + log.Error("[voteHandler] Error when getting epoch switch Info", "error", err) + return errors.New("fail on voteHandler due to failure in getting epoch switch info") + } + + // Skip and wait for the next vote to process again if valid votes is less than what we required + certThreshold := x.config.V2.Config(uint64(currentVoteMsg.(*types.Vote).ProposedBlockInfo.Round)).CertThreshold + if float64(len(validSignatures)) < float64(epochInfo.MasternodesLen)*certThreshold { + log.Warn("[onVotePoolThresholdReached] Not enough valid signatures to generate QC", "VotesSignaturesAfterFilter", validSignatures, "NumberOfValidVotes", len(validSignatures), "NumberOfVotes", len(pooledVotes)) + return nil + } + // Genrate QC + quorumCert := &types.QuorumCert{ + ProposedBlockInfo: currentVoteMsg.(*types.Vote).ProposedBlockInfo, + Signatures: validSignatures, + GapNumber: currentVoteMsg.(*types.Vote).GapNumber, + } + err = x.processQC(chain, quorumCert) + if err != nil { + log.Error("Error while processing QC in the Vote handler after reaching pool threshold, ", err) + return err + } + log.Info("Successfully processed the vote and produced QC!", "QcRound", quorumCert.ProposedBlockInfo.Round, "QcNumOfSig", len(quorumCert.Signatures), "QcHash", quorumCert.ProposedBlockInfo.Hash, "QcNumber", quorumCert.ProposedBlockInfo.Number.Uint64()) + return nil +} + +// Hot stuff rule to decide whether this node is eligible to vote for the received block +func (x *XDPoS_v2) verifyVotingRule(blockChainReader consensus.ChainReader, blockInfo *types.BlockInfo, quorumCert *types.QuorumCert) (bool, error) { + // Make sure this node has not voted for this round. + if x.currentRound <= x.highestVotedRound { + log.Info("Failed to pass the voting rule verification, currentRound is not large then highestVoteRound", "x.currentRound", x.currentRound, "x.highestVotedRound", x.highestVotedRound) + return false, nil + } + /* + HotStuff Voting rule: + header's round == local current round, AND (one of the following two:) + header's block extends lockQuorumCert's ProposedBlockInfo (we need a isExtending(block_a, block_b) function), OR + header's QC's ProposedBlockInfo.Round > lockQuorumCert's ProposedBlockInfo.Round + */ + if blockInfo.Round != x.currentRound { + log.Info("Failed to pass the voting rule verification, blockRound is not equal currentRound", "x.currentRound", x.currentRound, "blockInfo.Round", blockInfo.Round) + return false, nil + } + // XDPoS v1.0 switch to v2.0, the proposed block can always pass voting rule + if x.lockQuorumCert == nil { + return true, nil + } + + if quorumCert.ProposedBlockInfo.Round > x.lockQuorumCert.ProposedBlockInfo.Round { + return true, nil + } + + isExtended, err := x.isExtendingFromAncestor(blockChainReader, blockInfo, x.lockQuorumCert.ProposedBlockInfo) + if err != nil { + log.Error("Failed to pass the voting rule verification, error on isExtendingFromAncestor", "err", err, "blockInfo", blockInfo, "x.lockQuorumCert.ProposedBlockInfo", x.lockQuorumCert.ProposedBlockInfo) + return false, err + } + + if !isExtended { + log.Warn("Failed to pass the voting rule verification, block is not extended from ancestor", "blockInfo", blockInfo, "x.lockQuorumCert.ProposedBlockInfo", x.lockQuorumCert.ProposedBlockInfo) + return false, nil + } + + return true, nil +} + +func (x *XDPoS_v2) isExtendingFromAncestor(blockChainReader consensus.ChainReader, currentBlock *types.BlockInfo, ancestorBlock *types.BlockInfo) (bool, error) { + blockNumDiff := int(big.NewInt(0).Sub(currentBlock.Number, ancestorBlock.Number).Int64()) + + nextBlockHash := currentBlock.Hash + for i := 0; i < blockNumDiff; i++ { + parentBlock := blockChainReader.GetHeaderByHash(nextBlockHash) + if parentBlock == nil { + return false, fmt.Errorf("could not find its parent block when checking whether currentBlock %v with hash %v is extending from the ancestorBlock %v", currentBlock.Number, currentBlock.Hash, ancestorBlock.Number) + } else { + nextBlockHash = parentBlock.ParentHash + } + log.Debug("[isExtendingFromAncestor] Found parent block", "CurrentBlockHash", currentBlock.Hash, "ParentHash", nextBlockHash) + } + + if nextBlockHash == ancestorBlock.Hash { + return true, nil + } + return false, nil +} + +func (x *XDPoS_v2) hygieneVotePool() { + x.lock.RLock() + round := x.currentRound + x.lock.RUnlock() + votePoolKeys := x.votePool.PoolObjKeysList() + + // Extract round number + for _, k := range votePoolKeys { + keyedRound, err := strconv.ParseInt(strings.Split(k, ":")[0], 10, 64) + if err != nil { + log.Error("[hygieneVotePool] Error while trying to get keyedRound inside pool", "Error", err) + continue + } + // Clean up any votes round that is 10 rounds older + if keyedRound < int64(round)-utils.PoolHygieneRound { + log.Debug("[hygieneVotePool] Cleaned vote poll at round", "Round", keyedRound, "currentRound", round, "Key", k) + x.votePool.ClearByPoolKey(k) + } + } +} + +func (x *XDPoS_v2) ReceivedVotes() map[string]map[common.Hash]utils.PoolObj { + return x.votePool.Get() +} diff --git a/contracts/subnet_validator/contract/XDCSubnetValidator.sol b/contracts/subnet_validator/contract/XDCSubnetValidator.sol new file mode 100644 index 000000000000..7c9dbc38432a --- /dev/null +++ b/contracts/subnet_validator/contract/XDCSubnetValidator.sol @@ -0,0 +1,322 @@ + +pragma solidity ^0.4.21; + +import "./libs/SafeMath.sol"; + + +contract XDCSubnetValidator { + using SafeMath for uint256; + + event Vote(address _voter, address _candidate, uint256 _cap); + event Unvote(address _voter, address _candidate, uint256 _cap); + event Propose(address _owner, address _candidate, uint256 _cap); + event Resign(address _owner, address _candidate); + event Withdraw(address _owner, uint256 _blockNumber, uint256 _cap); + event UploadedKYC(address _owner,string kycHash); + event InvalidatedNode(address _masternodeOwner, address[] _masternodes); + + struct ValidatorState { + address owner; + bool isCandidate; + uint256 cap; + mapping(address => uint256) voters; + } + + struct WithdrawState { + mapping(uint256 => uint256) caps; + uint256[] blockNumbers; + } + + mapping(address => WithdrawState) withdrawsState; + + mapping(address => ValidatorState) validatorsState; + mapping(address => address[]) voters; + + // Mapping structures added for KYC feature. + mapping(address => string[]) public KYCString; + mapping(address => uint) public invalidKYCCount; + mapping(address => mapping(address => bool)) public hasVotedInvalid; + mapping(address => address[]) public ownerToCandidate; + address[] public owners; + + address[] public candidates; + + uint256 public candidateCount = 0; + uint256 public ownerCount =0; + uint256 public minCandidateCap; + uint256 public minVoterCap; + uint256 public maxValidatorNumber; + uint256 public candidateWithdrawDelay; + uint256 public voterWithdrawDelay; + + address[] public grandMasters; + mapping(address => bool) public grandMasterMap; + + modifier onlyValidCandidateCap { + // anyone can deposit X XDC to become a candidate + require(msg.value >= minCandidateCap); + _; + } + + modifier onlyValidVoterCap { + + require(msg.value >= minVoterCap); + _; + } + + modifier onlyKYCWhitelisted { + require(KYCString[msg.sender].length!=0 || ownerToCandidate[msg.sender].length>0); + _; + } + + modifier onlyOwner(address _candidate) { + require(validatorsState[_candidate].owner == msg.sender); + _; + } + + modifier onlyCandidate(address _candidate) { + require(validatorsState[_candidate].isCandidate); + _; + } + + modifier onlyValidCandidate (address _candidate) { + require(validatorsState[_candidate].isCandidate); + _; + } + + modifier onlyNotCandidate (address _candidate) { + require(!validatorsState[_candidate].isCandidate); + _; + } + + modifier onlyValidVote (address _candidate, uint256 _cap) { + require(validatorsState[_candidate].voters[msg.sender] >= _cap); + if (validatorsState[_candidate].owner == msg.sender) { + require(validatorsState[_candidate].voters[msg.sender].sub(_cap) >= minCandidateCap); + } + _; + } + + modifier onlyValidWithdraw (uint256 _blockNumber, uint _index) { + require(_blockNumber > 0); + require(block.number >= _blockNumber); + require(withdrawsState[msg.sender].caps[_blockNumber] > 0); + require(withdrawsState[msg.sender].blockNumbers[_index] == _blockNumber); + _; + } + + modifier onlyGrandMaster() { + require(grandMasterMap[msg.sender] == true); + _; + } + + function XDCValidator ( + address[] _candidates, + uint256[] _caps, + address _firstOwner, + uint256 _minCandidateCap, + uint256 _minVoterCap, + uint256 _maxValidatorNumber, + uint256 _candidateWithdrawDelay, + uint256 _voterWithdrawDelay, + address[] _grandMasters + ) public { + minCandidateCap = _minCandidateCap; + minVoterCap = _minVoterCap; + maxValidatorNumber = _maxValidatorNumber; + candidateWithdrawDelay = _candidateWithdrawDelay; + voterWithdrawDelay = _voterWithdrawDelay; + candidateCount = _candidates.length; + owners.push(_firstOwner); + ownerCount++; + for (uint256 i = 0; i < _candidates.length; i++) { + candidates.push(_candidates[i]); + validatorsState[_candidates[i]] = ValidatorState({ + owner: _firstOwner, + isCandidate: true, + cap: _caps[i] + }); + voters[_candidates[i]].push(_firstOwner); + ownerToCandidate[_firstOwner].push(_candidates[i]); + validatorsState[_candidates[i]].voters[_firstOwner] = minCandidateCap; + } + string memory grandMasterKYC = "grandmaster"; + for (i = 0; i < _grandMasters.length; i++) { + grandMasters.push(_grandMasters[i]); + grandMasterMap[_grandMasters[i]] = true; + KYCString[_grandMasters[i]].push(grandMasterKYC); + } + } + + + // uploadKYC : anyone can upload a KYC; its not equivalent to becoming an owner. + function uploadKYC(string kychash) external { + KYCString[msg.sender].push(kychash); + emit UploadedKYC(msg.sender,kychash); + } + + // propose : any non-candidate who has uploaded its KYC can become an owner by proposing a candidate. + function propose(address _candidate) external payable onlyValidCandidateCap onlyKYCWhitelisted onlyNotCandidate(_candidate) onlyGrandMaster { + uint256 cap = validatorsState[_candidate].cap.add(msg.value); + candidates.push(_candidate); + validatorsState[_candidate] = ValidatorState({ + owner: msg.sender, + isCandidate: true, + cap: cap + }); + validatorsState[_candidate].voters[msg.sender] = validatorsState[_candidate].voters[msg.sender].add(msg.value); + candidateCount = candidateCount.add(1); + if (ownerToCandidate[msg.sender].length ==0){ + owners.push(msg.sender); + ownerCount++; + } + ownerToCandidate[msg.sender].push(_candidate); + voters[_candidate].push(msg.sender); + emit Propose(msg.sender, _candidate, msg.value); + } + + function vote(address _candidate) external payable onlyValidVoterCap onlyValidCandidate(_candidate) onlyGrandMaster { + validatorsState[_candidate].cap = validatorsState[_candidate].cap.add(msg.value); + if (validatorsState[_candidate].voters[msg.sender] == 0) { + voters[_candidate].push(msg.sender); + } + validatorsState[_candidate].voters[msg.sender] = validatorsState[_candidate].voters[msg.sender].add(msg.value); + emit Vote(msg.sender, _candidate, msg.value); + } + + function getCandidates() public view returns(address[]) { + return candidates; + } + + function getGrandMasters() public view returns(address[]) { + return grandMasters; + } + + function getCandidateCap(address _candidate) public view returns(uint256) { + return validatorsState[_candidate].cap; + } + + function getCandidateOwner(address _candidate) public view returns(address) { + return validatorsState[_candidate].owner; + } + + function getVoterCap(address _candidate, address _voter) public view returns(uint256) { + return validatorsState[_candidate].voters[_voter]; + } + + function getVoters(address _candidate) public view returns(address[]) { + return voters[_candidate]; + } + + function isCandidate(address _candidate) public view returns(bool) { + return validatorsState[_candidate].isCandidate; + } + + function getWithdrawBlockNumbers() public view returns(uint256[]) { + return withdrawsState[msg.sender].blockNumbers; + } + + function getWithdrawCap(uint256 _blockNumber) public view returns(uint256) { + return withdrawsState[msg.sender].caps[_blockNumber]; + } + + function unvote(address _candidate, uint256 _cap) public onlyValidVote(_candidate, _cap) { + validatorsState[_candidate].cap = validatorsState[_candidate].cap.sub(_cap); + validatorsState[_candidate].voters[msg.sender] = validatorsState[_candidate].voters[msg.sender].sub(_cap); + + // refund after delay X blocks + uint256 withdrawBlockNumber = voterWithdrawDelay.add(block.number); + withdrawsState[msg.sender].caps[withdrawBlockNumber] = withdrawsState[msg.sender].caps[withdrawBlockNumber].add(_cap); + withdrawsState[msg.sender].blockNumbers.push(withdrawBlockNumber); + + emit Unvote(msg.sender, _candidate, _cap); + } + + function resign(address _candidate) public onlyOwner(_candidate) onlyCandidate(_candidate) { + validatorsState[_candidate].isCandidate = false; + candidateCount = candidateCount.sub(1); + for (uint256 i = 0; i < candidates.length; i++) { + if (candidates[i] == _candidate) { + delete candidates[i]; + break; + } + } + uint256 cap = validatorsState[_candidate].voters[msg.sender]; + validatorsState[_candidate].cap = validatorsState[_candidate].cap.sub(cap); + validatorsState[_candidate].voters[msg.sender] = 0; + // refunding after resigning X blocks + uint256 withdrawBlockNumber = candidateWithdrawDelay.add(block.number); + withdrawsState[msg.sender].caps[withdrawBlockNumber] = withdrawsState[msg.sender].caps[withdrawBlockNumber].add(cap); + withdrawsState[msg.sender].blockNumbers.push(withdrawBlockNumber); + emit Resign(msg.sender, _candidate); + } + + // voteInvalidKYC : any candidate can vote for invalid KYC i.e. a particular candidate's owner has uploaded a bad KYC. + // On securing 75% votes against an owner ( not candidate ), owner & all its candidates will lose their funds. + function voteInvalidKYC(address _invalidCandidate) onlyValidCandidate(msg.sender) onlyValidCandidate(_invalidCandidate) public { + address candidateOwner = getCandidateOwner(msg.sender); + address _invalidMasternode = getCandidateOwner(_invalidCandidate); + require(!hasVotedInvalid[candidateOwner][_invalidMasternode]); + hasVotedInvalid[candidateOwner][_invalidMasternode] = true; + invalidKYCCount[_invalidMasternode] += 1; + if( invalidKYCCount[_invalidMasternode]*100/getOwnerCount() >= 75 ){ + // 75% owners say that the KYC is invalid + address[] memory allMasternodes = new address[](candidates.length-1) ; + uint count=0; + for (uint i=0;i 0); // Solidity automatically throws when dividing by 0 + // uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return a / b; + } + + /** + * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + /** + * @dev Adds two numbers, throws on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; + } +} diff --git a/contracts/subnet_validator/contract/subnet_validator.go b/contracts/subnet_validator/contract/subnet_validator.go new file mode 100644 index 000000000000..d40731db3f06 --- /dev/null +++ b/contracts/subnet_validator/contract/subnet_validator.go @@ -0,0 +1,2134 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package contract + +import ( + "math/big" + "strings" + + ethereum "github.com/XinFinOrg/XDPoSChain" + "github.com/XinFinOrg/XDPoSChain/accounts/abi" + "github.com/XinFinOrg/XDPoSChain/accounts/abi/bind" + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/event" +) + +// SafeMathABI is the input ABI used to generate the binding from. +const SafeMathABI = "[]" + +// SafeMathBin is the compiled bytecode used for deploying new contracts. +const SafeMathBin = `0x604c602c600b82828239805160001a60731460008114601c57601e565bfe5b5030600052607381538281f30073000000000000000000000000000000000000000030146060604052600080fd00a165627a7a72305820b9407d48ebc7efee5c9f08b3b3a957df2939281f5913225e8c1291f069b900490029` + +// DeploySafeMath deploys a new Ethereum contract, binding an instance of SafeMath to it. +func DeploySafeMath(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *SafeMath, error) { + parsed, err := abi.JSON(strings.NewReader(SafeMathABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(SafeMathBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &SafeMath{SafeMathCaller: SafeMathCaller{contract: contract}, SafeMathTransactor: SafeMathTransactor{contract: contract}, SafeMathFilterer: SafeMathFilterer{contract: contract}}, nil +} + +// SafeMath is an auto generated Go binding around an Ethereum contract. +type SafeMath struct { + SafeMathCaller // Read-only binding to the contract + SafeMathTransactor // Write-only binding to the contract + SafeMathFilterer // Log filterer for contract events +} + +// SafeMathCaller is an auto generated read-only Go binding around an Ethereum contract. +type SafeMathCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// SafeMathTransactor is an auto generated write-only Go binding around an Ethereum contract. +type SafeMathTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// SafeMathFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type SafeMathFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// SafeMathSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type SafeMathSession struct { + Contract *SafeMath // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// SafeMathCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type SafeMathCallerSession struct { + Contract *SafeMathCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// SafeMathTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type SafeMathTransactorSession struct { + Contract *SafeMathTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// SafeMathRaw is an auto generated low-level Go binding around an Ethereum contract. +type SafeMathRaw struct { + Contract *SafeMath // Generic contract binding to access the raw methods on +} + +// SafeMathCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type SafeMathCallerRaw struct { + Contract *SafeMathCaller // Generic read-only contract binding to access the raw methods on +} + +// SafeMathTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type SafeMathTransactorRaw struct { + Contract *SafeMathTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewSafeMath creates a new instance of SafeMath, bound to a specific deployed contract. +func NewSafeMath(address common.Address, backend bind.ContractBackend) (*SafeMath, error) { + contract, err := bindSafeMath(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &SafeMath{SafeMathCaller: SafeMathCaller{contract: contract}, SafeMathTransactor: SafeMathTransactor{contract: contract}, SafeMathFilterer: SafeMathFilterer{contract: contract}}, nil +} + +// NewSafeMathCaller creates a new read-only instance of SafeMath, bound to a specific deployed contract. +func NewSafeMathCaller(address common.Address, caller bind.ContractCaller) (*SafeMathCaller, error) { + contract, err := bindSafeMath(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &SafeMathCaller{contract: contract}, nil +} + +// NewSafeMathTransactor creates a new write-only instance of SafeMath, bound to a specific deployed contract. +func NewSafeMathTransactor(address common.Address, transactor bind.ContractTransactor) (*SafeMathTransactor, error) { + contract, err := bindSafeMath(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &SafeMathTransactor{contract: contract}, nil +} + +// NewSafeMathFilterer creates a new log filterer instance of SafeMath, bound to a specific deployed contract. +func NewSafeMathFilterer(address common.Address, filterer bind.ContractFilterer) (*SafeMathFilterer, error) { + contract, err := bindSafeMath(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &SafeMathFilterer{contract: contract}, nil +} + +// bindSafeMath binds a generic wrapper to an already deployed contract. +func bindSafeMath(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(SafeMathABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_SafeMath *SafeMathRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _SafeMath.Contract.SafeMathCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_SafeMath *SafeMathRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _SafeMath.Contract.SafeMathTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_SafeMath *SafeMathRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _SafeMath.Contract.SafeMathTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_SafeMath *SafeMathCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _SafeMath.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_SafeMath *SafeMathTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _SafeMath.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_SafeMath *SafeMathTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _SafeMath.Contract.contract.Transact(opts, method, params...) +} + +// XDCValidatorABI is the input ABI used to generate the binding from. +const XDCValidatorABI = "[{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"owners\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"ownerCount\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"},{\"name\":\"\",\"type\":\"address\"}],\"name\":\"hasVotedInvalid\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"},{\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"ownerToCandidate\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"candidates\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"grandMasterMap\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"},{\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"KYCString\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"grandMasters\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"invalidKYCCount\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"candidateCount\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"voterWithdrawDelay\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"maxValidatorNumber\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"candidateWithdrawDelay\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"minCandidateCap\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"minVoterCap\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_candidates\",\"type\":\"address[]\"},{\"name\":\"_caps\",\"type\":\"uint256[]\"},{\"name\":\"_firstOwner\",\"type\":\"address\"},{\"name\":\"_minCandidateCap\",\"type\":\"uint256\"},{\"name\":\"_minVoterCap\",\"type\":\"uint256\"},{\"name\":\"_maxValidatorNumber\",\"type\":\"uint256\"},{\"name\":\"_candidateWithdrawDelay\",\"type\":\"uint256\"},{\"name\":\"_voterWithdrawDelay\",\"type\":\"uint256\"},{\"name\":\"_grandMasters\",\"type\":\"address[]\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_voter\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_candidate\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_cap\",\"type\":\"uint256\"}],\"name\":\"Vote\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_voter\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_candidate\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_cap\",\"type\":\"uint256\"}],\"name\":\"Unvote\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_owner\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_candidate\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_cap\",\"type\":\"uint256\"}],\"name\":\"Propose\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_owner\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_candidate\",\"type\":\"address\"}],\"name\":\"Resign\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_owner\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_blockNumber\",\"type\":\"uint256\"},{\"indexed\":false,\"name\":\"_cap\",\"type\":\"uint256\"}],\"name\":\"Withdraw\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_owner\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"kycHash\",\"type\":\"string\"}],\"name\":\"UploadedKYC\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"_masternodeOwner\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_masternodes\",\"type\":\"address[]\"}],\"name\":\"InvalidatedNode\",\"type\":\"event\"},{\"constant\":false,\"inputs\":[{\"name\":\"kychash\",\"type\":\"string\"}],\"name\":\"uploadKYC\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_candidate\",\"type\":\"address\"}],\"name\":\"propose\",\"outputs\":[],\"payable\":true,\"stateMutability\":\"payable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_candidate\",\"type\":\"address\"}],\"name\":\"vote\",\"outputs\":[],\"payable\":true,\"stateMutability\":\"payable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getCandidates\",\"outputs\":[{\"name\":\"\",\"type\":\"address[]\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getGrandMasters\",\"outputs\":[{\"name\":\"\",\"type\":\"address[]\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_candidate\",\"type\":\"address\"}],\"name\":\"getCandidateCap\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_candidate\",\"type\":\"address\"}],\"name\":\"getCandidateOwner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_candidate\",\"type\":\"address\"},{\"name\":\"_voter\",\"type\":\"address\"}],\"name\":\"getVoterCap\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_candidate\",\"type\":\"address\"}],\"name\":\"getVoters\",\"outputs\":[{\"name\":\"\",\"type\":\"address[]\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_candidate\",\"type\":\"address\"}],\"name\":\"isCandidate\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getWithdrawBlockNumbers\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256[]\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_blockNumber\",\"type\":\"uint256\"}],\"name\":\"getWithdrawCap\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_candidate\",\"type\":\"address\"},{\"name\":\"_cap\",\"type\":\"uint256\"}],\"name\":\"unvote\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_candidate\",\"type\":\"address\"}],\"name\":\"resign\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_invalidCandidate\",\"type\":\"address\"}],\"name\":\"voteInvalidKYC\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_invalidCandidate\",\"type\":\"address\"}],\"name\":\"invalidPercent\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"getOwnerCount\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_address\",\"type\":\"address\"}],\"name\":\"getLatestKYC\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_address\",\"type\":\"address\"}],\"name\":\"getHashCount\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_blockNumber\",\"type\":\"uint256\"},{\"name\":\"_index\",\"type\":\"uint256\"}],\"name\":\"withdraw\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" + +// XDCValidatorBin is the compiled bytecode used for deploying new contracts. +const XDCValidatorBin = `0x606060405260006009556000600a5534156200001a57600080fd5b604051620043ec380380620043ec8339810160405280805182019190602001805182019190602001805190602001909190805190602001909190805190602001909190805190602001909190805190602001909190805190602001909190805182019190505060006200008c620006da565b87600b8190555086600c8190555085600d8190555084600e8190555083600f819055508a5160098190555060078054806001018281620000cd9190620006ee565b916000526020600020900160008b909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050600a60008154809291906001019190505550600091505b8a51821015620004f25760088054806001018281620001539190620006ee565b916000526020600020900160008d858151811015156200016f57fe5b90602001906020020151909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550506060604051908101604052808a73ffffffffffffffffffffffffffffffffffffffff1681526020016001151581526020018b84815181101515620001fa57fe5b90602001906020020151815250600160008d858151811015156200021a57fe5b9060200190602002015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008201518160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060208201518160000160146101000a81548160ff02191690831515021790555060408201518160010155905050600260008c84815181101515620002e557fe5b9060200190602002015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002080548060010182816200033d9190620006ee565b916000526020600020900160008b909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050600660008a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208054806001018281620003df9190620006ee565b916000526020600020900160008d85815181101515620003fb57fe5b90602001906020020151909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050600b54600160008d858151811015156200045c57fe5b9060200190602002015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160008b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550818060010192505062000133565b6040805190810160405280600b81526020017f6772616e646d61737465720000000000000000000000000000000000000000008152509050600091505b8251821015620006c957601080548060010182816200054f9190620006ee565b9160005260206000209001600085858151811015156200056b57fe5b90602001906020020151909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550506001601160008585815181101515620005cb57fe5b9060200190602002015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055506003600084848151811015156200063957fe5b9060200190602002015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002080548060010182816200069191906200071d565b916000526020600020900160008390919091509080519060200190620006b99291906200074c565b505081806001019250506200052f565b505050505050505050505062000878565b602060405190810160405280600081525090565b8154818355818115116200071857818360005260206000209182019101620007179190620007d3565b5b505050565b8154818355818115116200074757818360005260206000209182019101620007469190620007fb565b5b505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106200078f57805160ff1916838001178555620007c0565b82800160010185558215620007c0579182015b82811115620007bf578251825591602001919060010190620007a2565b5b509050620007cf9190620007d3565b5090565b620007f891905b80821115620007f4576000816000905550600101620007da565b5090565b90565b6200082991905b808211156200082557600081816200081b91906200082c565b5060010162000802565b5090565b90565b50805460018160011615610100020316600290046000825580601f1062000854575062000875565b601f016020900490600052602060002090810190620008749190620007d3565b5b50565b613b6480620008886000396000f3006060604052600436106101b7576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806301267951146101bc578063025e7c27146101ea57806302aa9be21461024d57806306a49fce1461028f5780630db02622146102f95780630e3e4fb81461032257806315febd68146103925780632a3640b1146103c95780632d15cc041461044b5780632f9c4bba146104d9578063302b68721461054357806332658652146105af5780633477ee2e14610661578063441a3e70146106c457806352b3ed16146106f057806358e7525f1461075a5780635b6e3963146107a75780635b860d27146107f85780635b9cd8cc146108455780636132cd83146109005780636dd7d8ea1461096357806372e44a3814610991578063a9a981a3146109de578063a9ff959e14610a07578063ae6e43f514610a30578063b642facd14610a69578063c45607df14610ae2578063d09f1ab414610b2f578063d161c76714610b58578063d51b9e9314610b81578063d55b7dff14610bd2578063ef18374a14610bfb578063f2ee3c7d14610c24578063f5c9512514610c5d578063f8ac9dd514610c8b575b600080fd5b6101e8600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610cb4565b005b34156101f557600080fd5b61020b600480803590602001909190505061139a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561025857600080fd5b61028d600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506113d9565b005b341561029a57600080fd5b6102a2611934565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b838110156102e55780820151818401526020810190506102ca565b505050509050019250505060405180910390f35b341561030457600080fd5b61030c6119c8565b6040518082815260200191505060405180910390f35b341561032d57600080fd5b610378600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506119ce565b604051808215151515815260200191505060405180910390f35b341561039d57600080fd5b6103b360048080359060200190919050506119fd565b6040518082815260200191505060405180910390f35b34156103d457600080fd5b610409600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050611a59565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561045657600080fd5b610482600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050611aa7565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b838110156104c55780820151818401526020810190506104aa565b505050509050019250505060405180910390f35b34156104e457600080fd5b6104ec611b7a565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b8381101561052f578082015181840152602081019050610514565b505050509050019250505060405180910390f35b341561054e57600080fd5b610599600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050611c17565b6040518082815260200191505060405180910390f35b34156105ba57600080fd5b6105e6600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050611ca1565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561062657808201518184015260208101905061060b565b50505050905090810190601f1680156106535780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561066c57600080fd5b6106826004808035906020019091905050611f40565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156106cf57600080fd5b6106ee6004808035906020019091908035906020019091905050611f7f565b005b34156106fb57600080fd5b61070361222b565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b8381101561074657808201518184015260208101905061072b565b505050509050019250505060405180910390f35b341561076557600080fd5b610791600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506122bf565b6040518082815260200191505060405180910390f35b34156107b257600080fd5b6107de600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190505061230b565b604051808215151515815260200191505060405180910390f35b341561080357600080fd5b61082f600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190505061232b565b6040518082815260200191505060405180910390f35b341561085057600080fd5b610885600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506123f3565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156108c55780820151818401526020810190506108aa565b50505050905090810190601f1680156108f25780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561090b57600080fd5b61092160048080359060200190919050506124bc565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b61098f600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506124fb565b005b341561099c57600080fd5b6109c8600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050612943565b6040518082815260200191505060405180910390f35b34156109e957600080fd5b6109f161295b565b6040518082815260200191505060405180910390f35b3415610a1257600080fd5b610a1a612961565b6040518082815260200191505060405180910390f35b3415610a3b57600080fd5b610a67600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050612967565b005b3415610a7457600080fd5b610aa0600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050612f26565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b3415610aed57600080fd5b610b19600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050612f92565b6040518082815260200191505060405180910390f35b3415610b3a57600080fd5b610b42612fde565b6040518082815260200191505060405180910390f35b3415610b6357600080fd5b610b6b612fe4565b6040518082815260200191505060405180910390f35b3415610b8c57600080fd5b610bb8600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050612fea565b604051808215151515815260200191505060405180910390f35b3415610bdd57600080fd5b610be5613043565b6040518082815260200191505060405180910390f35b3415610c0657600080fd5b610c0e613049565b6040518082815260200191505060405180910390f35b3415610c2f57600080fd5b610c5b600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050613053565b005b3415610c6857600080fd5b610c89600480803590602001908201803590602001919091929050506137e1565b005b3415610c9657600080fd5b610c9e6138e0565b6040518082815260200191505060405180910390f35b6000600b543410151515610cc757600080fd5b6000600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002080549050141580610d5b57506000600660003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002080549050115b1515610d6657600080fd5b81600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160149054906101000a900460ff16151515610dc357600080fd5b60011515601160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff161515141515610e2257600080fd5b610e7734600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600101546138e690919063ffffffff16565b915060088054806001018281610e8d919061391d565b9160005260206000209001600085909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550506060604051908101604052803373ffffffffffffffffffffffffffffffffffffffff16815260200160011515815260200183815250600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008201518160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060208201518160000160146101000a81548160ff0219169083151502179055506040820151816001015590505061105634600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546138e690919063ffffffff16565b600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506110ef60016009546138e690919063ffffffff16565b6009819055506000600660003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208054905014156111b65760078054806001018281611154919061391d565b9160005260206000209001600033909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050600a600081548092919060010191905055505b600660003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208054806001018281611207919061391d565b9160005260206000209001600085909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002080548060010182816112a7919061391d565b9160005260206000209001600033909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550507f7635f1d87b47fba9f2b09e56eb4be75cca030e0cb179c1602ac9261d39a8f5c1338434604051808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001828152602001935050505060405180910390a1505050565b6007818154811015156113a957fe5b90600052602060002090016000915054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000828280600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015151561146b57600080fd5b3373ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614156115a457600b5461159682600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461390490919063ffffffff16565b101515156115a357600080fd5b5b6115f984600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001015461390490919063ffffffff16565b600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600101819055506116d184600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461390490919063ffffffff16565b600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555061176943600f546138e690919063ffffffff16565b92506117d0846000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000016000868152602001908152602001600020546138e690919063ffffffff16565b6000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000016000858152602001908152602001600020819055506000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010180548060010182816118799190613949565b9160005260206000209001600085909190915055507faa0e554f781c3c3b2be110a0557f260f11af9a8aa2c64bc1e7a31dbb21e32fa2338686604051808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001828152602001935050505060405180910390a15050505050565b61193c613975565b60088054806020026020016040519081016040528092919081815260200182805480156119be57602002820191906000526020600020905b8160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019060010190808311611974575b5050505050905090565b600a5481565b60056020528160005260406000206020528060005260406000206000915091509054906101000a900460ff1681565b60008060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000016000838152602001908152602001600020549050919050565b600660205281600052604060002081815481101515611a7457fe5b90600052602060002090016000915091509054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b611aaf613975565b600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020805480602002602001604051908101604052809291908181526020018280548015611b6e57602002820191906000526020600020905b8160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019060010190808311611b24575b50505050509050919050565b611b82613989565b6000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600101805480602002602001604051908101604052809291908181526020018280548015611c0d57602002820191906000526020600020905b815481526020019060010190808311611bf9575b5050505050905090565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b611ca961399d565b611cb282612fea565b15611e035760036000611cc484612f26565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600160036000611d0d86612f26565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208054905003815481101515611d5857fe5b90600052602060002090018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015611df75780601f10611dcc57610100808354040283529160200191611df7565b820191906000526020600020905b815481529060010190602001808311611dda57829003601f168201915b50505050509050611f3b565b600360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208054905003815481101515611e9457fe5b90600052602060002090018054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015611f335780601f10611f0857610100808354040283529160200191611f33565b820191906000526020600020905b815481529060010190602001808311611f1657829003601f168201915b505050505090505b919050565b600881815481101515611f4f57fe5b90600052602060002090016000915054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60008282600082111515611f9257600080fd5b814310151515611fa157600080fd5b60008060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160008481526020019081526020016000205411151561200257600080fd5b816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001018281548110151561205157fe5b90600052602060002090015414151561206957600080fd5b6000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160008681526020019081526020016000205492506000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000016000868152602001908152602001600020600090556000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001018481548110151561216257fe5b9060005260206000209001600090553373ffffffffffffffffffffffffffffffffffffffff166108fc849081150290604051600060405180830381858888f1935050505015156121b157600080fd5b7ff279e6a1f5e320cca91135676d9cb6e44ca8a08c0b88342bcdb1144f6511b568338685604051808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001838152602001828152602001935050505060405180910390a15050505050565b612233613975565b60108054806020026020016040519081016040528092919081815260200182805480156122b557602002820191906000526020600020905b8160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001906001019080831161226b575b5050505050905090565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600101549050919050565b60116020528060005260406000206000915054906101000a900460ff1681565b60008082600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160149054906101000a900460ff16151561238a57600080fd5b61239384612f26565b915061239d613049565b6064600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054028115156123e957fe5b0492505050919050565b60036020528160005260406000208181548110151561240e57fe5b9060005260206000209001600091509150508054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156124b45780601f10612489576101008083540402835291602001916124b4565b820191906000526020600020905b81548152906001019060200180831161249757829003601f168201915b505050505081565b6010818154811015156124cb57fe5b90600052602060002090016000915054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600c54341015151561250c57600080fd5b80600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160149054906101000a900460ff16151561256857600080fd5b60011515601160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff1615151415156125c757600080fd5b61261c34600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600101546138e690919063ffffffff16565b600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600101819055506000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054141561278b57600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020805480600101828161273b919061391d565b9160005260206000209001600033909190916101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505b61281d34600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546138e690919063ffffffff16565b600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055507f66a9138482c99e9baf08860110ef332cc0c23b4a199a53593d8db0fc8f96fbfc338334604051808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001828152602001935050505060405180910390a15050565b60046020528060005260406000206000915090505481565b60095481565b600f5481565b6000806000833373ffffffffffffffffffffffffffffffffffffffff16600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141515612a0957600080fd5b84600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160149054906101000a900460ff161515612a6557600080fd5b6000600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160146101000a81548160ff021916908315150217905550612ad6600160095461390490919063ffffffff16565b600981905550600094505b600880549050851015612bab578573ffffffffffffffffffffffffffffffffffffffff16600886815481101515612b1457fe5b906000526020600020900160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415612b9e57600885815481101515612b6b57fe5b906000526020600020900160006101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055612bab565b8480600101955050612ae1565b600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549350612c8284600160008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001015461390490919063ffffffff16565b600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600101819055506000600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060020160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550612d6243600e546138e690919063ffffffff16565b9250612dc9846000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000016000868152602001908152602001600020546138e690919063ffffffff16565b6000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000016000858152602001908152602001600020819055506000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206001018054806001018281612e729190613949565b9160005260206000209001600085909190915055507f4edf3e325d0063213a39f9085522994a1c44bea5f39e7d63ef61260a1e58c6d33387604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a1505050505050565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b6000600360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020805490509050919050565b600d5481565b600e5481565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160149054906101000a900460ff169050919050565b600b5481565b6000600a54905090565b60008061305e613975565b600080600033600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160149054906101000a900460ff1615156130bf57600080fd5b87600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160149054906101000a900460ff16151561311b57600080fd5b61312433612f26565b975061312f89612f26565b9650600560008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff161515156131c757600080fd5b6001600560008a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055506001600460008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550604b6132b4613049565b6064600460008b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020540281151561330057fe5b041015156137d65760016008805490500360405180591061331e5750595b9080825280602002602001820160405250955060009450600093505b600880549050841015613647578673ffffffffffffffffffffffffffffffffffffffff166133a160088681548110151561337057fe5b906000526020600020900160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16612f26565b73ffffffffffffffffffffffffffffffffffffffff16141561363a576133d3600160095461390490919063ffffffff16565b6009819055506008848154811015156133e857fe5b906000526020600020900160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16868680600101975081518110151561342857fe5b9060200190602002019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff168152505060088481548110151561347357fe5b906000526020600020900160006101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055600160006008868154811015156134b457fe5b906000526020600020900160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600080820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556000820160146101000a81549060ff021916905560018201600090555050600360008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006135ab91906139b1565b600660008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006135f691906139d2565b600460008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600090555b838060010194505061333a565b600092505b600780549050831015613729578673ffffffffffffffffffffffffffffffffffffffff1660078481548110151561367f57fe5b906000526020600020900160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141561371c576007838154811015156136d657fe5b906000526020600020900160006101000a81549073ffffffffffffffffffffffffffffffffffffffff0219169055600a6000815480929190600190039190505550613729565b828060010193505061364c565b7fe18d61a5bf4aa2ab40afc88aa9039d27ae17ff4ec1c65f5f414df6f02ce8b35e8787604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200180602001828103825283818151815260200191508051906020019060200280838360005b838110156137c15780820151818401526020810190506137a6565b50505050905001935050505060405180910390a15b505050505050505050565b600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020805480600101828161383291906139f3565b916000526020600020900160008484909192909192509190613855929190613a1f565b50507f949360d814b28a3b393a68909efe1fee120ee09cac30f360a0f80ab5415a611a338383604051808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001806020018281038252848482818152602001925080828437820191505094505050505060405180910390a15050565b600c5481565b60008082840190508381101515156138fa57fe5b8091505092915050565b600082821115151561391257fe5b818303905092915050565b815481835581811511613944578183600052602060002091820191016139439190613a9f565b5b505050565b8154818355818115116139705781836000526020600020918201910161396f9190613a9f565b5b505050565b602060405190810160405280600081525090565b602060405190810160405280600081525090565b602060405190810160405280600081525090565b50805460008255906000526020600020908101906139cf9190613ac4565b50565b50805460008255906000526020600020908101906139f09190613a9f565b50565b815481835581811511613a1a57818360005260206000209182019101613a199190613ac4565b5b505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10613a6057803560ff1916838001178555613a8e565b82800160010185558215613a8e579182015b82811115613a8d578235825591602001919060010190613a72565b5b509050613a9b9190613a9f565b5090565b613ac191905b80821115613abd576000816000905550600101613aa5565b5090565b90565b613aed91905b80821115613ae95760008181613ae09190613af0565b50600101613aca565b5090565b90565b50805460018160011615610100020316600290046000825580601f10613b165750613b35565b601f016020900490600052602060002090810190613b349190613a9f565b5b505600a165627a7a723058206cc88e4a7644089f97a9fbb30ce517eee86595dfa256c692c1b8269048d0d7480029` + +// DeployXDCValidator deploys a new Ethereum contract, binding an instance of XDCValidator to it. +func DeployXDCValidator(auth *bind.TransactOpts, backend bind.ContractBackend, _candidates []common.Address, _caps []*big.Int, _firstOwner common.Address, _minCandidateCap *big.Int, _minVoterCap *big.Int, _maxValidatorNumber *big.Int, _candidateWithdrawDelay *big.Int, _voterWithdrawDelay *big.Int, _grandMasters []common.Address) (common.Address, *types.Transaction, *XDCValidator, error) { + parsed, err := abi.JSON(strings.NewReader(XDCValidatorABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(XDCValidatorBin), backend, _candidates, _caps, _firstOwner, _minCandidateCap, _minVoterCap, _maxValidatorNumber, _candidateWithdrawDelay, _voterWithdrawDelay, _grandMasters) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &XDCValidator{XDCValidatorCaller: XDCValidatorCaller{contract: contract}, XDCValidatorTransactor: XDCValidatorTransactor{contract: contract}, XDCValidatorFilterer: XDCValidatorFilterer{contract: contract}}, nil +} + +// XDCValidator is an auto generated Go binding around an Ethereum contract. +type XDCValidator struct { + XDCValidatorCaller // Read-only binding to the contract + XDCValidatorTransactor // Write-only binding to the contract + XDCValidatorFilterer // Log filterer for contract events +} + +// XDCValidatorCaller is an auto generated read-only Go binding around an Ethereum contract. +type XDCValidatorCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// XDCValidatorTransactor is an auto generated write-only Go binding around an Ethereum contract. +type XDCValidatorTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// XDCValidatorFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type XDCValidatorFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// XDCValidatorSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type XDCValidatorSession struct { + Contract *XDCValidator // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// XDCValidatorCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type XDCValidatorCallerSession struct { + Contract *XDCValidatorCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// XDCValidatorTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type XDCValidatorTransactorSession struct { + Contract *XDCValidatorTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// XDCValidatorRaw is an auto generated low-level Go binding around an Ethereum contract. +type XDCValidatorRaw struct { + Contract *XDCValidator // Generic contract binding to access the raw methods on +} + +// XDCValidatorCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type XDCValidatorCallerRaw struct { + Contract *XDCValidatorCaller // Generic read-only contract binding to access the raw methods on +} + +// XDCValidatorTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type XDCValidatorTransactorRaw struct { + Contract *XDCValidatorTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewXDCValidator creates a new instance of XDCValidator, bound to a specific deployed contract. +func NewXDCValidator(address common.Address, backend bind.ContractBackend) (*XDCValidator, error) { + contract, err := bindXDCValidator(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &XDCValidator{XDCValidatorCaller: XDCValidatorCaller{contract: contract}, XDCValidatorTransactor: XDCValidatorTransactor{contract: contract}, XDCValidatorFilterer: XDCValidatorFilterer{contract: contract}}, nil +} + +// NewXDCValidatorCaller creates a new read-only instance of XDCValidator, bound to a specific deployed contract. +func NewXDCValidatorCaller(address common.Address, caller bind.ContractCaller) (*XDCValidatorCaller, error) { + contract, err := bindXDCValidator(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &XDCValidatorCaller{contract: contract}, nil +} + +// NewXDCValidatorTransactor creates a new write-only instance of XDCValidator, bound to a specific deployed contract. +func NewXDCValidatorTransactor(address common.Address, transactor bind.ContractTransactor) (*XDCValidatorTransactor, error) { + contract, err := bindXDCValidator(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &XDCValidatorTransactor{contract: contract}, nil +} + +// NewXDCValidatorFilterer creates a new log filterer instance of XDCValidator, bound to a specific deployed contract. +func NewXDCValidatorFilterer(address common.Address, filterer bind.ContractFilterer) (*XDCValidatorFilterer, error) { + contract, err := bindXDCValidator(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &XDCValidatorFilterer{contract: contract}, nil +} + +// bindXDCValidator binds a generic wrapper to an already deployed contract. +func bindXDCValidator(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(XDCValidatorABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_XDCValidator *XDCValidatorRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _XDCValidator.Contract.XDCValidatorCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_XDCValidator *XDCValidatorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _XDCValidator.Contract.XDCValidatorTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_XDCValidator *XDCValidatorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _XDCValidator.Contract.XDCValidatorTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_XDCValidator *XDCValidatorCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _XDCValidator.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_XDCValidator *XDCValidatorTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _XDCValidator.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_XDCValidator *XDCValidatorTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _XDCValidator.Contract.contract.Transact(opts, method, params...) +} + +// KYCString is a free data retrieval call binding the contract method 0x5b9cd8cc. +// +// Solidity: function KYCString( address, uint256) constant returns(string) +func (_XDCValidator *XDCValidatorCaller) KYCString(opts *bind.CallOpts, arg0 common.Address, arg1 *big.Int) (string, error) { + var ( + ret0 = new(string) + ) + out := &[]interface{}{ + ret0, + } + err := _XDCValidator.contract.Call(opts, out, "KYCString", arg0, arg1) + return *ret0, err +} + +// KYCString is a free data retrieval call binding the contract method 0x5b9cd8cc. +// +// Solidity: function KYCString( address, uint256) constant returns(string) +func (_XDCValidator *XDCValidatorSession) KYCString(arg0 common.Address, arg1 *big.Int) (string, error) { + return _XDCValidator.Contract.KYCString(&_XDCValidator.CallOpts, arg0, arg1) +} + +// KYCString is a free data retrieval call binding the contract method 0x5b9cd8cc. +// +// Solidity: function KYCString( address, uint256) constant returns(string) +func (_XDCValidator *XDCValidatorCallerSession) KYCString(arg0 common.Address, arg1 *big.Int) (string, error) { + return _XDCValidator.Contract.KYCString(&_XDCValidator.CallOpts, arg0, arg1) +} + +// CandidateCount is a free data retrieval call binding the contract method 0xa9a981a3. +// +// Solidity: function candidateCount() constant returns(uint256) +func (_XDCValidator *XDCValidatorCaller) CandidateCount(opts *bind.CallOpts) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := &[]interface{}{ + ret0, + } + err := _XDCValidator.contract.Call(opts, out, "candidateCount") + return *ret0, err +} + +// CandidateCount is a free data retrieval call binding the contract method 0xa9a981a3. +// +// Solidity: function candidateCount() constant returns(uint256) +func (_XDCValidator *XDCValidatorSession) CandidateCount() (*big.Int, error) { + return _XDCValidator.Contract.CandidateCount(&_XDCValidator.CallOpts) +} + +// CandidateCount is a free data retrieval call binding the contract method 0xa9a981a3. +// +// Solidity: function candidateCount() constant returns(uint256) +func (_XDCValidator *XDCValidatorCallerSession) CandidateCount() (*big.Int, error) { + return _XDCValidator.Contract.CandidateCount(&_XDCValidator.CallOpts) +} + +// CandidateWithdrawDelay is a free data retrieval call binding the contract method 0xd161c767. +// +// Solidity: function candidateWithdrawDelay() constant returns(uint256) +func (_XDCValidator *XDCValidatorCaller) CandidateWithdrawDelay(opts *bind.CallOpts) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := &[]interface{}{ + ret0, + } + err := _XDCValidator.contract.Call(opts, out, "candidateWithdrawDelay") + return *ret0, err +} + +// CandidateWithdrawDelay is a free data retrieval call binding the contract method 0xd161c767. +// +// Solidity: function candidateWithdrawDelay() constant returns(uint256) +func (_XDCValidator *XDCValidatorSession) CandidateWithdrawDelay() (*big.Int, error) { + return _XDCValidator.Contract.CandidateWithdrawDelay(&_XDCValidator.CallOpts) +} + +// CandidateWithdrawDelay is a free data retrieval call binding the contract method 0xd161c767. +// +// Solidity: function candidateWithdrawDelay() constant returns(uint256) +func (_XDCValidator *XDCValidatorCallerSession) CandidateWithdrawDelay() (*big.Int, error) { + return _XDCValidator.Contract.CandidateWithdrawDelay(&_XDCValidator.CallOpts) +} + +// Candidates is a free data retrieval call binding the contract method 0x3477ee2e. +// +// Solidity: function candidates( uint256) constant returns(address) +func (_XDCValidator *XDCValidatorCaller) Candidates(opts *bind.CallOpts, arg0 *big.Int) (common.Address, error) { + var ( + ret0 = new(common.Address) + ) + out := &[]interface{}{ + ret0, + } + err := _XDCValidator.contract.Call(opts, out, "candidates", arg0) + return *ret0, err +} + +// Candidates is a free data retrieval call binding the contract method 0x3477ee2e. +// +// Solidity: function candidates( uint256) constant returns(address) +func (_XDCValidator *XDCValidatorSession) Candidates(arg0 *big.Int) (common.Address, error) { + return _XDCValidator.Contract.Candidates(&_XDCValidator.CallOpts, arg0) +} + +// Candidates is a free data retrieval call binding the contract method 0x3477ee2e. +// +// Solidity: function candidates( uint256) constant returns(address) +func (_XDCValidator *XDCValidatorCallerSession) Candidates(arg0 *big.Int) (common.Address, error) { + return _XDCValidator.Contract.Candidates(&_XDCValidator.CallOpts, arg0) +} + +// GetCandidateCap is a free data retrieval call binding the contract method 0x58e7525f. +// +// Solidity: function getCandidateCap(_candidate address) constant returns(uint256) +func (_XDCValidator *XDCValidatorCaller) GetCandidateCap(opts *bind.CallOpts, _candidate common.Address) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := &[]interface{}{ + ret0, + } + err := _XDCValidator.contract.Call(opts, out, "getCandidateCap", _candidate) + return *ret0, err +} + +// GetCandidateCap is a free data retrieval call binding the contract method 0x58e7525f. +// +// Solidity: function getCandidateCap(_candidate address) constant returns(uint256) +func (_XDCValidator *XDCValidatorSession) GetCandidateCap(_candidate common.Address) (*big.Int, error) { + return _XDCValidator.Contract.GetCandidateCap(&_XDCValidator.CallOpts, _candidate) +} + +// GetCandidateCap is a free data retrieval call binding the contract method 0x58e7525f. +// +// Solidity: function getCandidateCap(_candidate address) constant returns(uint256) +func (_XDCValidator *XDCValidatorCallerSession) GetCandidateCap(_candidate common.Address) (*big.Int, error) { + return _XDCValidator.Contract.GetCandidateCap(&_XDCValidator.CallOpts, _candidate) +} + +// GetCandidateOwner is a free data retrieval call binding the contract method 0xb642facd. +// +// Solidity: function getCandidateOwner(_candidate address) constant returns(address) +func (_XDCValidator *XDCValidatorCaller) GetCandidateOwner(opts *bind.CallOpts, _candidate common.Address) (common.Address, error) { + var ( + ret0 = new(common.Address) + ) + out := &[]interface{}{ + ret0, + } + err := _XDCValidator.contract.Call(opts, out, "getCandidateOwner", _candidate) + return *ret0, err +} + +// GetCandidateOwner is a free data retrieval call binding the contract method 0xb642facd. +// +// Solidity: function getCandidateOwner(_candidate address) constant returns(address) +func (_XDCValidator *XDCValidatorSession) GetCandidateOwner(_candidate common.Address) (common.Address, error) { + return _XDCValidator.Contract.GetCandidateOwner(&_XDCValidator.CallOpts, _candidate) +} + +// GetCandidateOwner is a free data retrieval call binding the contract method 0xb642facd. +// +// Solidity: function getCandidateOwner(_candidate address) constant returns(address) +func (_XDCValidator *XDCValidatorCallerSession) GetCandidateOwner(_candidate common.Address) (common.Address, error) { + return _XDCValidator.Contract.GetCandidateOwner(&_XDCValidator.CallOpts, _candidate) +} + +// GetCandidates is a free data retrieval call binding the contract method 0x06a49fce. +// +// Solidity: function getCandidates() constant returns(address[]) +func (_XDCValidator *XDCValidatorCaller) GetCandidates(opts *bind.CallOpts) ([]common.Address, error) { + var ( + ret0 = new([]common.Address) + ) + out := &[]interface{}{ + ret0, + } + err := _XDCValidator.contract.Call(opts, out, "getCandidates") + return *ret0, err +} + +// GetCandidates is a free data retrieval call binding the contract method 0x06a49fce. +// +// Solidity: function getCandidates() constant returns(address[]) +func (_XDCValidator *XDCValidatorSession) GetCandidates() ([]common.Address, error) { + return _XDCValidator.Contract.GetCandidates(&_XDCValidator.CallOpts) +} + +// GetCandidates is a free data retrieval call binding the contract method 0x06a49fce. +// +// Solidity: function getCandidates() constant returns(address[]) +func (_XDCValidator *XDCValidatorCallerSession) GetCandidates() ([]common.Address, error) { + return _XDCValidator.Contract.GetCandidates(&_XDCValidator.CallOpts) +} + +// GetGrandMasters is a free data retrieval call binding the contract method 0x52b3ed16. +// +// Solidity: function getGrandMasters() constant returns(address[]) +func (_XDCValidator *XDCValidatorCaller) GetGrandMasters(opts *bind.CallOpts) ([]common.Address, error) { + var ( + ret0 = new([]common.Address) + ) + out := &[]interface{}{ + ret0, + } + err := _XDCValidator.contract.Call(opts, out, "getGrandMasters") + return *ret0, err +} + +// GetGrandMasters is a free data retrieval call binding the contract method 0x52b3ed16. +// +// Solidity: function getGrandMasters() constant returns(address[]) +func (_XDCValidator *XDCValidatorSession) GetGrandMasters() ([]common.Address, error) { + return _XDCValidator.Contract.GetGrandMasters(&_XDCValidator.CallOpts) +} + +// GetGrandMasters is a free data retrieval call binding the contract method 0x52b3ed16. +// +// Solidity: function getGrandMasters() constant returns(address[]) +func (_XDCValidator *XDCValidatorCallerSession) GetGrandMasters() ([]common.Address, error) { + return _XDCValidator.Contract.GetGrandMasters(&_XDCValidator.CallOpts) +} + +// GetHashCount is a free data retrieval call binding the contract method 0xc45607df. +// +// Solidity: function getHashCount(_address address) constant returns(uint256) +func (_XDCValidator *XDCValidatorCaller) GetHashCount(opts *bind.CallOpts, _address common.Address) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := &[]interface{}{ + ret0, + } + err := _XDCValidator.contract.Call(opts, out, "getHashCount", _address) + return *ret0, err +} + +// GetHashCount is a free data retrieval call binding the contract method 0xc45607df. +// +// Solidity: function getHashCount(_address address) constant returns(uint256) +func (_XDCValidator *XDCValidatorSession) GetHashCount(_address common.Address) (*big.Int, error) { + return _XDCValidator.Contract.GetHashCount(&_XDCValidator.CallOpts, _address) +} + +// GetHashCount is a free data retrieval call binding the contract method 0xc45607df. +// +// Solidity: function getHashCount(_address address) constant returns(uint256) +func (_XDCValidator *XDCValidatorCallerSession) GetHashCount(_address common.Address) (*big.Int, error) { + return _XDCValidator.Contract.GetHashCount(&_XDCValidator.CallOpts, _address) +} + +// GetLatestKYC is a free data retrieval call binding the contract method 0x32658652. +// +// Solidity: function getLatestKYC(_address address) constant returns(string) +func (_XDCValidator *XDCValidatorCaller) GetLatestKYC(opts *bind.CallOpts, _address common.Address) (string, error) { + var ( + ret0 = new(string) + ) + out := &[]interface{}{ + ret0, + } + err := _XDCValidator.contract.Call(opts, out, "getLatestKYC", _address) + return *ret0, err +} + +// GetLatestKYC is a free data retrieval call binding the contract method 0x32658652. +// +// Solidity: function getLatestKYC(_address address) constant returns(string) +func (_XDCValidator *XDCValidatorSession) GetLatestKYC(_address common.Address) (string, error) { + return _XDCValidator.Contract.GetLatestKYC(&_XDCValidator.CallOpts, _address) +} + +// GetLatestKYC is a free data retrieval call binding the contract method 0x32658652. +// +// Solidity: function getLatestKYC(_address address) constant returns(string) +func (_XDCValidator *XDCValidatorCallerSession) GetLatestKYC(_address common.Address) (string, error) { + return _XDCValidator.Contract.GetLatestKYC(&_XDCValidator.CallOpts, _address) +} + +// GetOwnerCount is a free data retrieval call binding the contract method 0xef18374a. +// +// Solidity: function getOwnerCount() constant returns(uint256) +func (_XDCValidator *XDCValidatorCaller) GetOwnerCount(opts *bind.CallOpts) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := &[]interface{}{ + ret0, + } + err := _XDCValidator.contract.Call(opts, out, "getOwnerCount") + return *ret0, err +} + +// GetOwnerCount is a free data retrieval call binding the contract method 0xef18374a. +// +// Solidity: function getOwnerCount() constant returns(uint256) +func (_XDCValidator *XDCValidatorSession) GetOwnerCount() (*big.Int, error) { + return _XDCValidator.Contract.GetOwnerCount(&_XDCValidator.CallOpts) +} + +// GetOwnerCount is a free data retrieval call binding the contract method 0xef18374a. +// +// Solidity: function getOwnerCount() constant returns(uint256) +func (_XDCValidator *XDCValidatorCallerSession) GetOwnerCount() (*big.Int, error) { + return _XDCValidator.Contract.GetOwnerCount(&_XDCValidator.CallOpts) +} + +// GetVoterCap is a free data retrieval call binding the contract method 0x302b6872. +// +// Solidity: function getVoterCap(_candidate address, _voter address) constant returns(uint256) +func (_XDCValidator *XDCValidatorCaller) GetVoterCap(opts *bind.CallOpts, _candidate common.Address, _voter common.Address) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := &[]interface{}{ + ret0, + } + err := _XDCValidator.contract.Call(opts, out, "getVoterCap", _candidate, _voter) + return *ret0, err +} + +// GetVoterCap is a free data retrieval call binding the contract method 0x302b6872. +// +// Solidity: function getVoterCap(_candidate address, _voter address) constant returns(uint256) +func (_XDCValidator *XDCValidatorSession) GetVoterCap(_candidate common.Address, _voter common.Address) (*big.Int, error) { + return _XDCValidator.Contract.GetVoterCap(&_XDCValidator.CallOpts, _candidate, _voter) +} + +// GetVoterCap is a free data retrieval call binding the contract method 0x302b6872. +// +// Solidity: function getVoterCap(_candidate address, _voter address) constant returns(uint256) +func (_XDCValidator *XDCValidatorCallerSession) GetVoterCap(_candidate common.Address, _voter common.Address) (*big.Int, error) { + return _XDCValidator.Contract.GetVoterCap(&_XDCValidator.CallOpts, _candidate, _voter) +} + +// GetVoters is a free data retrieval call binding the contract method 0x2d15cc04. +// +// Solidity: function getVoters(_candidate address) constant returns(address[]) +func (_XDCValidator *XDCValidatorCaller) GetVoters(opts *bind.CallOpts, _candidate common.Address) ([]common.Address, error) { + var ( + ret0 = new([]common.Address) + ) + out := &[]interface{}{ + ret0, + } + err := _XDCValidator.contract.Call(opts, out, "getVoters", _candidate) + return *ret0, err +} + +// GetVoters is a free data retrieval call binding the contract method 0x2d15cc04. +// +// Solidity: function getVoters(_candidate address) constant returns(address[]) +func (_XDCValidator *XDCValidatorSession) GetVoters(_candidate common.Address) ([]common.Address, error) { + return _XDCValidator.Contract.GetVoters(&_XDCValidator.CallOpts, _candidate) +} + +// GetVoters is a free data retrieval call binding the contract method 0x2d15cc04. +// +// Solidity: function getVoters(_candidate address) constant returns(address[]) +func (_XDCValidator *XDCValidatorCallerSession) GetVoters(_candidate common.Address) ([]common.Address, error) { + return _XDCValidator.Contract.GetVoters(&_XDCValidator.CallOpts, _candidate) +} + +// GetWithdrawBlockNumbers is a free data retrieval call binding the contract method 0x2f9c4bba. +// +// Solidity: function getWithdrawBlockNumbers() constant returns(uint256[]) +func (_XDCValidator *XDCValidatorCaller) GetWithdrawBlockNumbers(opts *bind.CallOpts) ([]*big.Int, error) { + var ( + ret0 = new([]*big.Int) + ) + out := &[]interface{}{ + ret0, + } + err := _XDCValidator.contract.Call(opts, out, "getWithdrawBlockNumbers") + return *ret0, err +} + +// GetWithdrawBlockNumbers is a free data retrieval call binding the contract method 0x2f9c4bba. +// +// Solidity: function getWithdrawBlockNumbers() constant returns(uint256[]) +func (_XDCValidator *XDCValidatorSession) GetWithdrawBlockNumbers() ([]*big.Int, error) { + return _XDCValidator.Contract.GetWithdrawBlockNumbers(&_XDCValidator.CallOpts) +} + +// GetWithdrawBlockNumbers is a free data retrieval call binding the contract method 0x2f9c4bba. +// +// Solidity: function getWithdrawBlockNumbers() constant returns(uint256[]) +func (_XDCValidator *XDCValidatorCallerSession) GetWithdrawBlockNumbers() ([]*big.Int, error) { + return _XDCValidator.Contract.GetWithdrawBlockNumbers(&_XDCValidator.CallOpts) +} + +// GetWithdrawCap is a free data retrieval call binding the contract method 0x15febd68. +// +// Solidity: function getWithdrawCap(_blockNumber uint256) constant returns(uint256) +func (_XDCValidator *XDCValidatorCaller) GetWithdrawCap(opts *bind.CallOpts, _blockNumber *big.Int) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := &[]interface{}{ + ret0, + } + err := _XDCValidator.contract.Call(opts, out, "getWithdrawCap", _blockNumber) + return *ret0, err +} + +// GetWithdrawCap is a free data retrieval call binding the contract method 0x15febd68. +// +// Solidity: function getWithdrawCap(_blockNumber uint256) constant returns(uint256) +func (_XDCValidator *XDCValidatorSession) GetWithdrawCap(_blockNumber *big.Int) (*big.Int, error) { + return _XDCValidator.Contract.GetWithdrawCap(&_XDCValidator.CallOpts, _blockNumber) +} + +// GetWithdrawCap is a free data retrieval call binding the contract method 0x15febd68. +// +// Solidity: function getWithdrawCap(_blockNumber uint256) constant returns(uint256) +func (_XDCValidator *XDCValidatorCallerSession) GetWithdrawCap(_blockNumber *big.Int) (*big.Int, error) { + return _XDCValidator.Contract.GetWithdrawCap(&_XDCValidator.CallOpts, _blockNumber) +} + +// GrandMasterMap is a free data retrieval call binding the contract method 0x5b6e3963. +// +// Solidity: function grandMasterMap( address) constant returns(bool) +func (_XDCValidator *XDCValidatorCaller) GrandMasterMap(opts *bind.CallOpts, arg0 common.Address) (bool, error) { + var ( + ret0 = new(bool) + ) + out := &[]interface{}{ + ret0, + } + err := _XDCValidator.contract.Call(opts, out, "grandMasterMap", arg0) + return *ret0, err +} + +// GrandMasterMap is a free data retrieval call binding the contract method 0x5b6e3963. +// +// Solidity: function grandMasterMap( address) constant returns(bool) +func (_XDCValidator *XDCValidatorSession) GrandMasterMap(arg0 common.Address) (bool, error) { + return _XDCValidator.Contract.GrandMasterMap(&_XDCValidator.CallOpts, arg0) +} + +// GrandMasterMap is a free data retrieval call binding the contract method 0x5b6e3963. +// +// Solidity: function grandMasterMap( address) constant returns(bool) +func (_XDCValidator *XDCValidatorCallerSession) GrandMasterMap(arg0 common.Address) (bool, error) { + return _XDCValidator.Contract.GrandMasterMap(&_XDCValidator.CallOpts, arg0) +} + +// GrandMasters is a free data retrieval call binding the contract method 0x6132cd83. +// +// Solidity: function grandMasters( uint256) constant returns(address) +func (_XDCValidator *XDCValidatorCaller) GrandMasters(opts *bind.CallOpts, arg0 *big.Int) (common.Address, error) { + var ( + ret0 = new(common.Address) + ) + out := &[]interface{}{ + ret0, + } + err := _XDCValidator.contract.Call(opts, out, "grandMasters", arg0) + return *ret0, err +} + +// GrandMasters is a free data retrieval call binding the contract method 0x6132cd83. +// +// Solidity: function grandMasters( uint256) constant returns(address) +func (_XDCValidator *XDCValidatorSession) GrandMasters(arg0 *big.Int) (common.Address, error) { + return _XDCValidator.Contract.GrandMasters(&_XDCValidator.CallOpts, arg0) +} + +// GrandMasters is a free data retrieval call binding the contract method 0x6132cd83. +// +// Solidity: function grandMasters( uint256) constant returns(address) +func (_XDCValidator *XDCValidatorCallerSession) GrandMasters(arg0 *big.Int) (common.Address, error) { + return _XDCValidator.Contract.GrandMasters(&_XDCValidator.CallOpts, arg0) +} + +// HasVotedInvalid is a free data retrieval call binding the contract method 0x0e3e4fb8. +// +// Solidity: function hasVotedInvalid( address, address) constant returns(bool) +func (_XDCValidator *XDCValidatorCaller) HasVotedInvalid(opts *bind.CallOpts, arg0 common.Address, arg1 common.Address) (bool, error) { + var ( + ret0 = new(bool) + ) + out := &[]interface{}{ + ret0, + } + err := _XDCValidator.contract.Call(opts, out, "hasVotedInvalid", arg0, arg1) + return *ret0, err +} + +// HasVotedInvalid is a free data retrieval call binding the contract method 0x0e3e4fb8. +// +// Solidity: function hasVotedInvalid( address, address) constant returns(bool) +func (_XDCValidator *XDCValidatorSession) HasVotedInvalid(arg0 common.Address, arg1 common.Address) (bool, error) { + return _XDCValidator.Contract.HasVotedInvalid(&_XDCValidator.CallOpts, arg0, arg1) +} + +// HasVotedInvalid is a free data retrieval call binding the contract method 0x0e3e4fb8. +// +// Solidity: function hasVotedInvalid( address, address) constant returns(bool) +func (_XDCValidator *XDCValidatorCallerSession) HasVotedInvalid(arg0 common.Address, arg1 common.Address) (bool, error) { + return _XDCValidator.Contract.HasVotedInvalid(&_XDCValidator.CallOpts, arg0, arg1) +} + +// InvalidKYCCount is a free data retrieval call binding the contract method 0x72e44a38. +// +// Solidity: function invalidKYCCount( address) constant returns(uint256) +func (_XDCValidator *XDCValidatorCaller) InvalidKYCCount(opts *bind.CallOpts, arg0 common.Address) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := &[]interface{}{ + ret0, + } + err := _XDCValidator.contract.Call(opts, out, "invalidKYCCount", arg0) + return *ret0, err +} + +// InvalidKYCCount is a free data retrieval call binding the contract method 0x72e44a38. +// +// Solidity: function invalidKYCCount( address) constant returns(uint256) +func (_XDCValidator *XDCValidatorSession) InvalidKYCCount(arg0 common.Address) (*big.Int, error) { + return _XDCValidator.Contract.InvalidKYCCount(&_XDCValidator.CallOpts, arg0) +} + +// InvalidKYCCount is a free data retrieval call binding the contract method 0x72e44a38. +// +// Solidity: function invalidKYCCount( address) constant returns(uint256) +func (_XDCValidator *XDCValidatorCallerSession) InvalidKYCCount(arg0 common.Address) (*big.Int, error) { + return _XDCValidator.Contract.InvalidKYCCount(&_XDCValidator.CallOpts, arg0) +} + +// InvalidPercent is a free data retrieval call binding the contract method 0x5b860d27. +// +// Solidity: function invalidPercent(_invalidCandidate address) constant returns(uint256) +func (_XDCValidator *XDCValidatorCaller) InvalidPercent(opts *bind.CallOpts, _invalidCandidate common.Address) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := &[]interface{}{ + ret0, + } + err := _XDCValidator.contract.Call(opts, out, "invalidPercent", _invalidCandidate) + return *ret0, err +} + +// InvalidPercent is a free data retrieval call binding the contract method 0x5b860d27. +// +// Solidity: function invalidPercent(_invalidCandidate address) constant returns(uint256) +func (_XDCValidator *XDCValidatorSession) InvalidPercent(_invalidCandidate common.Address) (*big.Int, error) { + return _XDCValidator.Contract.InvalidPercent(&_XDCValidator.CallOpts, _invalidCandidate) +} + +// InvalidPercent is a free data retrieval call binding the contract method 0x5b860d27. +// +// Solidity: function invalidPercent(_invalidCandidate address) constant returns(uint256) +func (_XDCValidator *XDCValidatorCallerSession) InvalidPercent(_invalidCandidate common.Address) (*big.Int, error) { + return _XDCValidator.Contract.InvalidPercent(&_XDCValidator.CallOpts, _invalidCandidate) +} + +// IsCandidate is a free data retrieval call binding the contract method 0xd51b9e93. +// +// Solidity: function isCandidate(_candidate address) constant returns(bool) +func (_XDCValidator *XDCValidatorCaller) IsCandidate(opts *bind.CallOpts, _candidate common.Address) (bool, error) { + var ( + ret0 = new(bool) + ) + out := &[]interface{}{ + ret0, + } + err := _XDCValidator.contract.Call(opts, out, "isCandidate", _candidate) + return *ret0, err +} + +// IsCandidate is a free data retrieval call binding the contract method 0xd51b9e93. +// +// Solidity: function isCandidate(_candidate address) constant returns(bool) +func (_XDCValidator *XDCValidatorSession) IsCandidate(_candidate common.Address) (bool, error) { + return _XDCValidator.Contract.IsCandidate(&_XDCValidator.CallOpts, _candidate) +} + +// IsCandidate is a free data retrieval call binding the contract method 0xd51b9e93. +// +// Solidity: function isCandidate(_candidate address) constant returns(bool) +func (_XDCValidator *XDCValidatorCallerSession) IsCandidate(_candidate common.Address) (bool, error) { + return _XDCValidator.Contract.IsCandidate(&_XDCValidator.CallOpts, _candidate) +} + +// MaxValidatorNumber is a free data retrieval call binding the contract method 0xd09f1ab4. +// +// Solidity: function maxValidatorNumber() constant returns(uint256) +func (_XDCValidator *XDCValidatorCaller) MaxValidatorNumber(opts *bind.CallOpts) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := &[]interface{}{ + ret0, + } + err := _XDCValidator.contract.Call(opts, out, "maxValidatorNumber") + return *ret0, err +} + +// MaxValidatorNumber is a free data retrieval call binding the contract method 0xd09f1ab4. +// +// Solidity: function maxValidatorNumber() constant returns(uint256) +func (_XDCValidator *XDCValidatorSession) MaxValidatorNumber() (*big.Int, error) { + return _XDCValidator.Contract.MaxValidatorNumber(&_XDCValidator.CallOpts) +} + +// MaxValidatorNumber is a free data retrieval call binding the contract method 0xd09f1ab4. +// +// Solidity: function maxValidatorNumber() constant returns(uint256) +func (_XDCValidator *XDCValidatorCallerSession) MaxValidatorNumber() (*big.Int, error) { + return _XDCValidator.Contract.MaxValidatorNumber(&_XDCValidator.CallOpts) +} + +// MinCandidateCap is a free data retrieval call binding the contract method 0xd55b7dff. +// +// Solidity: function minCandidateCap() constant returns(uint256) +func (_XDCValidator *XDCValidatorCaller) MinCandidateCap(opts *bind.CallOpts) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := &[]interface{}{ + ret0, + } + err := _XDCValidator.contract.Call(opts, out, "minCandidateCap") + return *ret0, err +} + +// MinCandidateCap is a free data retrieval call binding the contract method 0xd55b7dff. +// +// Solidity: function minCandidateCap() constant returns(uint256) +func (_XDCValidator *XDCValidatorSession) MinCandidateCap() (*big.Int, error) { + return _XDCValidator.Contract.MinCandidateCap(&_XDCValidator.CallOpts) +} + +// MinCandidateCap is a free data retrieval call binding the contract method 0xd55b7dff. +// +// Solidity: function minCandidateCap() constant returns(uint256) +func (_XDCValidator *XDCValidatorCallerSession) MinCandidateCap() (*big.Int, error) { + return _XDCValidator.Contract.MinCandidateCap(&_XDCValidator.CallOpts) +} + +// MinVoterCap is a free data retrieval call binding the contract method 0xf8ac9dd5. +// +// Solidity: function minVoterCap() constant returns(uint256) +func (_XDCValidator *XDCValidatorCaller) MinVoterCap(opts *bind.CallOpts) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := &[]interface{}{ + ret0, + } + err := _XDCValidator.contract.Call(opts, out, "minVoterCap") + return *ret0, err +} + +// MinVoterCap is a free data retrieval call binding the contract method 0xf8ac9dd5. +// +// Solidity: function minVoterCap() constant returns(uint256) +func (_XDCValidator *XDCValidatorSession) MinVoterCap() (*big.Int, error) { + return _XDCValidator.Contract.MinVoterCap(&_XDCValidator.CallOpts) +} + +// MinVoterCap is a free data retrieval call binding the contract method 0xf8ac9dd5. +// +// Solidity: function minVoterCap() constant returns(uint256) +func (_XDCValidator *XDCValidatorCallerSession) MinVoterCap() (*big.Int, error) { + return _XDCValidator.Contract.MinVoterCap(&_XDCValidator.CallOpts) +} + +// OwnerCount is a free data retrieval call binding the contract method 0x0db02622. +// +// Solidity: function ownerCount() constant returns(uint256) +func (_XDCValidator *XDCValidatorCaller) OwnerCount(opts *bind.CallOpts) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := &[]interface{}{ + ret0, + } + err := _XDCValidator.contract.Call(opts, out, "ownerCount") + return *ret0, err +} + +// OwnerCount is a free data retrieval call binding the contract method 0x0db02622. +// +// Solidity: function ownerCount() constant returns(uint256) +func (_XDCValidator *XDCValidatorSession) OwnerCount() (*big.Int, error) { + return _XDCValidator.Contract.OwnerCount(&_XDCValidator.CallOpts) +} + +// OwnerCount is a free data retrieval call binding the contract method 0x0db02622. +// +// Solidity: function ownerCount() constant returns(uint256) +func (_XDCValidator *XDCValidatorCallerSession) OwnerCount() (*big.Int, error) { + return _XDCValidator.Contract.OwnerCount(&_XDCValidator.CallOpts) +} + +// OwnerToCandidate is a free data retrieval call binding the contract method 0x2a3640b1. +// +// Solidity: function ownerToCandidate( address, uint256) constant returns(address) +func (_XDCValidator *XDCValidatorCaller) OwnerToCandidate(opts *bind.CallOpts, arg0 common.Address, arg1 *big.Int) (common.Address, error) { + var ( + ret0 = new(common.Address) + ) + out := &[]interface{}{ + ret0, + } + err := _XDCValidator.contract.Call(opts, out, "ownerToCandidate", arg0, arg1) + return *ret0, err +} + +// OwnerToCandidate is a free data retrieval call binding the contract method 0x2a3640b1. +// +// Solidity: function ownerToCandidate( address, uint256) constant returns(address) +func (_XDCValidator *XDCValidatorSession) OwnerToCandidate(arg0 common.Address, arg1 *big.Int) (common.Address, error) { + return _XDCValidator.Contract.OwnerToCandidate(&_XDCValidator.CallOpts, arg0, arg1) +} + +// OwnerToCandidate is a free data retrieval call binding the contract method 0x2a3640b1. +// +// Solidity: function ownerToCandidate( address, uint256) constant returns(address) +func (_XDCValidator *XDCValidatorCallerSession) OwnerToCandidate(arg0 common.Address, arg1 *big.Int) (common.Address, error) { + return _XDCValidator.Contract.OwnerToCandidate(&_XDCValidator.CallOpts, arg0, arg1) +} + +// Owners is a free data retrieval call binding the contract method 0x025e7c27. +// +// Solidity: function owners( uint256) constant returns(address) +func (_XDCValidator *XDCValidatorCaller) Owners(opts *bind.CallOpts, arg0 *big.Int) (common.Address, error) { + var ( + ret0 = new(common.Address) + ) + out := &[]interface{}{ + ret0, + } + err := _XDCValidator.contract.Call(opts, out, "owners", arg0) + return *ret0, err +} + +// Owners is a free data retrieval call binding the contract method 0x025e7c27. +// +// Solidity: function owners( uint256) constant returns(address) +func (_XDCValidator *XDCValidatorSession) Owners(arg0 *big.Int) (common.Address, error) { + return _XDCValidator.Contract.Owners(&_XDCValidator.CallOpts, arg0) +} + +// Owners is a free data retrieval call binding the contract method 0x025e7c27. +// +// Solidity: function owners( uint256) constant returns(address) +func (_XDCValidator *XDCValidatorCallerSession) Owners(arg0 *big.Int) (common.Address, error) { + return _XDCValidator.Contract.Owners(&_XDCValidator.CallOpts, arg0) +} + +// VoterWithdrawDelay is a free data retrieval call binding the contract method 0xa9ff959e. +// +// Solidity: function voterWithdrawDelay() constant returns(uint256) +func (_XDCValidator *XDCValidatorCaller) VoterWithdrawDelay(opts *bind.CallOpts) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := &[]interface{}{ + ret0, + } + err := _XDCValidator.contract.Call(opts, out, "voterWithdrawDelay") + return *ret0, err +} + +// VoterWithdrawDelay is a free data retrieval call binding the contract method 0xa9ff959e. +// +// Solidity: function voterWithdrawDelay() constant returns(uint256) +func (_XDCValidator *XDCValidatorSession) VoterWithdrawDelay() (*big.Int, error) { + return _XDCValidator.Contract.VoterWithdrawDelay(&_XDCValidator.CallOpts) +} + +// VoterWithdrawDelay is a free data retrieval call binding the contract method 0xa9ff959e. +// +// Solidity: function voterWithdrawDelay() constant returns(uint256) +func (_XDCValidator *XDCValidatorCallerSession) VoterWithdrawDelay() (*big.Int, error) { + return _XDCValidator.Contract.VoterWithdrawDelay(&_XDCValidator.CallOpts) +} + +// Propose is a paid mutator transaction binding the contract method 0x01267951. +// +// Solidity: function propose(_candidate address) returns() +func (_XDCValidator *XDCValidatorTransactor) Propose(opts *bind.TransactOpts, _candidate common.Address) (*types.Transaction, error) { + return _XDCValidator.contract.Transact(opts, "propose", _candidate) +} + +// Propose is a paid mutator transaction binding the contract method 0x01267951. +// +// Solidity: function propose(_candidate address) returns() +func (_XDCValidator *XDCValidatorSession) Propose(_candidate common.Address) (*types.Transaction, error) { + return _XDCValidator.Contract.Propose(&_XDCValidator.TransactOpts, _candidate) +} + +// Propose is a paid mutator transaction binding the contract method 0x01267951. +// +// Solidity: function propose(_candidate address) returns() +func (_XDCValidator *XDCValidatorTransactorSession) Propose(_candidate common.Address) (*types.Transaction, error) { + return _XDCValidator.Contract.Propose(&_XDCValidator.TransactOpts, _candidate) +} + +// Resign is a paid mutator transaction binding the contract method 0xae6e43f5. +// +// Solidity: function resign(_candidate address) returns() +func (_XDCValidator *XDCValidatorTransactor) Resign(opts *bind.TransactOpts, _candidate common.Address) (*types.Transaction, error) { + return _XDCValidator.contract.Transact(opts, "resign", _candidate) +} + +// Resign is a paid mutator transaction binding the contract method 0xae6e43f5. +// +// Solidity: function resign(_candidate address) returns() +func (_XDCValidator *XDCValidatorSession) Resign(_candidate common.Address) (*types.Transaction, error) { + return _XDCValidator.Contract.Resign(&_XDCValidator.TransactOpts, _candidate) +} + +// Resign is a paid mutator transaction binding the contract method 0xae6e43f5. +// +// Solidity: function resign(_candidate address) returns() +func (_XDCValidator *XDCValidatorTransactorSession) Resign(_candidate common.Address) (*types.Transaction, error) { + return _XDCValidator.Contract.Resign(&_XDCValidator.TransactOpts, _candidate) +} + +// Unvote is a paid mutator transaction binding the contract method 0x02aa9be2. +// +// Solidity: function unvote(_candidate address, _cap uint256) returns() +func (_XDCValidator *XDCValidatorTransactor) Unvote(opts *bind.TransactOpts, _candidate common.Address, _cap *big.Int) (*types.Transaction, error) { + return _XDCValidator.contract.Transact(opts, "unvote", _candidate, _cap) +} + +// Unvote is a paid mutator transaction binding the contract method 0x02aa9be2. +// +// Solidity: function unvote(_candidate address, _cap uint256) returns() +func (_XDCValidator *XDCValidatorSession) Unvote(_candidate common.Address, _cap *big.Int) (*types.Transaction, error) { + return _XDCValidator.Contract.Unvote(&_XDCValidator.TransactOpts, _candidate, _cap) +} + +// Unvote is a paid mutator transaction binding the contract method 0x02aa9be2. +// +// Solidity: function unvote(_candidate address, _cap uint256) returns() +func (_XDCValidator *XDCValidatorTransactorSession) Unvote(_candidate common.Address, _cap *big.Int) (*types.Transaction, error) { + return _XDCValidator.Contract.Unvote(&_XDCValidator.TransactOpts, _candidate, _cap) +} + +// UploadKYC is a paid mutator transaction binding the contract method 0xf5c95125. +// +// Solidity: function uploadKYC(kychash string) returns() +func (_XDCValidator *XDCValidatorTransactor) UploadKYC(opts *bind.TransactOpts, kychash string) (*types.Transaction, error) { + return _XDCValidator.contract.Transact(opts, "uploadKYC", kychash) +} + +// UploadKYC is a paid mutator transaction binding the contract method 0xf5c95125. +// +// Solidity: function uploadKYC(kychash string) returns() +func (_XDCValidator *XDCValidatorSession) UploadKYC(kychash string) (*types.Transaction, error) { + return _XDCValidator.Contract.UploadKYC(&_XDCValidator.TransactOpts, kychash) +} + +// UploadKYC is a paid mutator transaction binding the contract method 0xf5c95125. +// +// Solidity: function uploadKYC(kychash string) returns() +func (_XDCValidator *XDCValidatorTransactorSession) UploadKYC(kychash string) (*types.Transaction, error) { + return _XDCValidator.Contract.UploadKYC(&_XDCValidator.TransactOpts, kychash) +} + +// Vote is a paid mutator transaction binding the contract method 0x6dd7d8ea. +// +// Solidity: function vote(_candidate address) returns() +func (_XDCValidator *XDCValidatorTransactor) Vote(opts *bind.TransactOpts, _candidate common.Address) (*types.Transaction, error) { + return _XDCValidator.contract.Transact(opts, "vote", _candidate) +} + +// Vote is a paid mutator transaction binding the contract method 0x6dd7d8ea. +// +// Solidity: function vote(_candidate address) returns() +func (_XDCValidator *XDCValidatorSession) Vote(_candidate common.Address) (*types.Transaction, error) { + return _XDCValidator.Contract.Vote(&_XDCValidator.TransactOpts, _candidate) +} + +// Vote is a paid mutator transaction binding the contract method 0x6dd7d8ea. +// +// Solidity: function vote(_candidate address) returns() +func (_XDCValidator *XDCValidatorTransactorSession) Vote(_candidate common.Address) (*types.Transaction, error) { + return _XDCValidator.Contract.Vote(&_XDCValidator.TransactOpts, _candidate) +} + +// VoteInvalidKYC is a paid mutator transaction binding the contract method 0xf2ee3c7d. +// +// Solidity: function voteInvalidKYC(_invalidCandidate address) returns() +func (_XDCValidator *XDCValidatorTransactor) VoteInvalidKYC(opts *bind.TransactOpts, _invalidCandidate common.Address) (*types.Transaction, error) { + return _XDCValidator.contract.Transact(opts, "voteInvalidKYC", _invalidCandidate) +} + +// VoteInvalidKYC is a paid mutator transaction binding the contract method 0xf2ee3c7d. +// +// Solidity: function voteInvalidKYC(_invalidCandidate address) returns() +func (_XDCValidator *XDCValidatorSession) VoteInvalidKYC(_invalidCandidate common.Address) (*types.Transaction, error) { + return _XDCValidator.Contract.VoteInvalidKYC(&_XDCValidator.TransactOpts, _invalidCandidate) +} + +// VoteInvalidKYC is a paid mutator transaction binding the contract method 0xf2ee3c7d. +// +// Solidity: function voteInvalidKYC(_invalidCandidate address) returns() +func (_XDCValidator *XDCValidatorTransactorSession) VoteInvalidKYC(_invalidCandidate common.Address) (*types.Transaction, error) { + return _XDCValidator.Contract.VoteInvalidKYC(&_XDCValidator.TransactOpts, _invalidCandidate) +} + +// Withdraw is a paid mutator transaction binding the contract method 0x441a3e70. +// +// Solidity: function withdraw(_blockNumber uint256, _index uint256) returns() +func (_XDCValidator *XDCValidatorTransactor) Withdraw(opts *bind.TransactOpts, _blockNumber *big.Int, _index *big.Int) (*types.Transaction, error) { + return _XDCValidator.contract.Transact(opts, "withdraw", _blockNumber, _index) +} + +// Withdraw is a paid mutator transaction binding the contract method 0x441a3e70. +// +// Solidity: function withdraw(_blockNumber uint256, _index uint256) returns() +func (_XDCValidator *XDCValidatorSession) Withdraw(_blockNumber *big.Int, _index *big.Int) (*types.Transaction, error) { + return _XDCValidator.Contract.Withdraw(&_XDCValidator.TransactOpts, _blockNumber, _index) +} + +// Withdraw is a paid mutator transaction binding the contract method 0x441a3e70. +// +// Solidity: function withdraw(_blockNumber uint256, _index uint256) returns() +func (_XDCValidator *XDCValidatorTransactorSession) Withdraw(_blockNumber *big.Int, _index *big.Int) (*types.Transaction, error) { + return _XDCValidator.Contract.Withdraw(&_XDCValidator.TransactOpts, _blockNumber, _index) +} + +// XDCValidatorInvalidatedNodeIterator is returned from FilterInvalidatedNode and is used to iterate over the raw logs and unpacked data for InvalidatedNode events raised by the XDCValidator contract. +type XDCValidatorInvalidatedNodeIterator struct { + Event *XDCValidatorInvalidatedNode // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *XDCValidatorInvalidatedNodeIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(XDCValidatorInvalidatedNode) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(XDCValidatorInvalidatedNode) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *XDCValidatorInvalidatedNodeIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *XDCValidatorInvalidatedNodeIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// XDCValidatorInvalidatedNode represents a InvalidatedNode event raised by the XDCValidator contract. +type XDCValidatorInvalidatedNode struct { + MasternodeOwner common.Address + Masternodes []common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterInvalidatedNode is a free log retrieval operation binding the contract event 0xe18d61a5bf4aa2ab40afc88aa9039d27ae17ff4ec1c65f5f414df6f02ce8b35e. +// +// Solidity: event InvalidatedNode(_masternodeOwner address, _masternodes address[]) +func (_XDCValidator *XDCValidatorFilterer) FilterInvalidatedNode(opts *bind.FilterOpts) (*XDCValidatorInvalidatedNodeIterator, error) { + + logs, sub, err := _XDCValidator.contract.FilterLogs(opts, "InvalidatedNode") + if err != nil { + return nil, err + } + return &XDCValidatorInvalidatedNodeIterator{contract: _XDCValidator.contract, event: "InvalidatedNode", logs: logs, sub: sub}, nil +} + +// WatchInvalidatedNode is a free log subscription operation binding the contract event 0xe18d61a5bf4aa2ab40afc88aa9039d27ae17ff4ec1c65f5f414df6f02ce8b35e. +// +// Solidity: event InvalidatedNode(_masternodeOwner address, _masternodes address[]) +func (_XDCValidator *XDCValidatorFilterer) WatchInvalidatedNode(opts *bind.WatchOpts, sink chan<- *XDCValidatorInvalidatedNode) (event.Subscription, error) { + + logs, sub, err := _XDCValidator.contract.WatchLogs(opts, "InvalidatedNode") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(XDCValidatorInvalidatedNode) + if err := _XDCValidator.contract.UnpackLog(event, "InvalidatedNode", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// XDCValidatorProposeIterator is returned from FilterPropose and is used to iterate over the raw logs and unpacked data for Propose events raised by the XDCValidator contract. +type XDCValidatorProposeIterator struct { + Event *XDCValidatorPropose // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *XDCValidatorProposeIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(XDCValidatorPropose) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(XDCValidatorPropose) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *XDCValidatorProposeIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *XDCValidatorProposeIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// XDCValidatorPropose represents a Propose event raised by the XDCValidator contract. +type XDCValidatorPropose struct { + Owner common.Address + Candidate common.Address + Cap *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterPropose is a free log retrieval operation binding the contract event 0x7635f1d87b47fba9f2b09e56eb4be75cca030e0cb179c1602ac9261d39a8f5c1. +// +// Solidity: event Propose(_owner address, _candidate address, _cap uint256) +func (_XDCValidator *XDCValidatorFilterer) FilterPropose(opts *bind.FilterOpts) (*XDCValidatorProposeIterator, error) { + + logs, sub, err := _XDCValidator.contract.FilterLogs(opts, "Propose") + if err != nil { + return nil, err + } + return &XDCValidatorProposeIterator{contract: _XDCValidator.contract, event: "Propose", logs: logs, sub: sub}, nil +} + +// WatchPropose is a free log subscription operation binding the contract event 0x7635f1d87b47fba9f2b09e56eb4be75cca030e0cb179c1602ac9261d39a8f5c1. +// +// Solidity: event Propose(_owner address, _candidate address, _cap uint256) +func (_XDCValidator *XDCValidatorFilterer) WatchPropose(opts *bind.WatchOpts, sink chan<- *XDCValidatorPropose) (event.Subscription, error) { + + logs, sub, err := _XDCValidator.contract.WatchLogs(opts, "Propose") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(XDCValidatorPropose) + if err := _XDCValidator.contract.UnpackLog(event, "Propose", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// XDCValidatorResignIterator is returned from FilterResign and is used to iterate over the raw logs and unpacked data for Resign events raised by the XDCValidator contract. +type XDCValidatorResignIterator struct { + Event *XDCValidatorResign // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *XDCValidatorResignIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(XDCValidatorResign) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(XDCValidatorResign) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *XDCValidatorResignIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *XDCValidatorResignIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// XDCValidatorResign represents a Resign event raised by the XDCValidator contract. +type XDCValidatorResign struct { + Owner common.Address + Candidate common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterResign is a free log retrieval operation binding the contract event 0x4edf3e325d0063213a39f9085522994a1c44bea5f39e7d63ef61260a1e58c6d3. +// +// Solidity: event Resign(_owner address, _candidate address) +func (_XDCValidator *XDCValidatorFilterer) FilterResign(opts *bind.FilterOpts) (*XDCValidatorResignIterator, error) { + + logs, sub, err := _XDCValidator.contract.FilterLogs(opts, "Resign") + if err != nil { + return nil, err + } + return &XDCValidatorResignIterator{contract: _XDCValidator.contract, event: "Resign", logs: logs, sub: sub}, nil +} + +// WatchResign is a free log subscription operation binding the contract event 0x4edf3e325d0063213a39f9085522994a1c44bea5f39e7d63ef61260a1e58c6d3. +// +// Solidity: event Resign(_owner address, _candidate address) +func (_XDCValidator *XDCValidatorFilterer) WatchResign(opts *bind.WatchOpts, sink chan<- *XDCValidatorResign) (event.Subscription, error) { + + logs, sub, err := _XDCValidator.contract.WatchLogs(opts, "Resign") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(XDCValidatorResign) + if err := _XDCValidator.contract.UnpackLog(event, "Resign", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// XDCValidatorUnvoteIterator is returned from FilterUnvote and is used to iterate over the raw logs and unpacked data for Unvote events raised by the XDCValidator contract. +type XDCValidatorUnvoteIterator struct { + Event *XDCValidatorUnvote // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *XDCValidatorUnvoteIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(XDCValidatorUnvote) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(XDCValidatorUnvote) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *XDCValidatorUnvoteIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *XDCValidatorUnvoteIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// XDCValidatorUnvote represents a Unvote event raised by the XDCValidator contract. +type XDCValidatorUnvote struct { + Voter common.Address + Candidate common.Address + Cap *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterUnvote is a free log retrieval operation binding the contract event 0xaa0e554f781c3c3b2be110a0557f260f11af9a8aa2c64bc1e7a31dbb21e32fa2. +// +// Solidity: event Unvote(_voter address, _candidate address, _cap uint256) +func (_XDCValidator *XDCValidatorFilterer) FilterUnvote(opts *bind.FilterOpts) (*XDCValidatorUnvoteIterator, error) { + + logs, sub, err := _XDCValidator.contract.FilterLogs(opts, "Unvote") + if err != nil { + return nil, err + } + return &XDCValidatorUnvoteIterator{contract: _XDCValidator.contract, event: "Unvote", logs: logs, sub: sub}, nil +} + +// WatchUnvote is a free log subscription operation binding the contract event 0xaa0e554f781c3c3b2be110a0557f260f11af9a8aa2c64bc1e7a31dbb21e32fa2. +// +// Solidity: event Unvote(_voter address, _candidate address, _cap uint256) +func (_XDCValidator *XDCValidatorFilterer) WatchUnvote(opts *bind.WatchOpts, sink chan<- *XDCValidatorUnvote) (event.Subscription, error) { + + logs, sub, err := _XDCValidator.contract.WatchLogs(opts, "Unvote") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(XDCValidatorUnvote) + if err := _XDCValidator.contract.UnpackLog(event, "Unvote", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// XDCValidatorUploadedKYCIterator is returned from FilterUploadedKYC and is used to iterate over the raw logs and unpacked data for UploadedKYC events raised by the XDCValidator contract. +type XDCValidatorUploadedKYCIterator struct { + Event *XDCValidatorUploadedKYC // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *XDCValidatorUploadedKYCIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(XDCValidatorUploadedKYC) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(XDCValidatorUploadedKYC) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *XDCValidatorUploadedKYCIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *XDCValidatorUploadedKYCIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// XDCValidatorUploadedKYC represents a UploadedKYC event raised by the XDCValidator contract. +type XDCValidatorUploadedKYC struct { + Owner common.Address + KycHash string + Raw types.Log // Blockchain specific contextual infos +} + +// FilterUploadedKYC is a free log retrieval operation binding the contract event 0x949360d814b28a3b393a68909efe1fee120ee09cac30f360a0f80ab5415a611a. +// +// Solidity: event UploadedKYC(_owner address, kycHash string) +func (_XDCValidator *XDCValidatorFilterer) FilterUploadedKYC(opts *bind.FilterOpts) (*XDCValidatorUploadedKYCIterator, error) { + + logs, sub, err := _XDCValidator.contract.FilterLogs(opts, "UploadedKYC") + if err != nil { + return nil, err + } + return &XDCValidatorUploadedKYCIterator{contract: _XDCValidator.contract, event: "UploadedKYC", logs: logs, sub: sub}, nil +} + +// WatchUploadedKYC is a free log subscription operation binding the contract event 0x949360d814b28a3b393a68909efe1fee120ee09cac30f360a0f80ab5415a611a. +// +// Solidity: event UploadedKYC(_owner address, kycHash string) +func (_XDCValidator *XDCValidatorFilterer) WatchUploadedKYC(opts *bind.WatchOpts, sink chan<- *XDCValidatorUploadedKYC) (event.Subscription, error) { + + logs, sub, err := _XDCValidator.contract.WatchLogs(opts, "UploadedKYC") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(XDCValidatorUploadedKYC) + if err := _XDCValidator.contract.UnpackLog(event, "UploadedKYC", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// XDCValidatorVoteIterator is returned from FilterVote and is used to iterate over the raw logs and unpacked data for Vote events raised by the XDCValidator contract. +type XDCValidatorVoteIterator struct { + Event *XDCValidatorVote // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *XDCValidatorVoteIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(XDCValidatorVote) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(XDCValidatorVote) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *XDCValidatorVoteIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *XDCValidatorVoteIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// XDCValidatorVote represents a Vote event raised by the XDCValidator contract. +type XDCValidatorVote struct { + Voter common.Address + Candidate common.Address + Cap *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterVote is a free log retrieval operation binding the contract event 0x66a9138482c99e9baf08860110ef332cc0c23b4a199a53593d8db0fc8f96fbfc. +// +// Solidity: event Vote(_voter address, _candidate address, _cap uint256) +func (_XDCValidator *XDCValidatorFilterer) FilterVote(opts *bind.FilterOpts) (*XDCValidatorVoteIterator, error) { + + logs, sub, err := _XDCValidator.contract.FilterLogs(opts, "Vote") + if err != nil { + return nil, err + } + return &XDCValidatorVoteIterator{contract: _XDCValidator.contract, event: "Vote", logs: logs, sub: sub}, nil +} + +// WatchVote is a free log subscription operation binding the contract event 0x66a9138482c99e9baf08860110ef332cc0c23b4a199a53593d8db0fc8f96fbfc. +// +// Solidity: event Vote(_voter address, _candidate address, _cap uint256) +func (_XDCValidator *XDCValidatorFilterer) WatchVote(opts *bind.WatchOpts, sink chan<- *XDCValidatorVote) (event.Subscription, error) { + + logs, sub, err := _XDCValidator.contract.WatchLogs(opts, "Vote") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(XDCValidatorVote) + if err := _XDCValidator.contract.UnpackLog(event, "Vote", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// XDCValidatorWithdrawIterator is returned from FilterWithdraw and is used to iterate over the raw logs and unpacked data for Withdraw events raised by the XDCValidator contract. +type XDCValidatorWithdrawIterator struct { + Event *XDCValidatorWithdraw // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *XDCValidatorWithdrawIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(XDCValidatorWithdraw) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(XDCValidatorWithdraw) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *XDCValidatorWithdrawIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *XDCValidatorWithdrawIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// XDCValidatorWithdraw represents a Withdraw event raised by the XDCValidator contract. +type XDCValidatorWithdraw struct { + Owner common.Address + BlockNumber *big.Int + Cap *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterWithdraw is a free log retrieval operation binding the contract event 0xf279e6a1f5e320cca91135676d9cb6e44ca8a08c0b88342bcdb1144f6511b568. +// +// Solidity: event Withdraw(_owner address, _blockNumber uint256, _cap uint256) +func (_XDCValidator *XDCValidatorFilterer) FilterWithdraw(opts *bind.FilterOpts) (*XDCValidatorWithdrawIterator, error) { + + logs, sub, err := _XDCValidator.contract.FilterLogs(opts, "Withdraw") + if err != nil { + return nil, err + } + return &XDCValidatorWithdrawIterator{contract: _XDCValidator.contract, event: "Withdraw", logs: logs, sub: sub}, nil +} + +// WatchWithdraw is a free log subscription operation binding the contract event 0xf279e6a1f5e320cca91135676d9cb6e44ca8a08c0b88342bcdb1144f6511b568. +// +// Solidity: event Withdraw(_owner address, _blockNumber uint256, _cap uint256) +func (_XDCValidator *XDCValidatorFilterer) WatchWithdraw(opts *bind.WatchOpts, sink chan<- *XDCValidatorWithdraw) (event.Subscription, error) { + + logs, sub, err := _XDCValidator.contract.WatchLogs(opts, "Withdraw") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(XDCValidatorWithdraw) + if err := _XDCValidator.contract.UnpackLog(event, "Withdraw", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} diff --git a/contracts/subnet_validator/subnet_validator.go b/contracts/subnet_validator/subnet_validator.go new file mode 100644 index 000000000000..7f0bbd76c8bb --- /dev/null +++ b/contracts/subnet_validator/subnet_validator.go @@ -0,0 +1,67 @@ +// Copyright (c) 2018 XDPoSChain +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . + +package subnet_validator + +import ( + "math/big" + + "github.com/XinFinOrg/XDPoSChain/accounts/abi/bind" + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/contracts/subnet_validator/contract" +) + +type Validator struct { + *contract.XDCValidatorSession + contractBackend bind.ContractBackend +} + +func NewSubnetValidator(transactOpts *bind.TransactOpts, contractAddr common.Address, contractBackend bind.ContractBackend) (*Validator, error) { + validator, err := contract.NewXDCValidator(contractAddr, contractBackend) + if err != nil { + return nil, err + } + + return &Validator{ + &contract.XDCValidatorSession{ + Contract: validator, + TransactOpts: *transactOpts, + }, + contractBackend, + }, nil +} + +func DeploySubnetValidator(transactOpts *bind.TransactOpts, contractBackend bind.ContractBackend, validatorAddress []common.Address, caps []*big.Int, ownerAddress common.Address, grandMasterAddress []common.Address) (common.Address, *Validator, error) { + minDeposit := new(big.Int) + minDeposit.SetString("10000000000000000000000000", 10) + minVoterCap := new(big.Int) + minVoterCap.SetString("25000000000000000000000", 10) + // Deposit 50K XDC + // Min Voter Cap 10 XDC + // 150 masternodes + // Candidate Delay Withdraw 30 days = 1296000 blocks + // Voter Delay Withdraw 10 days = 432000 blocks + validatorAddr, _, _, err := contract.DeployXDCValidator(transactOpts, contractBackend, validatorAddress, caps, ownerAddress, minDeposit, minVoterCap, big.NewInt(18), big.NewInt(1296000), big.NewInt(432000), grandMasterAddress) + if err != nil { + return validatorAddr, nil, err + } + + validator, err := NewSubnetValidator(transactOpts, validatorAddr, contractBackend) + if err != nil { + return validatorAddr, nil, err + } + + return validatorAddr, validator, nil +} diff --git a/eth/bft/bft_handler.go b/eth/bft/bft_handler.go index 228621752315..a73a5eda5159 100644 --- a/eth/bft/bft_handler.go +++ b/eth/bft/bft_handler.go @@ -65,15 +65,26 @@ func (b *Bfter) InitEpochNumber() { func (b *Bfter) SetConsensusFuns(engine consensus.Engine) { e := engine.(*XDPoS.XDPoS) - b.broadcastCh = e.EngineV2.BroadcastCh + // TODO: proper inject consensus version + // b.broadcastCh = e.EngineV2.BroadcastCh + // b.consensus = ConsensusFns{ + // verifySyncInfo: e.EngineV2.VerifySyncInfoMessage, + // verifyVote: e.EngineV2.VerifyVoteMessage, + // verifyTimeout: e.EngineV2.VerifyTimeoutMessage, + + // voteHandler: e.EngineV2.VoteHandler, + // timeoutHandler: e.EngineV2.TimeoutHandler, + // syncInfoHandler: e.EngineV2.SyncInfoHandler, + // } + b.broadcastCh = e.EngineV2Subnet.BroadcastCh b.consensus = ConsensusFns{ - verifySyncInfo: e.EngineV2.VerifySyncInfoMessage, - verifyVote: e.EngineV2.VerifyVoteMessage, - verifyTimeout: e.EngineV2.VerifyTimeoutMessage, + verifySyncInfo: e.EngineV2Subnet.VerifySyncInfoMessage, + verifyVote: e.EngineV2Subnet.VerifyVoteMessage, + verifyTimeout: e.EngineV2Subnet.VerifyTimeoutMessage, - voteHandler: e.EngineV2.VoteHandler, - timeoutHandler: e.EngineV2.TimeoutHandler, - syncInfoHandler: e.EngineV2.SyncInfoHandler, + voteHandler: e.EngineV2Subnet.VoteHandler, + timeoutHandler: e.EngineV2Subnet.TimeoutHandler, + syncInfoHandler: e.EngineV2Subnet.SyncInfoHandler, } } diff --git a/params/config.go b/params/config.go index 556ac46478ae..abfed59ccca3 100644 --- a/params/config.go +++ b/params/config.go @@ -29,9 +29,10 @@ import ( ) const ( - ConsensusEngineVersion1 = "v1" - ConsensusEngineVersion2 = "v2" - Default = 0 + ConsensusEngineVersion1 = "v1" + ConsensusEngineVersion2 = "v2" + ConsensusEngineVersion2Subnet = "v2subnet" + Default = 0 ) var ( @@ -470,6 +471,7 @@ func (c *CliqueConfig) String() string { // XDPoSConfig is the consensus engine configs for delegated-proof-of-stake based sealing. type XDPoSConfig struct { + IsSubnet bool `json:"isSubnet"` // Is this subnet chain Period uint64 `json:"period"` // Number of seconds between blocks to enforce Epoch uint64 `json:"epoch"` // Epoch length to reset votes and checkpoint Reward uint64 `json:"reward"` // Block reward - unit Ether @@ -556,6 +558,7 @@ func (c *XDPoSConfig) Description(indent int) string { banner := "XDPoS\n" prefix := strings.Repeat(" ", indent) + banner += fmt.Sprintf("%s- IsSubnet: %v\n", prefix, c.IsSubnet) banner += fmt.Sprintf("%s- Period: %v\n", prefix, c.Period) banner += fmt.Sprintf("%s- Epoch: %v\n", prefix, c.Epoch) banner += fmt.Sprintf("%s- Reward: %v\n", prefix, c.Reward) @@ -625,6 +628,9 @@ func (c ExpTimeoutConfig) String() string { } func (c *XDPoSConfig) BlockConsensusVersion(num *big.Int) string { + if c.IsSubnet { + return ConsensusEngineVersion2Subnet + } if c.V2 != nil && c.V2.SwitchBlock != nil && num.Cmp(c.V2.SwitchBlock) > 0 { return ConsensusEngineVersion2 }