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