From 8e89efe6a9e3ffeefc74090af46f63bb04dceebe Mon Sep 17 00:00:00 2001 From: AnilChinchawale Date: Thu, 14 Mar 2019 15:59:10 +0530 Subject: [PATCH] File Updated | XDPoS Consensus updated --- consensus/XDPoS/XDPoS.go | 153 ++++++++---- consensus/XDPoS/snapshot_test.go | 404 ------------------------------- 2 files changed, 111 insertions(+), 446 deletions(-) delete mode 100644 consensus/XDPoS/snapshot_test.go diff --git a/consensus/XDPoS/XDPoS.go b/consensus/XDPoS/XDPoS.go index fbaaf12531..282f179782 100644 --- a/consensus/XDPoS/XDPoS.go +++ b/consensus/XDPoS/XDPoS.go @@ -13,15 +13,18 @@ // You should have received a copy of the GNU Lesser General Public License // along with this program. If not, see . -// Package XDPoS implements the XinFin-DPoS consensus engine. +// Package XDPoS implements the delegated-proof-of-stake consensus engine. package XDPoS import ( "bytes" + "encoding/json" "errors" "fmt" + "io/ioutil" "math/big" "math/rand" + "path/filepath" "strconv" "sync" "time" @@ -42,12 +45,12 @@ import ( "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" "github.com/hashicorp/golang-lru" - ) const ( - inmemorySnapshots = 128 // Number of recent vote snapshots to keep in memory - M2ByteLength = 4 + inmemorySnapshots = 128 // Number of recent vote snapshots to keep in memory + blockSignersCacheLimit = 9000 + M2ByteLength = 4 ) type Masternode struct { @@ -55,7 +58,7 @@ type Masternode struct { Stake *big.Int } -// XDPoS XinFin-DPoS protocol constants. +// XDPoS delegated-proof-of-stake protocol constants. var ( epochLength = uint64(900) // Default number of blocks after which to checkpoint and reset the pending votes @@ -146,7 +149,7 @@ var ( // backing account. //type SignerFn func(accounts.Account, []byte) ([]byte, error) -// sigHash returns the hash which is used as input for the XinFin-DPoS +// 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. // @@ -206,30 +209,31 @@ func ecrecover(header *types.Header, sigcache *lru.ARCCache) (common.Address, er return signer, nil } -// XDPoS is the XinFin-DPoS consensus engine proposed to support the +// XDPoS is the delegated-proof-of-stake consensus engine proposed to support the // Ethereum testnet following the Ropsten attacks. type XDPoS struct { config *params.XDPoSConfig // Consensus engine configuration parameters - db ethdb.Database // Database to store and retrieve snapshot checkpoints + db ethdb.Database // Database to store and retrieve snapshot checkpoints recents *lru.ARCCache // Snapshots for recent block to speed up reorgs signatures *lru.ARCCache // Signatures of recent blocks to speed up mining validatorSignatures *lru.ARCCache // Signatures of recent blocks to speed up mining verifiedHeaders *lru.ARCCache - rewards *lru.ARCCache proposals map[common.Address]bool // Current list of proposals we are pushing 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 - HookReward func(chain consensus.ChainReader, state *state.StateDB, header *types.Header) (error, map[string]interface{}) - HookPenalty func(chain consensus.ChainReader, blockNumberEpoc uint64) ([]common.Address, error) - HookValidator func(header *types.Header, signers []common.Address) ([]byte, error) - HookVerifyMNs func(header *types.Header, signers []common.Address) error + BlockSigners *lru.Cache + HookReward func(chain consensus.ChainReader, state *state.StateDB, header *types.Header) (error, map[string]interface{}) + HookPenalty func(chain consensus.ChainReader, blockNumberEpoc uint64) ([]common.Address, error) + HookPenaltyTIPSigning func(chain consensus.ChainReader, header *types.Header, candidate []common.Address) ([]common.Address, error) + HookValidator func(header *types.Header, signers []common.Address) ([]byte, error) + HookVerifyMNs func(header *types.Header, signers []common.Address) error } -// New creates a XDPoS XinFin-DPoS consensus engine with the initial +// New creates a XDPoS delegated-proof-of-stake consensus engine with the initial // signers set to the ones provided by the user. func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS { // Set any missing consensus parameters to their defaults @@ -238,19 +242,19 @@ func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS { conf.Epoch = epochLength } // Allocate the snapshot caches and create the engine + BlockSigners, _ := lru.New(blockSignersCacheLimit) recents, _ := lru.NewARC(inmemorySnapshots) signatures, _ := lru.NewARC(inmemorySnapshots) validatorSignatures, _ := lru.NewARC(inmemorySnapshots) verifiedHeaders, _ := lru.NewARC(inmemorySnapshots) - rewards, _ := lru.NewARC(inmemorySnapshots) return &XDPoS{ config: &conf, db: db, + BlockSigners: BlockSigners, recents: recents, signatures: signatures, verifiedHeaders: verifiedHeaders, validatorSignatures: validatorSignatures, - rewards: rewards, proposals: make(map[common.Address]bool), } } @@ -304,6 +308,9 @@ func (c *XDPoS) verifyHeaderWithCache(chain consensus.ChainReader, header *types // looking those up from the database. This is useful for concurrently verifying // a batch of new headers. func (c *XDPoS) verifyHeader(chain consensus.ChainReader, header *types.Header, parents []*types.Header, fullVerify bool) error { + if common.IsTestnet { + fullVerify = false + } if header.Number == nil { return errUnknownBlock } @@ -391,9 +398,15 @@ func (c *XDPoS) verifyCascadingFields(chain consensus.ChainReader, header *types } // If the block is a checkpoint block, verify the signer list if number%c.config.Epoch == 0 { + signers := snap.GetSigners() penPenalties := []common.Address{} - if c.HookPenalty != nil { - penPenalties, err = c.HookPenalty(chain, number) + if c.HookPenalty != nil || c.HookPenaltyTIPSigning != nil { + var err error = nil + if chain.Config().IsTIPSigning(header.Number) { + penPenalties, err = c.HookPenaltyTIPSigning(chain, header, signers) + } else { + penPenalties, err = c.HookPenalty(chain, number) + } if err != nil { return err } @@ -405,7 +418,6 @@ func (c *XDPoS) verifyCascadingFields(chain consensus.ChainReader, header *types return errInvalidCheckpointPenalties } } - signers := snap.GetSigners() signers = common.RemoveItemFromArray(signers, penPenalties) for i := 1; i <= common.LimitPenaltyEpoch; i++ { if number > uint64(i)*c.config.Epoch { @@ -480,10 +492,16 @@ func whoIsCreator(snap *Snapshot, header *types.Header) (common.Address, error) func (c *XDPoS) YourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (int, int, int, bool, error) { masternodes := c.GetMasternodes(chain, parent) + if common.IsTestnet { - // Only three mns for XDC testnet. - masternodes = masternodes[:3] + // Only three mns hard code for XDC testnet. + masternodes = []common.Address{ + common.HexToAddress("0xfFC679Dcdf444D2eEb0491A998E7902B411CcF20"), + common.HexToAddress("0xd76fd76F7101811726DCE9E43C2617706a4c45c8"), + common.HexToAddress("0x8A97753311aeAFACfd76a68Cf2e2a9808d3e65E8"), + } } + snap, err := c.GetSnapshot(chain, parent) if err != nil { log.Warn("Failed when trying to commit new work", "err", err) @@ -775,9 +793,15 @@ func (c *XDPoS) Prepare(chain consensus.ChainReader, header *types.Header) error } header.Extra = header.Extra[:extraVanity] masternodes := snap.GetSigners() - if number > 0 && number%c.config.Epoch == 0 { - if c.HookPenalty != nil { - penMasternodes, err := c.HookPenalty(chain, number) + if number >= c.config.Epoch && number%c.config.Epoch == 0 { + if c.HookPenalty != nil || c.HookPenaltyTIPSigning != nil { + var penMasternodes []common.Address = nil + var err error = nil + if chain.Config().IsTIPSigning(header.Number) { + penMasternodes, err = c.HookPenaltyTIPSigning(chain, header, masternodes) + } else { + penMasternodes, err = c.HookPenalty(chain, number) + } if err != nil { return err } @@ -785,7 +809,7 @@ func (c *XDPoS) Prepare(chain consensus.ChainReader, header *types.Header) error // penalize bad masternode(s) masternodes = common.RemoveItemFromArray(masternodes, penMasternodes) for _, address := range penMasternodes { - log.Debug("Penalty status", "address", address, "block number", number) + log.Debug("Penalty status", "address", address, "number", number) } header.Penalties = common.ExtractAddressToBytes(penMasternodes) } @@ -850,12 +874,22 @@ func (c *XDPoS) Finalize(chain consensus.ChainReader, header *types.Header, stat number := header.Number.Uint64() rCheckpoint := chain.Config().XDPoS.RewardCheckpoint + // _ = c.CacheData(header, txs, receipts) + if c.HookReward != nil && number%rCheckpoint == 0 { - err, rewardResults := c.HookReward(chain, state, header) + err, rewards := c.HookReward(chain, state, header) if err != nil { return nil, err } - c.rewards.Add(header.Hash(), rewardResults) + if len(common.StoreRewardFolder) > 0 { + data, err := json.Marshal(rewards) + if err == nil { + err = ioutil.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 @@ -887,7 +921,8 @@ func (c *XDPoS) Seal(chain consensus.ChainReader, block *types.Block, stop <-cha return nil, errUnknownBlock } // For 0-period chains, refuse to seal empty blocks (no reward but would spin sealing) - if c.config.Period == 0 && len(block.Transactions()) == 0 { + // checkpoint blocks have no tx + if c.config.Period == 0 && len(block.Transactions()) == 0 && number%c.config.Epoch != 0 { return nil, errWaitTransactions } // Don't hold the signer fields for the entire sealing procedure @@ -923,8 +958,7 @@ func (c *XDPoS) Seal(chain consensus.ChainReader, block *types.Block, stop <-cha if limit := uint64(2); number < limit || seen > number-limit { // Only take into account the non-epoch blocks if number%c.config.Epoch != 0 { - log.Info("Length of MasterNodes", "len(masternodes)", len(masternodes), "number", number, "limit", limit, "seen", seen, "recent", recent.String(), "snap.Recents", snap.Recents) - log.Info("Signed recently, must wait for others") + log.Info("Signed recently, must wait for others ", "len(masternodes)", len(masternodes), "number", number, "limit", limit, "seen", seen, "recent", recent.String(), "snap.Recents", snap.Recents) <-stop return nil, nil } @@ -1010,9 +1044,56 @@ func (c *XDPoS) GetMasternodesFromCheckpointHeader(preCheckpointHeader *types.He for i := 0; i < len(masternodes); i++ { copy(masternodes[i][:], preCheckpointHeader.Extra[extraVanity+i*common.AddressLength:]) } + return masternodes } +func (c *XDPoS) CacheData(header *types.Header, txs []*types.Transaction, receipts []*types.Receipt) []*types.Transaction { + signTxs := []*types.Transaction{} + for _, tx := range txs { + if tx.IsSigningTransaction() { + var b uint + for _, r := range receipts { + if r.TxHash == tx.Hash() { + if len(r.PostState) > 0 { + b = types.ReceiptStatusSuccessful + } else { + b = r.Status + } + break + } + } + + if b == types.ReceiptStatusFailed { + continue + } + + signTxs = append(signTxs, tx) + } + } + + log.Debug("Save tx signers to cache", "hash", header.Hash().String(), "number", header.Number, "len(txs)", len(signTxs)) + c.BlockSigners.Add(header.Hash(), signTxs) + + return signTxs +} + +func (c *XDPoS) CacheSigner(hash common.Hash, txs []*types.Transaction) []*types.Transaction { + signTxs := []*types.Transaction{} + for _, tx := range txs { + if tx.IsSigningTransaction() { + signTxs = append(signTxs, tx) + } + } + log.Debug("Save tx signers to cache", "hash", hash.String(), "len(txs)", len(signTxs)) + c.BlockSigners.Add(hash, signTxs) + return signTxs +} + +func (c *XDPoS) GetDb() ethdb.Database { + return c.db +} + // Extract validators from byte array. func RemovePenaltiesFromBlock(chain consensus.ChainReader, masternodes []common.Address, epochNumber uint64) []common.Address { if epochNumber <= 0 { @@ -1085,15 +1166,3 @@ func Hop(len, pre, cur int) int { return len - 1 } } - -func (c *XDPoS) GetRewards(hash common.Hash) map[string]interface{} { - rewards, ok := c.rewards.Get(hash) - if !ok { - return nil - } - return rewards.(map[string]interface{}) -} - -func (c *XDPoS) InsertRewards(hash common.Hash, rewards map[string]interface{}) { - c.rewards.Add(hash, rewards) -} diff --git a/consensus/XDPoS/snapshot_test.go b/consensus/XDPoS/snapshot_test.go deleted file mode 100644 index 56a2120839..0000000000 --- a/consensus/XDPoS/snapshot_test.go +++ /dev/null @@ -1,404 +0,0 @@ -// Copyright (c) 2018 Xinfin -// -// 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 XDPoS - -import ( - "bytes" - "crypto/ecdsa" - "math/big" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/params" -) - -type testerVote struct { - signer string - voted string - auth bool -} - -// testerAccountPool is a pool to maintain currently active tester accounts, -// mapped from textual names used in the tests below to actual Ethereum private -// keys capable of signing transactions. -type testerAccountPool struct { - accounts map[string]*ecdsa.PrivateKey -} - -func newTesterAccountPool() *testerAccountPool { - return &testerAccountPool{ - accounts: make(map[string]*ecdsa.PrivateKey), - } -} - -func (ap *testerAccountPool) sign(header *types.Header, signer string) { - // Ensure we have a persistent key for the signer - if ap.accounts[signer] == nil { - ap.accounts[signer], _ = crypto.GenerateKey() - } - // Sign the header and embed the signature in extra data - sig, _ := crypto.Sign(sigHash(header).Bytes(), ap.accounts[signer]) - copy(header.Extra[len(header.Extra)-65:], sig) -} - -func (ap *testerAccountPool) address(account string) common.Address { - // Ensure we have a persistent key for the account - if ap.accounts[account] == nil { - ap.accounts[account], _ = crypto.GenerateKey() - } - // Resolve and return the Ethereum address - return crypto.PubkeyToAddress(ap.accounts[account].PublicKey) -} - -// testerChainReader implements consensus.ChainReader to access the genesis -// block. All other methods and requests will panic. -type testerChainReader struct { - db ethdb.Database -} - -func (r *testerChainReader) Config() *params.ChainConfig { return params.AllXDPoSProtocolChanges } -func (r *testerChainReader) CurrentHeader() *types.Header { panic("not supported") } -func (r *testerChainReader) GetHeader(common.Hash, uint64) *types.Header { panic("not supported") } -func (r *testerChainReader) GetBlock(common.Hash, uint64) *types.Block { panic("not supported") } -func (r *testerChainReader) GetHeaderByHash(common.Hash) *types.Header { panic("not supported") } -func (r *testerChainReader) GetHeaderByNumber(number uint64) *types.Header { - if number == 0 { - return core.GetHeader(r.db, core.GetCanonicalHash(r.db, 0), 0) - } - panic("not supported") -} - -// Tests that voting is evaluated correctly for various simple and complex scenarios. -func TestVoting(t *testing.T) { - // Define the various voting scenarios to test - tests := []struct { - epoch uint64 - signers []string - votes []testerVote - results []string - }{ - { - // Single signer, no votes cast - signers: []string{"A"}, - votes: []testerVote{{signer: "A"}}, - results: []string{"A"}, - }, { - // Single signer, voting to add two others (only accept first, second needs 2 votes) - signers: []string{"A"}, - votes: []testerVote{ - {signer: "A", voted: "B", auth: true}, - {signer: "B"}, - {signer: "A", voted: "C", auth: true}, - }, - results: []string{"A", "B"}, - }, { - // Two signers, voting to add three others (only accept first two, third needs 3 votes already) - signers: []string{"A", "B"}, - votes: []testerVote{ - {signer: "A", voted: "C", auth: true}, - {signer: "B", voted: "C", auth: true}, - {signer: "A", voted: "D", auth: true}, - {signer: "B", voted: "D", auth: true}, - {signer: "C"}, - {signer: "A", voted: "E", auth: true}, - {signer: "B", voted: "E", auth: true}, - }, - results: []string{"A", "B", "C", "D"}, - }, { - // Single signer, dropping itself (weird, but one less cornercase by explicitly allowing this) - signers: []string{"A"}, - votes: []testerVote{ - {signer: "A", voted: "A", auth: false}, - }, - results: []string{}, - }, { - // Two signers, actually needing mutual consent to drop either of them (not fulfilled) - signers: []string{"A", "B"}, - votes: []testerVote{ - {signer: "A", voted: "B", auth: false}, - }, - results: []string{"A", "B"}, - }, { - // Two signers, actually needing mutual consent to drop either of them (fulfilled) - signers: []string{"A", "B"}, - votes: []testerVote{ - {signer: "A", voted: "B", auth: false}, - {signer: "B", voted: "B", auth: false}, - }, - results: []string{"A"}, - }, { - // Three signers, two of them deciding to drop the third - signers: []string{"A", "B", "C"}, - votes: []testerVote{ - {signer: "A", voted: "C", auth: false}, - {signer: "B", voted: "C", auth: false}, - }, - results: []string{"A", "B"}, - }, { - // Four signers, consensus of two not being enough to drop anyone - signers: []string{"A", "B", "C", "D"}, - votes: []testerVote{ - {signer: "A", voted: "C", auth: false}, - {signer: "B", voted: "C", auth: false}, - }, - results: []string{"A", "B", "C", "D"}, - }, { - // Four signers, consensus of three already being enough to drop someone - signers: []string{"A", "B", "C", "D"}, - votes: []testerVote{ - {signer: "A", voted: "D", auth: false}, - {signer: "B", voted: "D", auth: false}, - {signer: "C", voted: "D", auth: false}, - }, - results: []string{"A", "B", "C"}, - }, { - // Authorizations are counted once per signer per target - signers: []string{"A", "B"}, - votes: []testerVote{ - {signer: "A", voted: "C", auth: true}, - {signer: "B"}, - {signer: "A", voted: "C", auth: true}, - {signer: "B"}, - {signer: "A", voted: "C", auth: true}, - }, - results: []string{"A", "B"}, - }, { - // Authorizing multiple accounts concurrently is permitted - signers: []string{"A", "B"}, - votes: []testerVote{ - {signer: "A", voted: "C", auth: true}, - {signer: "B"}, - {signer: "A", voted: "D", auth: true}, - {signer: "B"}, - {signer: "A"}, - {signer: "B", voted: "D", auth: true}, - {signer: "A"}, - {signer: "B", voted: "C", auth: true}, - }, - results: []string{"A", "B", "C", "D"}, - }, { - // Deauthorizations are counted once per signer per target - signers: []string{"A", "B"}, - votes: []testerVote{ - {signer: "A", voted: "B", auth: false}, - {signer: "B"}, - {signer: "A", voted: "B", auth: false}, - {signer: "B"}, - {signer: "A", voted: "B", auth: false}, - }, - results: []string{"A", "B"}, - }, { - // Deauthorizing multiple accounts concurrently is permitted - signers: []string{"A", "B", "C", "D"}, - votes: []testerVote{ - {signer: "A", voted: "C", auth: false}, - {signer: "B"}, - {signer: "C"}, - {signer: "A", voted: "D", auth: false}, - {signer: "B"}, - {signer: "C"}, - {signer: "A"}, - {signer: "B", voted: "D", auth: false}, - {signer: "C", voted: "D", auth: false}, - {signer: "A"}, - {signer: "B", voted: "C", auth: false}, - }, - results: []string{"A", "B"}, - }, { - // Votes from deauthorized signers are discarded immediately (deauth votes) - signers: []string{"A", "B", "C"}, - votes: []testerVote{ - {signer: "C", voted: "B", auth: false}, - {signer: "A", voted: "C", auth: false}, - {signer: "B", voted: "C", auth: false}, - {signer: "A", voted: "B", auth: false}, - }, - results: []string{"A", "B"}, - }, { - // Votes from deauthorized signers are discarded immediately (auth votes) - signers: []string{"A", "B", "C"}, - votes: []testerVote{ - {signer: "C", voted: "B", auth: false}, - {signer: "A", voted: "C", auth: false}, - {signer: "B", voted: "C", auth: false}, - {signer: "A", voted: "B", auth: false}, - }, - results: []string{"A", "B"}, - }, { - // Cascading changes are not allowed, only the account being voted on may change - signers: []string{"A", "B", "C", "D"}, - votes: []testerVote{ - {signer: "A", voted: "C", auth: false}, - {signer: "B"}, - {signer: "C"}, - {signer: "A", voted: "D", auth: false}, - {signer: "B", voted: "C", auth: false}, - {signer: "C"}, - {signer: "A"}, - {signer: "B", voted: "D", auth: false}, - {signer: "C", voted: "D", auth: false}, - }, - results: []string{"A", "B", "C"}, - }, { - // Changes reaching consensus out of bounds (via a deauth) execute on touch - signers: []string{"A", "B", "C", "D"}, - votes: []testerVote{ - {signer: "A", voted: "C", auth: false}, - {signer: "B"}, - {signer: "C"}, - {signer: "A", voted: "D", auth: false}, - {signer: "B", voted: "C", auth: false}, - {signer: "C"}, - {signer: "A"}, - {signer: "B", voted: "D", auth: false}, - {signer: "C", voted: "D", auth: false}, - {signer: "A"}, - {signer: "C", voted: "C", auth: true}, - }, - results: []string{"A", "B"}, - }, { - // Changes reaching consensus out of bounds (via a deauth) may go out of consensus on first touch - signers: []string{"A", "B", "C", "D"}, - votes: []testerVote{ - {signer: "A", voted: "C", auth: false}, - {signer: "B"}, - {signer: "C"}, - {signer: "A", voted: "D", auth: false}, - {signer: "B", voted: "C", auth: false}, - {signer: "C"}, - {signer: "A"}, - {signer: "B", voted: "D", auth: false}, - {signer: "C", voted: "D", auth: false}, - {signer: "A"}, - {signer: "B", voted: "C", auth: true}, - }, - results: []string{"A", "B", "C"}, - }, { - // Ensure that pending votes don't survive authorization status changes. This - // corner case can only appear if a signer is quickly added, removed and then - // readded (or the inverse), while one of the original voters dropped. If a - // past vote is left cached in the system somewhere, this will interfere with - // the final signer outcome. - signers: []string{"A", "B", "C", "D", "E"}, - votes: []testerVote{ - {signer: "A", voted: "F", auth: true}, // Authorize F, 3 votes needed - {signer: "B", voted: "F", auth: true}, - {signer: "C", voted: "F", auth: true}, - {signer: "D", voted: "F", auth: false}, // Deauthorize F, 4 votes needed (leave A's previous vote "unchanged") - {signer: "E", voted: "F", auth: false}, - {signer: "B", voted: "F", auth: false}, - {signer: "C", voted: "F", auth: false}, - {signer: "D", voted: "F", auth: true}, // Almost authorize F, 2/3 votes needed - {signer: "E", voted: "F", auth: true}, - {signer: "B", voted: "A", auth: false}, // Deauthorize A, 3 votes needed - {signer: "C", voted: "A", auth: false}, - {signer: "D", voted: "A", auth: false}, - {signer: "B", voted: "F", auth: true}, // Finish authorizing F, 3/3 votes needed - }, - results: []string{"B", "C", "D", "E", "F"}, - }, { - // Epoch transitions reset all votes to allow chain checkpointing - epoch: 3, - signers: []string{"A", "B"}, - votes: []testerVote{ - {signer: "A", voted: "C", auth: true}, - {signer: "B"}, - {signer: "A"}, // Checkpoint block, (don't vote here, it's validated outside of snapshots) - {signer: "B", voted: "C", auth: true}, - }, - results: []string{"A", "B"}, - }, - } - // Run through the scenarios and test them - for i, tt := range tests { - // Create the account pool and generate the initial set of signers - accounts := newTesterAccountPool() - - signers := make([]common.Address, len(tt.signers)) - for j, signer := range tt.signers { - signers[j] = accounts.address(signer) - } - for j := 0; j < len(signers); j++ { - for k := j + 1; k < len(signers); k++ { - if bytes.Compare(signers[j][:], signers[k][:]) > 0 { - signers[j], signers[k] = signers[k], signers[j] - } - } - } - // Create the genesis block with the initial set of signers - genesis := &core.Genesis{ - ExtraData: make([]byte, extraVanity+common.AddressLength*len(signers)+extraSeal), - } - for j, signer := range signers { - copy(genesis.ExtraData[extraVanity+j*common.AddressLength:], signer[:]) - } - // Create a pristine blockchain with the genesis injected - db, _ := ethdb.NewMemDatabase() - genesis.Commit(db) - - // Assemble a chain of headers from the cast votes - headers := make([]*types.Header, len(tt.votes)) - for j, vote := range tt.votes { - headers[j] = &types.Header{ - Number: big.NewInt(int64(j) + 1), - Time: big.NewInt(int64(j) * int64(blockPeriod)), - Coinbase: accounts.address(vote.voted), - Extra: make([]byte, extraVanity+extraSeal), - } - if j > 0 { - headers[j].ParentHash = headers[j-1].Hash() - } - if vote.auth { - copy(headers[j].Nonce[:], nonceAuthVote) - } - accounts.sign(headers[j], vote.signer) - } - // Pass all the headers through XDPoS and ensure tallying succeeds - head := headers[len(headers)-1] - - snap, err := New(¶ms.XDPoSConfig{Epoch: tt.epoch}, db).snapshot(&testerChainReader{db: db}, head.Number.Uint64(), head.Hash(), headers) - if err != nil { - t.Errorf("test %d: failed to create voting snapshot: %v", i, err) - continue - } - // Verify the final list of signers against the expected ones - signers = make([]common.Address, len(tt.results)) - for j, signer := range tt.results { - signers[j] = accounts.address(signer) - } - for j := 0; j < len(signers); j++ { - for k := j + 1; k < len(signers); k++ { - if bytes.Compare(signers[j][:], signers[k][:]) > 0 { - signers[j], signers[k] = signers[k], signers[j] - } - } - } - result := snap.signers() - if len(result) != len(signers) { - t.Errorf("test %d: signers mismatch: have %x, want %x", i, result, signers) - continue - } - for j := 0; j < len(result); j++ { - if !bytes.Equal(result[j][:], signers[j][:]) { - t.Errorf("test %d, signer %d: signer mismatch: have %x, want %x", i, j, result[j], signers[j]) - } - } - } -} \ No newline at end of file