v2 miner function implementation and happy path (#22)

* New struct in consensus/XDPoS/utils/types.go, util functions, and test. (#14)

* define vote, timeout, sync info, qc, tc, extra fields in types.go, add test in types_test.go

* add json tag in types.go, refine encoder decoder of extra fields

* refactor types.go utils.go

* re-write types, comments

* add Hash SigHash for types, and tests

* define Round type

* remove unnecessary logs

* add v2 engine functions placeholder

* typo fix on the consensus v2 function placeholders

* add countdown timer

* make initilised private to countdown

* add v2 specific config struct

* rename some config variables

* Implement BFT Message receiver (#13)

* fix or skip tests due to PR-136 changes

* add bft receiver functions

* add bft receiver functions

* rename tc to TimeoutCert

* implement more functions

* New struct in consensus/XDPoS/utils/types.go, util functions, and test. (#14)

* define vote, timeout, sync info, qc, tc, extra fields in types.go, add test in types_test.go

* add json tag in types.go, refine encoder decoder of extra fields

* refactor types.go utils.go

* re-write types, comments

* add Hash SigHash for types, and tests

* define Round type

* remove unnecessary logs

* add temp functions

* add v2 engine functions placeholder

* typo fix on the consensus v2 function placeholders

* add countdown timer

* make initilised private to countdown

* push verify function

* add test on receiving vote

* revert type change

* add async on broadcast function

* add quit initial

* fix test

Co-authored-by: Jianrong <wjrjerome@gmail.com>
Co-authored-by: wgr523 <wgr523@gmail.com>

* generate and verify timeout message

* Consensus V2 variable, timeout pool (#19)

* fill in XDPoS_v2 variables and processQC/TC

* add timeout pool, refine engine variables

* refactor type functions

* solve a small pointer bug

* create general pool and its test, refine engine

* refine pool, add xdpos v2 config cert threshold

* refine config

* vote and timeout handlers

* fix pool test

* bft miner preparation

* review comment improvement

* update

* relocate tests

* add and remove comment

* fix the syntax error

* update network layer and add handler functions (#23)

* update network layer and add handler functions

* fix test syntax error

* add ProcessQC implementation

* add ProcessQC tests

* add snapshot test

* add wait qc process

* remove testing files

* add route snapshot

* fix merge issue

* add default v2 behaviour (#24)

* add v2 ecrecover functions and refactor test

* fix all the tests

* put minimun lock variable

* debugging prepare and seal v2 blocks

* Trigger proposeBlockHandler after v2 block received and verified in fetcher

* skip snapshot apply related tests

* update test check

* rename bfter to bft handler and ignore normal behviour

* fix bugs during local 4 node run

* fix test

* fix sync info test

* fix bugs during local 4 node run

* rebase and fix bug

* remove hook validators function"

Co-authored-by: wgr523 <wgr523@gmail.com>
Co-authored-by: Jianrong <wjrjerome@gmail.com>
This commit is contained in:
Liam 2021-12-06 15:07:14 +11:00 committed by Jianrong
parent 6e13b4d6a2
commit 6c5fe34615
30 changed files with 1286 additions and 275 deletions

View file

@ -175,7 +175,7 @@ func (x *XDPoS) VerifySeal(chain consensus.ChainReader, header *types.Header) er
func (x *XDPoS) Prepare(chain consensus.ChainReader, header *types.Header) error {
switch x.config.BlockConsensusVersion(header.Number) {
case params.ConsensusEngineVersion2:
return nil
return x.EngineV2.Prepare(chain, header)
default: // Default "v1"
return x.EngineV1.Prepare(chain, header)
}
@ -186,7 +186,7 @@ func (x *XDPoS) Prepare(chain consensus.ChainReader, header *types.Header) error
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.ConsensusEngineVersion2:
return nil, nil
return x.EngineV2.Finalize(chain, header, state, parentState, txs, uncles, receipts)
default: // Default "v1"
return x.EngineV1.Finalize(chain, header, state, parentState, txs, uncles, receipts)
}
@ -197,7 +197,7 @@ func (x *XDPoS) Finalize(chain consensus.ChainReader, header *types.Header, stat
func (x *XDPoS) Seal(chain consensus.ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) {
switch x.config.BlockConsensusVersion(block.Number()) {
case params.ConsensusEngineVersion2:
return nil, nil
return x.EngineV2.Seal(chain, block, stop)
default: // Default "v1"
return x.EngineV1.Seal(chain, block, stop)
}
@ -209,12 +209,21 @@ func (x *XDPoS) Seal(chain consensus.ChainReader, block *types.Block, stop <-cha
func (x *XDPoS) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int {
switch x.config.BlockConsensusVersion(parent.Number) {
case params.ConsensusEngineVersion2:
return nil
return x.EngineV2.CalcDifficulty(chain, time, parent)
default: // Default "v1"
return x.EngineV1.CalcDifficulty(chain, time, parent)
}
}
func (x *XDPoS) HandleProposedBlock(chain consensus.ChainReader, header *types.Header) error {
switch x.config.BlockConsensusVersion(header.Number) {
case params.ConsensusEngineVersion2:
return x.EngineV2.ProposedBlockHandler(chain, header)
default: // Default "v1"
return nil
}
}
/*
XDC specific methods
*/
@ -243,7 +252,7 @@ func (x *XDPoS) IsAuthorisedAddress(header *types.Header, chain consensus.ChainR
func (x *XDPoS) GetMasternodes(chain consensus.ChainReader, header *types.Header) []common.Address {
switch x.config.BlockConsensusVersion(header.Number) {
case params.ConsensusEngineVersion2:
return []common.Address{}
return x.EngineV2.GetMasternodes(chain, header)
default: // Default "v1"
return x.EngineV1.GetMasternodes(chain, header)
}
@ -251,6 +260,8 @@ func (x *XDPoS) GetMasternodes(chain consensus.ChainReader, header *types.Header
func (x *XDPoS) YourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (int, int, int, bool, error) {
switch x.config.BlockConsensusVersion(parent.Number) {
case params.ConsensusEngineVersion2:
return x.EngineV2.YourTurn(chain, parent, signer)
default: // Default "v1"
return x.EngineV1.YourTurn(chain, parent, signer)
}
@ -258,7 +269,7 @@ func (x *XDPoS) YourTurn(chain consensus.ChainReader, parent *types.Header, sign
func (x *XDPoS) GetValidator(creator common.Address, chain consensus.ChainReader, header *types.Header) (common.Address, error) {
switch x.config.BlockConsensusVersion(header.Number) {
default: // Default "v1"
default: // Default "v1", v2 does not need this function
return x.EngineV1.GetValidator(creator, chain, header)
}
}
@ -307,6 +318,13 @@ func (x *XDPoS) GetDb() ethdb.Database {
func (x *XDPoS) GetSnapshot(chain consensus.ChainReader, header *types.Header) (*utils.PublicApiSnapshot, error) {
switch x.config.BlockConsensusVersion(header.Number) {
case params.ConsensusEngineVersion2:
sp, err := x.EngineV2.GetSnapshot(chain, header)
return &utils.PublicApiSnapshot{
Number: sp.Number,
Hash: sp.Hash,
Signers: sp.MasterNodes,
}, err
default: // Default "v1"
sp, err := x.EngineV1.GetSnapshot(chain, header)
// Convert to a standard PublicApiSnapshot type, otherwise it's a breaking change to API

View file

@ -28,31 +28,6 @@ import (
lru "github.com/hashicorp/golang-lru"
)
// ecrecover extracts the Ethereum account address from a signed header.
func ecrecover(header *types.Header, sigcache *lru.ARCCache) (common.Address, error) {
// If the signature's already cached, return that
hash := header.Hash()
if address, known := sigcache.Get(hash); known {
return address.(common.Address), nil
}
// Retrieve the signature from the header extra-data
if len(header.Extra) < utils.ExtraSeal {
return common.Address{}, utils.ErrMissingSignature
}
signature := header.Extra[len(header.Extra)-utils.ExtraSeal:]
// Recover the public key and the Ethereum address
pubkey, err := crypto.Ecrecover(utils.SigHash(header).Bytes(), signature)
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
}
// XDPoS is the delegated-proof-of-stake consensus engine proposed to support the
// Ethereum testnet following the Ropsten attacks.
type XDPoS_v1 struct {
@ -106,7 +81,7 @@ func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS_v1 {
// Author implements consensus.Engine, returning the Ethereum address recovered
// from the signature in the header's extra-data section.
func (x *XDPoS_v1) Author(header *types.Header) (common.Address, error) {
return ecrecover(header, x.signatures)
return utils.Ecrecover(header, x.signatures)
}
// VerifyHeader checks whether a header conforms to the consensus rules.
@ -384,7 +359,7 @@ func whoIsCreator(snap *SnapshotV1, header *types.Header) (common.Address, error
if header.Number.Uint64() == 0 {
return common.Address{}, errors.New("Don't take block 0")
}
m, err := ecrecover(header, snap.sigcache)
m, err := utils.Ecrecover(header, snap.sigcache)
if err != nil {
return common.Address{}, err
}
@ -549,7 +524,7 @@ func (x *XDPoS_v1) verifySeal(chain consensus.ChainReader, header *types.Header,
}
// Resolve the authorization key and check against signers
creator, err := ecrecover(header, x.signatures)
creator, err := utils.Ecrecover(header, x.signatures)
if err != nil {
return err
}
@ -643,7 +618,7 @@ func (x *XDPoS_v1) GetValidator(creator common.Address, chain consensus.ChainRea
return common.Address{}, fmt.Errorf("couldn't find checkpoint header")
}
}
m, err := GetM1M2FromCheckpointHeader(cpHeader, header, chain.Config())
m, err := utils.GetM1M2FromCheckpointHeader(cpHeader, header, chain.Config())
if err != nil {
return common.Address{}, err
}
@ -911,7 +886,7 @@ func (x *XDPoS_v1) calcDifficulty(chain consensus.ChainReader, parent *types.Hea
}
func (x *XDPoS_v1) RecoverSigner(header *types.Header) (common.Address, error) {
return ecrecover(header, x.signatures)
return utils.Ecrecover(header, x.signatures)
}
func (x *XDPoS_v1) RecoverValidator(header *types.Header) (common.Address, error) {
@ -979,21 +954,6 @@ func GetMasternodesFromCheckpointHeader(checkpointHeader *types.Header) []common
return masternodes
}
// Get m2 list from checkpoint block.
func GetM1M2FromCheckpointHeader(checkpointHeader *types.Header, currentHeader *types.Header, config *params.ChainConfig) (map[common.Address]common.Address, error) {
if checkpointHeader.Number.Uint64()%common.EpocBlockRandomize != 0 {
return nil, errors.New("This block is not checkpoint block epoc.")
}
// Get signers from this block.
masternodes := GetMasternodesFromCheckpointHeader(checkpointHeader)
validators := utils.ExtractValidatorsFromBytes(checkpointHeader.Validators)
m1m2, _, err := utils.GetM1M2(masternodes, validators, currentHeader, config)
if err != nil {
return map[common.Address]common.Address{}, err
}
return m1m2, nil
}
func (x *XDPoS_v1) getSignersFromContract(chain consensus.ChainReader, checkpointHeader *types.Header) ([]common.Address, error) {
startGapBlockHeader := checkpointHeader
number := checkpointHeader.Number.Uint64()

View file

@ -187,7 +187,7 @@ func (s *SnapshotV1) apply(headers []*types.Header) (*SnapshotV1, error) {
delete(snap.Recents, number-limit)
}
// Resolve the authorization key and check against signers
signer, err := ecrecover(header, s.sigcache)
signer, err := utils.Ecrecover(header, s.sigcache)
if err != nil {
return nil, err
}

View file

@ -1,8 +1,12 @@
package engine_v2
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"math/big"
"path/filepath"
"sync"
"time"
@ -12,25 +16,30 @@ import (
"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/crypto"
"github.com/XinFinOrg/XDPoSChain/ethdb"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/params"
lru "github.com/hashicorp/golang-lru"
)
type XDPoS_v2 struct {
config *params.XDPoSConfig // Consensus engine configuration parameters
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
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{}
timeoutWorker *countdown.CountdownTimer // Timer to generate broadcast timeout msg if threashold reached
lock sync.RWMutex // Protects the currentRound fields etc
timeoutPool *utils.Pool
votePool *utils.Pool
currentRound utils.Round
@ -40,23 +49,33 @@ type XDPoS_v2 struct {
lockQuorumCert *utils.QuorumCert
highestTimeoutCert *utils.TimeoutCert
highestCommitBlock *utils.BlockInfo
HookReward func(chain consensus.ChainReader, state *state.StateDB, parentState *state.StateDB, header *types.Header) (error, map[string]interface{})
}
func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS_v2 {
// Setup Timer
duration := time.Duration(config.V2.TimeoutWorkerDuration) * time.Millisecond
duration := time.Duration(config.V2.TimeoutWorkerDuration) * time.Second
timer := countdown.NewCountDown(duration)
timeoutPool := utils.NewPool(config.V2.CertThreshold)
recents, _ := lru.NewARC(utils.InmemorySnapshots)
signatures, _ := lru.NewARC(utils.InmemorySnapshots)
votePool := utils.NewPool(config.V2.CertThreshold)
engine := &XDPoS_v2{
config: config,
db: db,
timeoutWorker: timer,
BroadcastCh: make(chan interface{}),
timeoutPool: timeoutPool,
votePool: votePool,
highestTimeoutCert: &utils.TimeoutCert{},
highestQuorumCert: &utils.QuorumCert{},
config: config,
db: db,
signatures: signatures,
recents: recents,
timeoutWorker: timer,
BroadcastCh: make(chan interface{}),
timeoutPool: timeoutPool,
votePool: votePool,
highestTimeoutCert: nil,
highestQuorumCert: nil,
highestVotedRound: utils.Round(0),
}
// Add callback to the timer
@ -65,24 +84,118 @@ func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS_v2 {
return engine
}
/*
Testing tools
*/
func (x *XDPoS_v2) SetNewRoundFaker(newRound utils.Round, resetTimer bool) {
// 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 {
// Verify mined block parent matches highest QC
x.lock.Lock()
defer x.lock.Unlock()
// Reset a bunch of things
if resetTimer {
x.timeoutWorker.Reset()
// Check header if it is the first consensus v2 block, if so, assign initial values to current round and highestQC
if header.Number.Cmp(big.NewInt(0).Add(x.config.XDPoSV2Block, big.NewInt(1))) == 0 {
log.Info("[Prepare] Initilising highest QC for consensus v2 first block", "Block Num", header.Number.String(), "BlockHash", header.Hash())
// Generate new parent blockInfo and put it into QC
parentBlockInfo := &utils.BlockInfo{
Hash: header.ParentHash,
Round: utils.Round(0),
Number: big.NewInt(0).Sub(header.Number, big.NewInt(1)),
}
quorumCert := &utils.QuorumCert{
ProposedBlockInfo: parentBlockInfo,
Signatures: nil,
}
x.currentRound = 1
x.highestQuorumCert = quorumCert
}
x.currentRound = newRound
currentRound := x.currentRound
highestQC := x.highestQuorumCert
x.lock.Unlock()
//parentRound := highestQC.ProposedBlockInfo.Round
if (highestQC == nil) || (header.ParentHash != highestQC.ProposedBlockInfo.Hash) {
return consensus.ErrNotReadyToPropose
}
extra := utils.ExtraFields_v2{
Round: currentRound,
QuorumCert: highestQC,
}
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
}
// Set the correct difficulty
header.Difficulty = x.calcDifficulty(chain, parent, x.signer)
log.Debug("CalcDifficulty ", "number", header.Number, "difficulty", header.Difficulty)
// TODO: previous round should sit on previous Epoch and x.currentRound should >= Epoch number
if number%x.config.Epoch == 0 {
snap, err := x.snapshot(chain, number-1, header.ParentHash, nil)
if err != nil {
return err
}
masternodes := snap.GetMasterNodes()
//TODO: remove penalty nodes and add comeback nodes
for _, v := range masternodes {
header.Validators = append(header.Validators, v[:]...)
}
}
extraBytes, err := extra.EncodeToBytes()
if err != nil {
return err
}
header.Extra = extraBytes
// Mix digest is reserved for now, set to empty
header.MixDigest = common.Hash{}
// Ensure the timestamp has the correct delay
// TODO: if timestamp > current time, how to deal with future timestamp
header.Time = new(big.Int).Add(parent.Time, new(big.Int).SetUint64(x.config.Period))
if header.Time.Int64() < time.Now().Unix() {
header.Time = big.NewInt(time.Now().Unix())
}
return nil
}
// Utils for test to check currentRound value
func (x *XDPoS_v2) GetProperties() (utils.Round, *utils.QuorumCert, *utils.QuorumCert) {
x.lock.Lock()
defer x.lock.Unlock()
return x.currentRound, x.lockQuorumCert, x.highestQuorumCert
// 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
number := header.Number.Uint64()
rCheckpoint := chain.Config().XDPoS.RewardCheckpoint
// _ = c.CacheData(header, txs, receipts)
if x.HookReward != nil && number%rCheckpoint == 0 {
err, rewards := 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 = 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
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, txs, nil, receipts), nil
}
// Authorize injects a private key into the consensus engine to mint new blocks with.
@ -95,13 +208,241 @@ func (x *XDPoS_v2) Authorize(signer common.Address, signFn clique.SignerFn) {
}
func (x *XDPoS_v2) Author(header *types.Header) (common.Address, error) {
return common.Address{}, nil
return utils.EcrecoverV2(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
}
// For 0-period chains, refuse to seal empty blocks (no reward but would spin sealing)
// checkpoint blocks have no tx
if x.config.Period == 0 && len(block.Transactions()) == 0 && number%x.config.Epoch != 0 {
return nil, utils.ErrWaitTransactions
}
// Don't hold the signer fields for the entire sealing procedure
x.signLock.RLock()
signer, signFn := x.signer, x.signFn
x.signLock.RUnlock()
// Bail out if we're unauthorized to sign a block
snap, err := x.snapshot(chain, number-1, header.ParentHash, nil)
if err != nil {
return nil, err
}
masternodes := x.GetMasternodes(chain, header)
if _, authorized := snap.MasterNodes[signer]; !authorized {
valid := false
for _, m := range masternodes {
if m == signer {
valid = true
break
}
}
if !valid {
return nil, utils.ErrUnauthorized
}
}
select {
case <-stop:
return nil, nil
default:
}
// Sign all the things!
signature, err := signFn(accounts.Account{Address: signer}, utils.SigHashV2(header).Bytes())
if err != nil {
return nil, err
}
header.Validator = signature
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)
}
// TODO: what should be new difficulty
func (x *XDPoS_v2) calcDifficulty(chain consensus.ChainReader, parent *types.Header, signer common.Address) *big.Int {
// TODO: The difference of round number between parent round and current round
return big.NewInt(1)
}
// Copy from v1
func (x *XDPoS_v2) YourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (int, int, int, bool, error) {
snap, err := x.GetSnapshot(chain, parent)
if err != nil {
log.Error("[YourTurn] Failed while getting snapshot", "parentHash", parent.Hash(), "err", err)
return 0, -1, -1, false, err
}
masternodes := x.GetMasternodes(chain, parent)
if len(masternodes) == 0 {
return 0, -1, -1, false, errors.New("Masternodes not found")
}
pre := common.Address{}
// masternode[0] has chance to create block 1
preIndex := -1
if parent.Number.Uint64() != 0 {
pre, err = whoIsCreator(snap, parent)
if err != nil {
return 0, 0, 0, false, err
}
preIndex = utils.Position(masternodes, pre)
}
curIndex := utils.Position(masternodes, signer)
if signer == x.signer {
log.Debug("Masternodes cycle info", "number of masternodes", len(masternodes), "previous", pre, "position", preIndex, "current", signer, "position", curIndex)
}
for i, s := range masternodes {
log.Debug("Masternode:", "index", i, "address", s.String())
}
if (preIndex+1)%len(masternodes) == curIndex {
return len(masternodes), preIndex, curIndex, true, nil
}
return len(masternodes), preIndex, curIndex, false, nil
}
// Copy from v1
func whoIsCreator(snap *SnapshotV2, header *types.Header) (common.Address, error) {
if header.Number.Uint64() == 0 {
return common.Address{}, errors.New("Don't take block 0")
}
m, err := utils.EcrecoverV2(header, snap.sigcache)
if err != nil {
return common.Address{}, err
}
return m, nil
}
// Copy from v1
func (x *XDPoS_v2) GetMasternodes(chain consensus.ChainReader, header *types.Header) []common.Address {
n := header.Number.Uint64()
e := x.config.Epoch
switch {
case n%e == 0:
return utils.GetMasternodesFromCheckpointHeader(header)
case n%e != 0:
h := chain.GetHeaderByNumber(n - (n % e))
return utils.GetMasternodesFromCheckpointHeader(h)
default:
return []common.Address{}
}
}
// Copy from v1
func (x *XDPoS_v2) GetSnapshot(chain consensus.ChainReader, header *types.Header) (*SnapshotV2, error) {
number := header.Number.Uint64()
log.Trace("get snapshot", "number", number, "hash", header.Hash())
snap, err := x.snapshot(chain, number, header.Hash(), nil)
if err != nil {
return nil, err
}
return snap, nil
}
// snapshot retrieves the authorization snapshot at a given point in time.
func (x *XDPoS_v2) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*SnapshotV2, error) {
// Search for a SnapshotV2 in memory or on disk for checkpoints
var (
headers []*types.Header
snap *SnapshotV2
)
for snap == nil {
// If an in-memory SnapshotV2 was found, use that
if s, ok := x.recents.Get(hash); ok {
snap = s.(*SnapshotV2)
break
}
// If an on-disk checkpoint snapshot can be found, use that
// checkpoint snapshot = checkpoint - gap
if (number+x.config.Gap)%x.config.Epoch == 0 {
if s, err := loadSnapshot(x.signatures, x.db, hash); err == nil {
log.Trace("Loaded snapshot form disk", "number", number, "hash", hash)
snap = s
break
}
}
// If we're at 0 block, make a snapshot
// TODO: We may need to store snapshot at the v1 -> v2 switch block
if number == 0 {
genesis := chain.GetHeaderByNumber(0)
if err := x.VerifyHeader(chain, genesis, true); err != nil {
return nil, err
}
signers := make([]common.Address, (len(genesis.Extra)-utils.ExtraVanity-utils.ExtraSeal)/common.AddressLength)
for i := 0; i < len(signers); i++ {
copy(signers[i][:], genesis.Extra[utils.ExtraVanity+i*common.AddressLength:])
}
snap = newSnapshot(x.signatures, 0, genesis.Hash(), x.currentRound, x.highestQuorumCert, signers)
if err := storeSnapshot(snap, x.db); err != nil {
return nil, err
}
log.Trace("Stored genesis voting snapshot to disk")
break
}
// No snapshot for this header, gather the header and move backward
var header *types.Header
if len(parents) > 0 {
// If we have explicit parents, pick from there (enforced)
header = parents[len(parents)-1]
if header.Hash() != hash || header.Number.Uint64() != number {
return nil, consensus.ErrUnknownAncestor
}
parents = parents[:len(parents)-1]
} else {
// No explicit parents (or no more left), reach out to the database
header = chain.GetHeader(hash, number)
if header == nil {
log.Error("[Seal] Failed due to no header found", "hash", hash, "number", number)
return nil, consensus.ErrUnknownAncestor
}
}
headers = append(headers, header)
number, hash = number-1, header.ParentHash
}
// Previous snapshot found, apply any pending headers on top of it
for i := 0; i < len(headers)/2; i++ {
headers[i], headers[len(headers)-1-i] = headers[len(headers)-1-i], headers[i]
}
snap, err := snap.apply(headers)
if err != nil {
return nil, err
}
x.recents.Add(snap.Hash, snap)
// If we've generated a new checkpoint snapshot, save to disk
// TODO how to save correct snapshot
if uint64(snap.Round)%x.config.Epoch == x.config.Gap {
if err = storeSnapshot(snap, x.db); err != nil {
return nil, err
}
log.Trace("Stored snapshot to disk", "round number", snap.Round, "hash", snap.Hash)
}
return snap, err
}
func (x *XDPoS_v2) VerifyHeader(chain consensus.ChainReader, header *types.Header, fullVerify bool) error {
return nil
}
// Utils for test to check currentRound value
func (x *XDPoS_v2) GetProperties() (utils.Round, *utils.QuorumCert, *utils.QuorumCert) {
x.lock.Lock()
defer x.lock.Unlock()
return x.currentRound, x.lockQuorumCert, x.highestQuorumCert
}
/*
SyncInfo workflow
*/
@ -152,7 +493,7 @@ func (x *XDPoS_v2) VerifyVoteMessage(vote *utils.Vote) (bool, error) {
2. Verify blockInfo
3. Broadcast(Not part of consensus)
*/
return x.verifyMsgSignature(utils.VoteSigHash(&vote.ProposedBlockInfo), vote.Signature)
return x.verifyMsgSignature(utils.VoteSigHash(vote.ProposedBlockInfo), vote.Signature)
}
// Consensus entry point for processing vote message to produce QC
@ -228,7 +569,7 @@ func (x *XDPoS_v2) TimeoutHandler(timeout *utils.Timeout) error {
// 1. checkRoundNumber
if timeout.Round != x.currentRound {
return fmt.Errorf("Timeout message round number: %v does not match currentRound: %v", timeout.Round, x.currentRound)
return &utils.ErrIncomingMessageRoundNotEqualCurrentRound{timeout.Round, x.currentRound}
}
// Collect timeout, generate TC
isThresholdReached, numberOfTimeoutsInPool, pooledTimeouts := x.timeoutPool.Add(timeout)
@ -277,19 +618,43 @@ func (x *XDPoS_v2) onTimeoutPoolThresholdReached(pooledTimeouts map[common.Hash]
/*
Proposed Block workflow
*/
func (x *XDPoS_v2) ProposedBlockHandler(blockChainReader consensus.ChainReader, blockInfo *utils.BlockInfo, quorumCert *utils.QuorumCert) error {
func (x *XDPoS_v2) ProposedBlockHandler(blockChainReader consensus.ChainReader, blockHeader *types.Header) error {
x.lock.Lock()
defer x.lock.Unlock()
/*
1. processQC(): process the QC inside the proposed block
2. verifyVotingRule(): the proposed block's info is extracted into BlockInfo and verified for voting
3. sendVote()
1. Verify QC
2. Generate blockInfo
3. processQC(): process the QC inside the proposed block
4. verifyVotingRule(): the proposed block's info is extracted into BlockInfo and verified for voting
5. sendVote()
*/
err := x.processQC(blockChainReader, quorumCert)
// Get QC and Round from Extra
var decodedExtraField utils.ExtraFields_v2
err := utils.DecodeBytesExtraFields(blockHeader.Extra, &decodedExtraField)
if err != nil {
return err
}
quorumCert := decodedExtraField.QuorumCert
round := decodedExtraField.Round
err = x.verifyQC(quorumCert)
if err != nil {
log.Error("[ProposedBlockHandler] Fail to verify QC", "Extra round", round, "QC proposed BlockInfo Hash", quorumCert.ProposedBlockInfo.Hash)
return err
}
// Generate blockInfo
blockInfo := &utils.BlockInfo{
Hash: blockHeader.Hash(),
Round: round,
Number: blockHeader.Number,
}
err = x.processQC(blockChainReader, 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
}
verified, err := x.verifyVotingRule(blockChainReader, blockInfo, quorumCert)
if err != nil {
return err
@ -335,33 +700,41 @@ func (x *XDPoS_v2) verifyTC(timeoutCert *utils.TimeoutCert) error {
// 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, quorumCert *utils.QuorumCert) error {
log.Trace("[ProcessQC][Before]", "HighQC", x.highestQuorumCert)
// 1. Update HighestQC
if x.highestQuorumCert == nil || (quorumCert.ProposedBlockInfo.Round > x.highestQuorumCert.ProposedBlockInfo.Round) {
x.highestQuorumCert = quorumCert
}
// 2. Get QC from header and update lockQuorumCert(lockQuorumCert is the parent of highestQC)
proposedBlockHeader := blockChainReader.GetHeaderByHash(quorumCert.ProposedBlockInfo.Hash)
// Extra field contain parent information
var decodedExtraField utils.ExtraFields_v2
err := utils.DecodeBytesExtraFields(proposedBlockHeader.Extra, &decodedExtraField)
if err != nil {
return err
}
x.lockQuorumCert = &decodedExtraField.QuorumCert
if proposedBlockHeader.Number.Cmp(x.config.XDPoSV2Block) > 0 {
// Extra field contain parent information
var decodedExtraField utils.ExtraFields_v2
err := utils.DecodeBytesExtraFields(proposedBlockHeader.Extra, &decodedExtraField)
if err != nil {
return err
}
if x.lockQuorumCert == nil || decodedExtraField.QuorumCert.ProposedBlockInfo.Round > x.lockQuorumCert.ProposedBlockInfo.Round {
x.lockQuorumCert = decodedExtraField.QuorumCert
}
proposedBlockRound := &decodedExtraField.Round
// 3. Update commit block info
_, err = x.commitBlocks(blockChainReader, proposedBlockHeader, proposedBlockRound)
if err != nil {
return err
proposedBlockRound := &decodedExtraField.Round
// 3. Update commit block info
_, err = x.commitBlocks(blockChainReader, proposedBlockHeader, proposedBlockRound)
if err != nil {
log.Error("[processQC] Fail to commitBlocks", "proposedBlockRound", proposedBlockRound)
return err
}
}
// 4. Set new round
if quorumCert.ProposedBlockInfo.Round >= x.currentRound {
err := x.setNewRound(quorumCert.ProposedBlockInfo.Round + 1)
if err != nil {
log.Error("[processQC] Fail to setNewRound", "new round to set", quorumCert.ProposedBlockInfo.Round+1)
return err
}
}
log.Trace("[ProcessQC][After]", "HighQC", x.highestQuorumCert)
return nil
}
@ -411,7 +784,11 @@ func (x *XDPoS_v2) verifyVotingRule(blockChainReader consensus.ChainReader, bloc
if blockInfo.Round != x.currentRound {
return false, nil
}
isExtended, err := x.isExtendingFromAncestor(blockChainReader, blockInfo, &x.lockQuorumCert.ProposedBlockInfo)
// XDPoS v1.0 switch to v2.0, the proposed block can always pass voting rule
if x.lockQuorumCert == nil {
return true, nil
}
isExtended, err := x.isExtendingFromAncestor(blockChainReader, blockInfo, x.lockQuorumCert.ProposedBlockInfo)
if err != nil {
return false, err
}
@ -436,7 +813,7 @@ func (x *XDPoS_v2) sendVote(blockInfo *utils.BlockInfo) error {
x.highestVotedRound = x.currentRound
voteMsg := &utils.Vote{
ProposedBlockInfo: *blockInfo,
ProposedBlockInfo: blockInfo,
Signature: signedHash,
}
x.broadcastToBftChannel(voteMsg)
@ -524,17 +901,44 @@ func (x *XDPoS_v2) getCurrentRoundMasterNodes() []common.Address {
return []common.Address{}
}
func (x *XDPoS_v2) getSyncInfo() utils.SyncInfo {
return utils.SyncInfo{
/*
Testing tools
*/
func (x *XDPoS_v2) SetHighestQuorumCert(qc *utils.QuorumCert) {
x.highestQuorumCert = qc
}
func (x *XDPoS_v2) getSyncInfo() *utils.SyncInfo {
return &utils.SyncInfo{
HighestQuorumCert: x.highestQuorumCert,
HighestTimeoutCert: x.highestTimeoutCert,
}
}
func (x *XDPoS_v2) SetNewRoundFaker(newRound utils.Round, resetTimer bool) {
x.lock.Lock()
defer x.lock.Unlock()
// Reset a bunch of things
if resetTimer {
x.timeoutWorker.Reset()
}
x.currentRound = newRound
}
// Utils for test to check currentRound value
func (x *XDPoS_v2) GetCurrentRound() utils.Round {
return x.currentRound
}
//TODO: find parent and grandparent and grandgrandparent block, check round number, if so, commit grandgrandparent
func (x *XDPoS_v2) commitBlocks(blockChainReader consensus.ChainReader, proposedBlockHeader *types.Header, proposedBlockRound *utils.Round) (bool, error) {
func (x *XDPoS_v2) commitBlocks(blockCahinReader consensus.ChainReader, proposedBlockHeader *types.Header, proposedBlockRound *utils.Round) (bool, error) {
// XDPoS v1.0 switch to v2.0, skip commit
if big.NewInt(0).Sub(proposedBlockHeader.Number, big.NewInt(2)).Cmp(x.config.XDPoSV2Block) <= 0 {
return false, nil
}
// Find the last two parent block and check their rounds are the continous
parentBlock := blockChainReader.GetHeaderByHash(proposedBlockHeader.ParentHash)
parentBlock := blockCahinReader.GetHeaderByHash(proposedBlockHeader.ParentHash)
var decodedExtraField utils.ExtraFields_v2
err := utils.DecodeBytesExtraFields(parentBlock.Extra, &decodedExtraField)
@ -546,7 +950,7 @@ func (x *XDPoS_v2) commitBlocks(blockChainReader consensus.ChainReader, proposed
}
// If parent round is continous, we check grandparent
grandParentBlock := blockChainReader.GetHeaderByHash(parentBlock.ParentHash)
grandParentBlock := blockCahinReader.GetHeaderByHash(parentBlock.ParentHash)
err = utils.DecodeBytesExtraFields(grandParentBlock.Extra, &decodedExtraField)
if err != nil {
return false, err

View file

@ -1 +1,133 @@
package engine_v2
import (
"encoding/json"
"sort"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/ethdb"
lru "github.com/hashicorp/golang-lru"
)
// Snapshot is the state of the smart contract validator list
type SnapshotV2 struct {
sigcache *lru.ARCCache // Cache of recent block signatures to speed up ecrecover
Round utils.Round `json:"round"` // Round number
Number uint64 `json:"number"` // Block number where the snapshot was created
Hash common.Hash `json:"hash"` // Block hash where the snapshot was created
// MasterNodes will get assigned on updateM1
MasterNodes map[common.Address]struct{} `json:"masterNodes"` // Set of authorized master nodes at this moment
}
// newSnapshot creates a new snapshot with the specified startup parameters. This
// method does not initialize the set of recent signers, so only ever use if for
// the genesis block.
func newSnapshot(sigcache *lru.ARCCache, number uint64, hash common.Hash, round utils.Round, qc *utils.QuorumCert, masternodes []common.Address) *SnapshotV2 {
snap := &SnapshotV2{
sigcache: sigcache,
Round: round,
Number: number,
Hash: hash,
MasterNodes: make(map[common.Address]struct{}),
}
for _, signer := range masternodes {
snap.MasterNodes[signer] = struct{}{}
}
return snap
}
// loadSnapshot loads an existing snapshot from the database.
func loadSnapshot(sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash) (*SnapshotV2, error) {
blob, err := db.Get(append([]byte("XDPoS-"), hash[:]...))
if err != nil {
return nil, err
}
snap := new(SnapshotV2)
if err := json.Unmarshal(blob, snap); err != nil {
return nil, err
}
snap.sigcache = sigcache
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-"), s.Hash[:]...), blob)
}
// copy creates a deep copy of the SnapshotV2, though not the individual votes.
func (s *SnapshotV2) copy() *SnapshotV2 {
cpy := &SnapshotV2{
sigcache: s.sigcache,
Round: s.Round,
Number: s.Number,
Hash: s.Hash,
MasterNodes: make(map[common.Address]struct{}),
}
for signer := range s.MasterNodes {
cpy.MasterNodes[signer] = struct{}{}
}
return cpy
}
// apply creates a new authorization SnapshotV2 by applying the given headers to
// the original one.
// TODO: XIN-100
func (s *SnapshotV2) apply(headers []*types.Header) (*SnapshotV2, error) {
return s, nil
// Allow passing in no headers for cleaner code
// if len(headers) == 0 {
// return s, nil
// }
// // Sanity check that the headers can be applied
// for i := 0; i < len(headers)-1; i++ {
// if headers[i+1].Number.Uint64() != headers[i].Number.Uint64()+1 {
// return nil, utils.ErrInvalidHeaderOrder
// }
// }
// if headers[0].Number.Uint64() != s.Number+1 {
// return nil, utils.ErrInvalidChild
// }
// // Iterate through the headers and create a new SnapshotV2
// snap := s.copy()
// lastHeader := headers[len(headers)-1]
// snap.Number += uint64(len(headers))
// snap.Hash = lastHeader.Hash()
// extraV2 := new(utils.ExtraFields_v2)
// err := utils.DecodeBytesExtraFields(lastHeader.Extra, &extraV2)
// if err != nil {
// return nil, err
// }
// snap.Round = extraV2.Round
// return snap, nil
}
// signers retrieves the list of authorized signers in ascending order, convert into strings then use native sort lib
func (s *SnapshotV2) GetMasterNodes() []common.Address {
nodes := make([]common.Address, 0, len(s.MasterNodes))
nodeStrs := make([]string, 0, len(s.MasterNodes))
for node := range s.MasterNodes {
nodeStrs = append(nodeStrs, node.Str())
}
sort.Strings(nodeStrs)
for _, str := range nodeStrs {
nodes = append(nodes, common.StringToAddress(str))
}
return nodes
}

View file

@ -0,0 +1,122 @@
package engine_v2
import (
"fmt"
"io/ioutil"
"math/big"
"testing"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/ethdb/leveldb"
"github.com/stretchr/testify/assert"
)
func TestGetMasterNodes(t *testing.T) {
masterNodes := []common.Address{
{4}, {3}, {2}, {1},
}
snap := newSnapshot(nil, 1, common.Hash{}, utils.Round(1), nil, masterNodes)
sortedNodes := snap.GetMasterNodes()
for i := range masterNodes {
if masterNodes[i] != sortedNodes[3-i] {
t.Error("should get sorted master nodes list", i, sortedNodes[i])
return
}
}
}
func TestApplyNewSnapshot(t *testing.T) {
t.Skip("apply has been temporary commented out")
snap := newSnapshot(nil, 1, common.Hash{}, utils.Round(1), nil, nil)
extra := utils.ExtraFields_v2{
Round: 10,
QuorumCert: &utils.QuorumCert{
ProposedBlockInfo: &utils.BlockInfo{},
},
}
extraBytes, err := extra.EncodeToBytes()
assert.Nil(t, err)
headers := []*types.Header{
{Number: big.NewInt(2)},
{Number: big.NewInt(3)},
{Number: big.NewInt(4)},
{
Number: big.NewInt(5),
Extra: extraBytes,
},
}
newSnap, err := snap.apply(headers)
assert.Nil(t, err)
if newSnap.Number != 5 {
t.Error("newSnapshot number should have last header number")
}
if newSnap.Hash != headers[3].Hash() {
t.Error("newSnapshot hash should equal the last header given")
}
if newSnap.Round != 10 {
t.Error("newSnapshot round number should also have last header round number")
}
}
func TestApplyWithWrongHeader(t *testing.T) {
t.Skip("apply has been temporary commented out")
snap := newSnapshot(nil, 1, common.Hash{}, utils.Round(1), nil, nil)
headers := []*types.Header{
{Number: big.NewInt(3)},
}
_, err := snap.apply(headers)
assert.Equal(t, err, utils.ErrInvalidChild)
snap = newSnapshot(nil, 1, common.Hash{}, utils.Round(1), nil, nil)
headers = []*types.Header{
{Number: big.NewInt(2)},
{Number: big.NewInt(4)},
}
_, err = snap.apply(headers)
assert.Equal(t, err, utils.ErrInvalidHeaderOrder)
}
// Should perform deep copy
func TestCopySnapshot(t *testing.T) {
masterNodes := []common.Address{
{4}, {3}, {2}, {1},
}
snap := newSnapshot(nil, 1, common.Hash{}, utils.Round(1), nil, masterNodes)
newSnapshot := snap.copy()
if newSnapshot == snap {
t.Error("should return given different memory address")
}
for node := range snap.MasterNodes {
if _, ok := newSnapshot.MasterNodes[node]; !ok {
t.Error("snapshot masternodes should copy to new object")
}
}
}
func TestStoreLoadSnapshot(t *testing.T) {
snap := newSnapshot(nil, 1, common.Hash{0x1}, utils.Round(1), nil, nil)
dir, err := ioutil.TempDir("", "snapshot-test")
if err != nil {
panic(fmt.Sprintf("can't create temporary directory: %v", err))
}
db, err := leveldb.New(dir, 256, 0, "")
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(nil, lddb, snap.Hash)
if err != nil || restoredSnapshot.Hash != snap.Hash {
t.Error("load snapshot failed", err)
}
}

View file

@ -1,6 +1,9 @@
package utils
import "errors"
import (
"errors"
"fmt"
)
// Various error messages to mark blocks invalid. These should be private to
// prevent engine specific errors from being referenced in the remainder of the
@ -60,6 +63,9 @@ var (
// be modified via out-of-range or non-contiguous headers.
ErrInvalidVotingChain = errors.New("invalid voting chain")
ErrInvalidHeaderOrder = errors.New("invalid header order")
ErrInvalidChild = errors.New("invalid header child")
// errUnauthorized is returned if a header is signed by a non-authorized entity.
ErrUnauthorized = errors.New("unauthorized")
@ -72,3 +78,12 @@ var (
ErrInvalidCheckpointValidators = errors.New("invalid validators list on checkpoint block")
)
type ErrIncomingMessageRoundNotEqualCurrentRound struct {
IncomingRound Round
CurrentRound Round
}
func (e *ErrIncomingMessageRoundNotEqualCurrentRound) Error() string {
return fmt.Sprintf("Timeout message round number: %v does not match currentRound: %v", e.IncomingRound, e.CurrentRound)
}

View file

@ -73,7 +73,7 @@ type BlockInfo struct {
// Vote message in XDPoS 2.0
type Vote struct {
ProposedBlockInfo BlockInfo
ProposedBlockInfo *BlockInfo
Signature Signature
}
@ -91,7 +91,7 @@ type SyncInfo struct {
// Quorum Certificate struct in XDPoS 2.0
type QuorumCert struct {
ProposedBlockInfo BlockInfo
ProposedBlockInfo *BlockInfo
Signatures []Signature
}
@ -105,7 +105,17 @@ type TimeoutCert struct {
// The version byte (consensus version) is the first byte in header's extra and it's only valid with value >= 2
type ExtraFields_v2 struct {
Round Round
QuorumCert QuorumCert
QuorumCert *QuorumCert
}
// Encode XDPoS 2.0 extra fields into bytes
func (e *ExtraFields_v2) EncodeToBytes() ([]byte, error) {
bytes, err := rlp.EncodeToBytes(e)
if err != nil {
return nil, err
}
versionByte := []byte{2}
return append(versionByte, bytes...), nil
}
func rlpHash(x interface{}) (h common.Hash) {

View file

@ -10,10 +10,10 @@ import (
func toyExtraFields() *ExtraFields_v2 {
round := Round(307)
blockInfo := BlockInfo{Hash: common.BigToHash(big.NewInt(2047)), Round: round - 1, Number: big.NewInt(1)}
blockInfo := &BlockInfo{Hash: common.BigToHash(big.NewInt(2047)), Round: round - 1, Number: big.NewInt(1)}
signature := []byte{1, 2, 3, 4, 5, 6, 7, 8}
signatures := []Signature{signature}
quorumCert := QuorumCert{ProposedBlockInfo: blockInfo, Signatures: signatures}
quorumCert := &QuorumCert{ProposedBlockInfo: blockInfo, Signatures: signatures}
e := &ExtraFields_v2{Round: round, QuorumCert: quorumCert}
return e
}
@ -35,14 +35,14 @@ func TestExtraFieldsEncodeDecode(t *testing.T) {
func TestHashAndSigHash(t *testing.T) {
round := Round(307)
blockInfo1 := BlockInfo{Hash: common.BigToHash(big.NewInt(2047)), Round: round - 1, Number: big.NewInt(1)}
blockInfo2 := BlockInfo{Hash: common.BigToHash(big.NewInt(4095)), Round: round - 1, Number: big.NewInt(1)}
blockInfo1 := &BlockInfo{Hash: common.BigToHash(big.NewInt(2047)), Round: round - 1, Number: big.NewInt(1)}
blockInfo2 := &BlockInfo{Hash: common.BigToHash(big.NewInt(4095)), Round: round - 1, Number: big.NewInt(1)}
signature1 := []byte{1, 2, 3, 4, 5, 6, 7, 8}
signature2 := []byte{1, 2, 3, 4, 5, 6, 7, 7}
signatures1 := []Signature{signature1}
quorumCert1 := QuorumCert{ProposedBlockInfo: blockInfo1, Signatures: signatures1}
signatures2 := []Signature{signature2}
quorumCert2 := QuorumCert{ProposedBlockInfo: blockInfo1, Signatures: signatures2}
quorumCert1 := &QuorumCert{ProposedBlockInfo: blockInfo1, Signatures: signatures1}
quorumCert2 := &QuorumCert{ProposedBlockInfo: blockInfo1, Signatures: signatures2}
vote1 := Vote{ProposedBlockInfo: blockInfo1, Signature: signature1}
vote2 := Vote{ProposedBlockInfo: blockInfo1, Signature: signature2}
if vote1.Hash() == vote2.Hash() {
@ -53,12 +53,12 @@ func TestHashAndSigHash(t *testing.T) {
if timeout1.Hash() == timeout2.Hash() {
t.Fatalf("Hash of two timeouts shouldn't equal")
}
syncInfo1 := SyncInfo{HighestQuorumCert: &quorumCert1}
syncInfo2 := SyncInfo{HighestQuorumCert: &quorumCert2}
syncInfo1 := SyncInfo{HighestQuorumCert: quorumCert1}
syncInfo2 := SyncInfo{HighestQuorumCert: quorumCert2}
if syncInfo1.Hash() == syncInfo2.Hash() {
t.Fatalf("Hash of two sync info shouldn't equal")
}
if VoteSigHash(&blockInfo1) == VoteSigHash(&blockInfo2) {
if VoteSigHash(blockInfo1) == VoteSigHash(blockInfo2) {
t.Fatalf("SigHash of two block info shouldn't equal")
}
round2 := Round(999)

View file

@ -10,10 +10,12 @@ import (
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/crypto/sha3"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/XinFinOrg/XDPoSChain/rlp"
lru "github.com/hashicorp/golang-lru"
)
func Position(list []common.Address, x common.Address) int {
@ -151,14 +153,33 @@ func SigHash(header *types.Header) (hash common.Hash) {
return hash
}
// Encode XDPoS 2.0 extra fields into bytes
func (e *ExtraFields_v2) EncodeToBytes() ([]byte, error) {
bytes, err := rlp.EncodeToBytes(e)
func SigHashV2(header *types.Header) (hash common.Hash) {
hasher := sha3.NewKeccak256()
err := rlp.Encode(hasher, []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 err != nil {
return nil, err
log.Debug("Fail to encode", err)
}
versionByte := []byte{2}
return append(versionByte, bytes...), nil
hasher.Sum(hash[:0])
return hash
}
// Decode extra fields for consensus version >= 2 (XDPoS 2.0 and future versions)
@ -174,4 +195,48 @@ func DecodeBytesExtraFields(b []byte, val interface{}) error {
default:
return fmt.Errorf("consensus version %d is not defined", b[0])
}
}
}
// ecrecover extracts the Ethereum account address from a signed header.
func Ecrecover(header *types.Header, sigcache *lru.ARCCache) (common.Address, error) {
// If the signature's already cached, return that
hash := header.Hash()
if address, known := sigcache.Get(hash); known {
return address.(common.Address), nil
}
// Retrieve the signature from the header extra-data
if len(header.Extra) < ExtraSeal {
return common.Address{}, ErrMissingSignature
}
signature := header.Extra[len(header.Extra)-ExtraSeal:]
// Recover the public key and the Ethereum address
pubkey, err := crypto.Ecrecover(SigHash(header).Bytes(), signature)
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
}
func EcrecoverV2(header *types.Header, sigcache *lru.ARCCache) (common.Address, error) {
// If the signature's already cached, return that
hash := header.Hash()
if address, known := sigcache.Get(hash); known {
return address.(common.Address), nil
}
// Recover the public key and the Ethereum address
pubkey, err := crypto.Ecrecover(SigHashV2(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
}

View file

@ -38,4 +38,6 @@ var (
ErrFailValidatorSignature = errors.New("missing validator in header")
ErrNoValidatorSignature = errors.New("no validator in header")
ErrNotReadyToPropose = errors.New("not ready to propose, QC is not ready")
)

View file

@ -1,16 +1,19 @@
package consensus
package tests
import (
"fmt"
"math/big"
"testing"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/stretchr/testify/assert"
)
func TestAdaptorShouldGetAuthorForDifferentConsensusVersion(t *testing.T) {
blockchain, _, currentBlock, _ := PrepareXDCTestBlockChain(t, 10, params.TestXDPoSMockChainConfigWithV2Engine)
blockchain, backend, currentBlock, _ := PrepareXDCTestBlockChainForV2Engine(t, 10, params.TestXDPoSMockChainConfigWithV2Engine)
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
addressFromAdaptor, errorAdaptor := adaptor.Author(currentBlock.Header())
@ -26,9 +29,17 @@ func TestAdaptorShouldGetAuthorForDifferentConsensusVersion(t *testing.T) {
// Insert one more block to make it above 10, which means now we are on v2 of consensus engine
// Insert block 11
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", 11)
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
block11, err := insertBlock(blockchain, 11, blockCoinBase, currentBlock, merkleRoot, nil, 1)
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(11)),
ParentHash: currentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinBase),
}
generateSignature(backend, header)
block11, err := insertBlock(blockchain, header)
if err != nil {
t.Fatal(err)
}

View file

@ -1,4 +1,4 @@
package consensus
package tests
import (
"fmt"
@ -6,6 +6,7 @@ import (
"reflect"
"testing"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/params"
)
@ -26,7 +27,13 @@ func TestNotUpdateSignerListIfNotOnGapBlock(t *testing.T) {
//Get from block validator error message
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
blockA, err := insertBlockTxs(blockchain, 401, blockCoinbaseA, parentBlock, []*types.Transaction{tx}, merkleRoot, 1)
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(401)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinbaseA),
}
blockA, err := insertBlockTxs(blockchain, header, []*types.Transaction{tx})
if err != nil {
t.Fatal(err)
}
@ -54,7 +61,13 @@ func TestNotChangeSingerListIfNothingProposedOrVoted(t *testing.T) {
// Insert block 450
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", 450)
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
block, err := insertBlock(blockchain, 450, blockCoinBase, parentBlock, merkleRoot, nil, 1)
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(450)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinBase),
}
block, err := insertBlock(blockchain, header)
if err != nil {
t.Fatal(err)
}
@ -89,7 +102,13 @@ func TestUpdateSignerListIfVotedBeforeGap(t *testing.T) {
//Get from block validator error message
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
block449, err := insertBlockTxs(blockchain, 449, blockCoinbaseA, parentBlock, []*types.Transaction{tx}, merkleRoot, 1)
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(449)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinbaseA),
}
block449, err := insertBlockTxs(blockchain, header, []*types.Transaction{tx})
if err != nil {
t.Fatal(err)
}
@ -113,7 +132,13 @@ func TestUpdateSignerListIfVotedBeforeGap(t *testing.T) {
// Now, let's mine another block to trigger the GAP block signerList update
block450CoinbaseAddress := "0xaaa0000000000000000000000000000000000450"
merkleRoot = "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
block450, err := insertBlock(blockchain, 450, block450CoinbaseAddress, parentBlock, merkleRoot, nil, 1)
header = &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(450)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(block450CoinbaseAddress),
}
block450, err := insertBlock(blockchain, header)
if err != nil {
t.Fatal(err)
}
@ -147,7 +172,13 @@ func TestCallUpdateM1WithSmartContractTranscation(t *testing.T) {
//Get from block validator error message
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
blockA, err := insertBlockTxs(blockchain, 450, blockCoinbaseA, currentBlock, []*types.Transaction{tx}, merkleRoot, 1)
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(450)),
ParentHash: currentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinbaseA),
}
blockA, err := insertBlockTxs(blockchain, header, []*types.Transaction{tx})
if err != nil {
t.Fatal(err)
}
@ -189,7 +220,13 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
}
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
blockA, err := insertBlockTxs(blockchain, 450, blockCoinbaseA, currentBlock, []*types.Transaction{tx}, merkleRoot, 1)
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(450)),
ParentHash: currentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinbaseA),
}
blockA, err := insertBlockTxs(blockchain, header, []*types.Transaction{tx})
if err != nil {
t.Fatal(err)
}
@ -217,7 +254,13 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
}
merkleRoot = "068dfa09d7b4093441c0cc4d9807a71bc586f6101c072d939b214c21cd136eb3"
block450B, err := insertBlockTxs(blockchain, 450, blockCoinBase450B, currentBlock, []*types.Transaction{tx}, merkleRoot, 1)
header = &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(450)),
ParentHash: currentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinBase450B),
}
block450B, err := insertBlockTxs(blockchain, header, []*types.Transaction{tx})
if err != nil {
t.Fatal(err)
}
@ -240,7 +283,13 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
merkleRoot = "068dfa09d7b4093441c0cc4d9807a71bc586f6101c072d939b214c21cd136eb3"
block451B, err := insertBlock(blockchain, 451, blockCoinBase451B, block450B, merkleRoot, nil, 1)
header = &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(451)),
ParentHash: block450B.Hash(),
Coinbase: common.HexToAddress(blockCoinBase451B),
}
block451B, err := insertBlock(blockchain, header)
if err != nil {
t.Fatal(err)
@ -316,7 +365,13 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin
transferTransaction := transferTx(t, acc1Addr, 999)
merkleRoot := "ea465415b60d88429f181fec9fae67c0f19cbf5a4fa10971d96d4faa57d96ffa"
blockA, err := insertBlockTxs(blockchain, 450, blockCoinbaseA, parentBlock, []*types.Transaction{tx, transferTransaction}, merkleRoot, 1)
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(450)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinbaseA),
}
blockA, err := insertBlockTxs(blockchain, header, []*types.Transaction{tx, transferTransaction})
if err != nil {
t.Fatal(err)
}
@ -351,7 +406,13 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin
transferTransaction = transferTx(t, acc1Addr, 888)
merkleRoot = "184edaddeafc2404248f896ae46be503ae68949896c8eb6b6ad43695581e5022"
block450B, err := insertBlockTxs(blockchain, 450, blockCoinBase450B, parentBlock, []*types.Transaction{tx, transferTransaction}, merkleRoot, 1)
header = &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(450)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinBase450B),
}
block450B, err := insertBlockTxs(blockchain, header, []*types.Transaction{tx, transferTransaction})
if err != nil {
t.Fatal(err)
}
@ -378,7 +439,13 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
merkleRoot = "184edaddeafc2404248f896ae46be503ae68949896c8eb6b6ad43695581e5022"
block451B, err := insertBlock(blockchain, 451, blockCoinBase451B, block450B, merkleRoot, nil, 1)
header = &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(451)),
ParentHash: block450B.Hash(),
Coinbase: common.HexToAddress(blockCoinBase451B),
}
block451B, err := insertBlock(blockchain, header)
if err != nil {
t.Fatal(err)
@ -440,7 +507,13 @@ func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
// Insert normal blocks 450 A
blockCoinBase450A := "0xaaa0000000000000000000000000000000000450"
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
block450A, err := insertBlock(blockchain, 450, blockCoinBase450A, parentBlock, merkleRoot, nil, 1)
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(450)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinBase450A),
}
block450A, err := insertBlock(blockchain, header)
if err != nil {
t.Fatal(err)
}
@ -453,7 +526,13 @@ func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
}
merkleRoot = "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
block451A, err := insertBlockTxs(blockchain, 451, blockCoinbase451A, block450A, []*types.Transaction{tx}, merkleRoot, 1)
header = &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(451)),
ParentHash: block450A.Hash(),
Coinbase: common.HexToAddress(blockCoinbase451A),
}
block451A, err := insertBlockTxs(blockchain, header, []*types.Transaction{tx})
if err != nil {
t.Fatal(err)
}
@ -476,21 +555,39 @@ func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
// Insert forked Block 450 B
blockCoinBase450B := "0xbbb0000000000000000000000000000000000450"
merkleRoot = "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
block450B, err := insertBlock(blockchain, 450, blockCoinBase450B, parentBlock, merkleRoot, nil, 1)
header = &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(450)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinBase450B),
}
block450B, err := insertBlock(blockchain, header)
if err != nil {
t.Fatal(err)
}
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
merkleRoot = "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
block451B, err := insertBlock(blockchain, 451, blockCoinBase451B, block450B, merkleRoot, nil, 1)
header = &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(451)),
ParentHash: block450B.Hash(),
Coinbase: common.HexToAddress(blockCoinBase451B),
}
block451B, err := insertBlock(blockchain, header)
if err != nil {
t.Fatal(err)
}
blockCoinBase452B := "0xbbb0000000000000000000000000000000000452"
merkleRoot = "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
block452B, err := insertBlock(blockchain, 452, blockCoinBase452B, block451B, merkleRoot, nil, 1)
header = &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(452)),
ParentHash: block451B.Hash(),
Coinbase: common.HexToAddress(blockCoinBase452B),
}
block452B, err := insertBlock(blockchain, header)
if err != nil {
t.Fatal(err)
}

View file

@ -1,9 +1,10 @@
package consensus
package tests
import (
"math/big"
"testing"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/params"
)
@ -39,7 +40,15 @@ func TestRaceConditionOnBlockchainReadAndWrite(t *testing.T) {
transferTransaction := transferTx(t, acc1Addr, 999)
merkleRoot := "ea465415b60d88429f181fec9fae67c0f19cbf5a4fa10971d96d4faa57d96ffa"
blockA, err := insertBlockTxs(blockchain, 450, blockCoinbaseA, parentBlock, []*types.Transaction{tx, transferTransaction}, merkleRoot, 1)
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(450)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinbaseA),
}
blockA, err := insertBlockTxs(blockchain, header, []*types.Transaction{tx, transferTransaction})
if err != nil {
t.Fatal(err)
}
@ -74,7 +83,16 @@ func TestRaceConditionOnBlockchainReadAndWrite(t *testing.T) {
transferTransaction = transferTx(t, acc1Addr, 888)
merkleRoot = "184edaddeafc2404248f896ae46be503ae68949896c8eb6b6ad43695581e5022"
block450B, err := insertBlockTxs(blockchain, 450, blockCoinBase450B, parentBlock, []*types.Transaction{tx, transferTransaction}, merkleRoot, 2)
header = &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(450)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinBase450B),
Difficulty: big.NewInt(2),
}
block450B, err := insertBlockTxs(blockchain, header, []*types.Transaction{tx, transferTransaction})
if err != nil {
t.Fatal(err)
}
@ -104,7 +122,14 @@ func TestRaceConditionOnBlockchainReadAndWrite(t *testing.T) {
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
merkleRoot = "184edaddeafc2404248f896ae46be503ae68949896c8eb6b6ad43695581e5022"
block451B, err := insertBlock(blockchain, 451, blockCoinBase451B, block450B, merkleRoot, nil, 3)
header = &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(451)),
ParentHash: block450B.Hash(),
Coinbase: common.HexToAddress(blockCoinBase451B),
Difficulty: big.NewInt(3),
}
block451B, err := insertBlock(blockchain, header)
if err != nil {
t.Fatal(err)

View file

@ -1,4 +1,4 @@
package consensus
package tests
import (
"testing"
@ -10,7 +10,7 @@ import (
)
func TestCountdownTimeoutToSendTimeoutMessage(t *testing.T) {
blockchain, _, _, _ := PrepareXDCTestBlockChain(t, 11, params.TestXDPoSMockChainConfigWithV2Engine)
blockchain, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 11, params.TestXDPoSMockChainConfigWithV2Engine)
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
engineV2.SetNewRoundFaker(utils.Round(1), true)

View file

@ -1,7 +1,6 @@
package consensus
package tests
import (
"math/big"
"testing"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
@ -24,20 +23,14 @@ func TestProposedBlockMessageHandlerSuccessfullyGenerateVote(t *testing.T) {
t.Fatal("Fail to decode extra data", err)
}
proposedBlockInfo := &utils.BlockInfo{
Hash: currentBlock.Hash(),
Round: utils.Round(11),
Number: big.NewInt(11),
}
err = engineV2.ProposedBlockHandler(blockchain, proposedBlockInfo, &extraField.QuorumCert)
err = engineV2.ProposedBlockHandler(blockchain, currentBlock.Header())
if err != nil {
t.Fatal("Fail propose proposedBlock handler", err)
}
voteMsg := <-engineV2.BroadcastCh
assert.NotNil(t, voteMsg)
assert.Equal(t, proposedBlockInfo.Hash, voteMsg.(*utils.Vote).ProposedBlockInfo.Hash)
assert.Equal(t, currentBlock.Hash(), voteMsg.(*utils.Vote).ProposedBlockInfo.Hash)
round, _, highestQC := engineV2.GetProperties()
// Shoud not trigger setNewRound

View file

@ -1,4 +1,4 @@
package consensus
package tests
import (
"bytes"
@ -245,8 +245,13 @@ func PrepareXDCTestBlockChain(t *testing.T, numOfBlocks int, chainConfig *params
for i := 1; i <= numOfBlocks; i++ {
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", i)
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
block, err := insertBlock(blockchain, i, blockCoinBase, currentBlock, merkleRoot, nil, 1)
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(i)),
ParentHash: currentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinBase),
}
block, err := insertBlock(blockchain, header)
if err != nil {
t.Fatal(err)
}
@ -283,19 +288,19 @@ func PrepareXDCTestBlockChainForV2Engine(t *testing.T, numOfBlocks int, chainCon
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
// Build engine v2 compatible extra data field
proposedBlockInfo := utils.BlockInfo{
proposedBlockInfo := &utils.BlockInfo{
Hash: currentBlock.Hash(),
Round: utils.Round(i - 1),
Number: big.NewInt(int64(i - 1)),
}
// Genrate QC
signedHash, err := signFn(accounts.Account{Address: signer}, utils.VoteSigHash(&proposedBlockInfo).Bytes())
signedHash, err := signFn(accounts.Account{Address: signer}, utils.VoteSigHash(proposedBlockInfo).Bytes())
if err != nil {
panic(fmt.Errorf("Error generate QC by creating signedHash: %v", err))
}
var signatures []utils.Signature
signatures = append(signatures, signedHash)
quorumCert := utils.QuorumCert{
quorumCert := &utils.QuorumCert{
ProposedBlockInfo: proposedBlockInfo,
Signatures: signatures,
}
@ -309,7 +314,15 @@ func PrepareXDCTestBlockChainForV2Engine(t *testing.T, numOfBlocks int, chainCon
panic(fmt.Errorf("Error encode extra into bytes: %v", err))
}
block, err := insertBlock(blockchain, i, blockCoinBase, currentBlock, merkleRoot, extraInBytes, 1)
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(i)),
ParentHash: currentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinBase),
Extra: extraInBytes,
Validator: signedHash,
}
block, err := insertBlock(blockchain, header)
if err != nil {
t.Fatal(err)
}
@ -324,16 +337,27 @@ func PrepareXDCTestBlockChainForV2Engine(t *testing.T, numOfBlocks int, chainCon
return blockchain, backend, currentBlock, signer
}
func generateSignature(backend *backends.SimulatedBackend, header *types.Header) error {
signer, signFn, err := backends.SimulateWalletAddressAndSignFn()
if err != nil {
panic(fmt.Errorf("Error while creating simulated wallet for generating singer address and signer fn: %v", err))
}
signature, err := signFn(accounts.Account{Address: signer}, utils.SigHashV2(header).Bytes())
if err != nil {
return err
}
header.Validator = signature
return nil
}
// insert Block without transcation attached
func insertBlock(blockchain *BlockChain, blockNum int, blockCoinBase string, parentBlock *types.Block, root string, customExtra []byte, difficulty int64) (*types.Block, error) {
func insertBlock(blockchain *BlockChain, header *types.Header) (*types.Block, error) {
header.ReceiptHash = common.HexToHash("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
block, err := createXDPoSTestBlock(
blockchain,
parentBlock.Hash().Hex(),
blockCoinBase, blockNum, nil,
"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
common.HexToHash(root),
customExtra,
difficulty,
header,
nil,
)
if err != nil {
return nil, err
@ -347,15 +371,20 @@ func insertBlock(blockchain *BlockChain, blockNum int, blockCoinBase string, par
}
// insert Block with transcation attached
func insertBlockTxs(blockchain *BlockChain, blockNum int, blockCoinBase string, parentBlock *types.Block, txs []*types.Transaction, root string, difficulty int64) (*types.Block, error) {
func insertBlockTxs(blockchain *BlockChain, header *types.Header, txs []*types.Transaction) (*types.Block, error) {
/*
header := types.Header{
Root: common.HexToHash(root),
Number: big.NewInt(int64(blockNum)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinBase),
}
*/
header.ReceiptHash = common.HexToHash("0x9319777b782ba2c83a33c995481ff894ac96d9a92a1963091346a3e1e386705c")
block, err := createXDPoSTestBlock(
blockchain,
parentBlock.Hash().Hex(),
blockCoinBase, blockNum, txs,
"0x9319777b782ba2c83a33c995481ff894ac96d9a92a1963091346a3e1e386705c",
common.HexToHash(root),
nil,
difficulty,
header,
txs,
)
if err != nil {
return nil, err
@ -368,35 +397,56 @@ func insertBlockTxs(blockchain *BlockChain, blockNum int, blockCoinBase string,
return block, nil
}
func createXDPoSTestBlock(bc *BlockChain, parentHash, coinbase string, number int, txs []*types.Transaction, receiptHash string, root common.Hash, customExtra []byte, difficulty int64) (*types.Block, error) {
if customExtra == nil {
//func createXDPoSTestBlock(bc *BlockChain, parentHash, coinbase string, number int, txs []*types.Transaction, receiptHash string, root common.Hash, customExtra []byte, signer common.Address) (*types.Block, error) {
func createXDPoSTestBlock(bc *BlockChain, customHeader *types.Header, txs []*types.Transaction) (*types.Block, error) {
if customHeader.Extra == nil {
extraSubstring := "d7830100018358444388676f312e31342e31856c696e75780000000000000000b185dc0d0e917d18e5dbf0746be6597d3331dd27ea0554e6db433feb2e81730b20b2807d33a1527bf43cd3bc057aa7f641609c2551ebe2fd575f4db704fbf38101" // Grabbed from existing mainnet block, it does not have any meaning except for the length validation
//ReceiptHash = "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
//Root := "0xc99c095e53ff1afe3b86750affd13c7550a2d24d51fb8e41b3c3ef2ea8274bcc"
customExtra, _ = hex.DecodeString(extraSubstring)
customHeader.Extra, _ = hex.DecodeString(extraSubstring)
}
var difficulty *big.Int
if customHeader.Difficulty == nil {
difficulty = big.NewInt(1)
} else {
difficulty = customHeader.Difficulty
}
/*
header := types.Header{
ParentHash: common.HexToHash(parentHash),
UncleHash: types.EmptyUncleHash,
TxHash: types.EmptyRootHash,
// ReceiptHash: types.EmptyRootHash,
ReceiptHash: common.HexToHash(receiptHash),
Root: root,
Coinbase: common.HexToAddress(coinbase),
Difficulty: big.NewInt(int64(1)),
Number: big.NewInt(int64(number)),
GasLimit: 1200000000,
Time: big.NewInt(int64(number * 10)),
Extra: customExtra,
Validator: signer[:],
}
*/
header := types.Header{
ParentHash: common.HexToHash(parentHash),
ParentHash: customHeader.ParentHash,
UncleHash: types.EmptyUncleHash,
TxHash: types.EmptyRootHash,
// ReceiptHash: types.EmptyRootHash,
ReceiptHash: common.HexToHash(receiptHash),
Root: root,
Coinbase: common.HexToAddress(coinbase),
Difficulty: big.NewInt(difficulty),
Number: big.NewInt(int64(number)),
ReceiptHash: customHeader.ReceiptHash,
Root: customHeader.Root,
Coinbase: customHeader.Coinbase,
Difficulty: difficulty,
Number: customHeader.Number,
GasLimit: 1200000000,
Time: big.NewInt(int64(number * 10)),
Extra: customExtra,
Time: big.NewInt(customHeader.Number.Int64() * 10),
Extra: customHeader.Extra,
Validator: customHeader.Validator,
}
var block *types.Block
if len(txs) == 0 {
block = types.NewBlockWithHeader(&header)
} else {
// Prepare Receipt
statedb, err := bc.StateAt(bc.GetBlockByNumber(uint64(number - 1)).Root()) //Get parent root
statedb, err := bc.StateAt(bc.GetBlockByNumber(customHeader.Number.Uint64() - 1).Root()) //Get parent root
if err != nil {
return nil, fmt.Errorf("%v when get state", err)
}

View file

@ -1,4 +1,4 @@
package consensus
package tests
import (
"testing"
@ -11,7 +11,7 @@ import (
// Timeout handler
func TestTimeoutMessageHandlerSuccessfullyGenerateTCandSyncInfo(t *testing.T) {
blockchain, _, _, _ := PrepareXDCTestBlockChain(t, 11, params.TestXDPoSMockChainConfigWithV2Engine)
blockchain, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 11, params.TestXDPoSMockChainConfigWithV2Engine)
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
// Set round to 1
@ -49,11 +49,11 @@ func TestTimeoutMessageHandlerSuccessfullyGenerateTCandSyncInfo(t *testing.T) {
assert.NotNil(t, syncInfoMsg)
// Should have QC, however, we did not inilise it, hence will show default empty value
qc := syncInfoMsg.(utils.SyncInfo).HighestQuorumCert
assert.NotNil(t, qc)
// Shouldn't have QC, however, we did not inilise it, hence will show default empty value
qc := syncInfoMsg.(*utils.SyncInfo).HighestQuorumCert
assert.Nil(t, qc)
tc := syncInfoMsg.(utils.SyncInfo).HighestTimeoutCert
tc := syncInfoMsg.(*utils.SyncInfo).HighestTimeoutCert
assert.NotNil(t, tc)
assert.Equal(t, tc.Round, utils.Round(1))
sigatures := []utils.Signature{[]byte{1}, []byte{2}, []byte{3}}
@ -62,7 +62,7 @@ func TestTimeoutMessageHandlerSuccessfullyGenerateTCandSyncInfo(t *testing.T) {
}
func TestThrowErrorIfTimeoutMsgRoundNotEqualToCurrentRound(t *testing.T) {
blockchain, _, _, _ := PrepareXDCTestBlockChain(t, 11, params.TestXDPoSMockChainConfigWithV2Engine)
blockchain, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 11, params.TestXDPoSMockChainConfigWithV2Engine)
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
// Set round to 3

View file

@ -1,4 +1,4 @@
package consensus
package tests
import (
"math/big"
@ -26,7 +26,7 @@ func TestVoteMessageHandlerSuccessfullyGeneratedAndProcessQC(t *testing.T) {
engineV2.SetNewRoundFaker(utils.Round(11), false)
// Create two timeout message which will not reach vote pool threshold
voteMsg := &utils.Vote{
ProposedBlockInfo: *blockInfo,
ProposedBlockInfo: blockInfo,
Signature: []byte{1},
}
@ -35,10 +35,10 @@ func TestVoteMessageHandlerSuccessfullyGeneratedAndProcessQC(t *testing.T) {
currentRound, lockQuorumCert, highestQuorumCert := engineV2.GetProperties()
// Inilised with nil and 0 round
assert.Nil(t, lockQuorumCert)
assert.Equal(t, utils.Round(0), highestQuorumCert.ProposedBlockInfo.Round)
assert.Nil(t, highestQuorumCert)
assert.Equal(t, utils.Round(11), currentRound)
voteMsg = &utils.Vote{
ProposedBlockInfo: *blockInfo,
ProposedBlockInfo: blockInfo,
Signature: []byte{2},
}
err = engineV2.VoteHandler(blockchain, voteMsg)
@ -46,13 +46,13 @@ func TestVoteMessageHandlerSuccessfullyGeneratedAndProcessQC(t *testing.T) {
currentRound, lockQuorumCert, highestQuorumCert = engineV2.GetProperties()
// Still using the initlised value because we did not yet go to the next round
assert.Nil(t, lockQuorumCert)
assert.Equal(t, utils.Round(0), highestQuorumCert.ProposedBlockInfo.Round)
assert.Nil(t, highestQuorumCert)
assert.Equal(t, utils.Round(11), currentRound)
// Create a vote message that should trigger vote pool hook and increment the round to 12
voteMsg = &utils.Vote{
ProposedBlockInfo: *blockInfo,
ProposedBlockInfo: blockInfo,
Signature: []byte{3},
}
@ -80,7 +80,7 @@ func TestThrowErrorIfVoteMsgRoundNotEqualToCurrentRound(t *testing.T) {
// Set round to 13
engineV2.SetNewRoundFaker(utils.Round(13), false)
voteMsg := &utils.Vote{
ProposedBlockInfo: *blockInfo,
ProposedBlockInfo: blockInfo,
Signature: []byte{1},
}
@ -112,7 +112,7 @@ func TestProcessVoteMsgThenTimeoutMsg(t *testing.T) {
}
// Create two vote message which will not reach vote pool threshold
voteMsg := &utils.Vote{
ProposedBlockInfo: *blockInfo,
ProposedBlockInfo: blockInfo,
Signature: []byte{1},
}
@ -121,11 +121,11 @@ func TestProcessVoteMsgThenTimeoutMsg(t *testing.T) {
currentRound, lockQuorumCert, highestQuorumCert := engineV2.GetProperties()
// Inilised with nil and 0 round
assert.Nil(t, lockQuorumCert)
assert.Equal(t, utils.Round(0), highestQuorumCert.ProposedBlockInfo.Round)
assert.Nil(t, highestQuorumCert)
assert.Equal(t, utils.Round(11), currentRound)
voteMsg = &utils.Vote{
ProposedBlockInfo: *blockInfo,
ProposedBlockInfo: blockInfo,
Signature: []byte{2},
}
err = engineV2.VoteHandler(blockchain, voteMsg)
@ -135,7 +135,7 @@ func TestProcessVoteMsgThenTimeoutMsg(t *testing.T) {
// Create a vote message that should trigger vote pool hook
voteMsg = &utils.Vote{
ProposedBlockInfo: *blockInfo,
ProposedBlockInfo: blockInfo,
Signature: []byte{3},
}
@ -194,11 +194,11 @@ func TestProcessVoteMsgThenTimeoutMsg(t *testing.T) {
assert.NotNil(t, syncInfoMsg)
// Should have HighestQuorumCert from previous round votes
qc := syncInfoMsg.(utils.SyncInfo).HighestQuorumCert
qc := syncInfoMsg.(*utils.SyncInfo).HighestQuorumCert
assert.NotNil(t, qc)
assert.Equal(t, utils.Round(11), qc.ProposedBlockInfo.Round)
tc := syncInfoMsg.(utils.SyncInfo).HighestTimeoutCert
tc := syncInfoMsg.(*utils.SyncInfo).HighestTimeoutCert
assert.NotNil(t, tc)
assert.Equal(t, utils.Round(12), tc.Round)
sigatures := []utils.Signature{[]byte{1}, []byte{2}, []byte{3}}

View file

@ -1,4 +1,4 @@
package bfter
package bft
import (
"fmt"
@ -11,13 +11,17 @@ import (
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/engines/engine_v2"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/core"
"github.com/stretchr/testify/assert"
)
// make different votes based on Signatures
func makeVotes(n int) []utils.Vote {
var votes []utils.Vote
for i := 0; i < n; i++ {
votes = append(votes, utils.Vote{Signature: []byte{byte(i)}})
votes = append(votes, utils.Vote{
ProposedBlockInfo: &utils.BlockInfo{},
Signature: []byte{byte(i)},
})
}
return votes
}
@ -101,7 +105,7 @@ func TestDuplicateVotes(t *testing.T) {
atomic.AddUint32(&broadcastCounter, 1)
}
vote := utils.Vote{}
vote := utils.Vote{ProposedBlockInfo: &utils.BlockInfo{}}
// send twice
tester.bfter.Vote(&vote)
@ -132,7 +136,7 @@ func TestNotBoardcastInvalidVote(t *testing.T) {
atomic.AddUint32(&broadcastCounter, 1)
}
vote := utils.Vote{}
vote := utils.Vote{ProposedBlockInfo: &utils.BlockInfo{}}
tester.bfter.Vote(&vote)
time.Sleep(50 * time.Millisecond)
@ -143,3 +147,59 @@ func TestNotBoardcastInvalidVote(t *testing.T) {
// TODO: SyncInfo and Timeout Test, should be same as Vote.
// Once all test on vote covered, then duplicate to others
func TestTimeoutHandler(t *testing.T) {
tester := newTester()
verifyCounter := uint32(0)
handlerCounter := uint32(0)
broadcastCounter := uint32(0)
targetVotes := 1
tester.bfter.consensus.verifyTimeout = func(timeout *utils.Timeout) error {
atomic.AddUint32(&verifyCounter, 1)
return nil
}
tester.bfter.consensus.timeoutHandler = func(timeout *utils.Timeout) error {
atomic.AddUint32(&handlerCounter, 1)
return nil
}
tester.bfter.broadcast.Timeout = func(*utils.Timeout) {
atomic.AddUint32(&broadcastCounter, 1)
}
timeoutMsg := &utils.Timeout{}
err := tester.bfter.Timeout(timeoutMsg)
if err != nil {
t.Fatal(err)
}
time.Sleep(100 * time.Millisecond)
if int(verifyCounter) != targetVotes || int(handlerCounter) != targetVotes || int(broadcastCounter) != targetVotes {
t.Fatalf("count mismatch: have %v on verify, %v on handler, %v on broadcast, want %v", verifyCounter, handlerCounter, broadcastCounter, targetVotes)
}
}
func TestTimeoutHandlerRoundNotEqual(t *testing.T) {
tester := newTester()
tester.bfter.consensus.verifyTimeout = func(timeout *utils.Timeout) error {
return nil
}
tester.bfter.consensus.timeoutHandler = func(timeout *utils.Timeout) error {
return &utils.ErrIncomingMessageRoundNotEqualCurrentRound{utils.Round(1), utils.Round(2)}
}
tester.bfter.broadcast.Timeout = func(*utils.Timeout) {
return
}
timeoutMsg := &utils.Timeout{}
err := tester.bfter.Timeout(timeoutMsg)
assert.Equal(t, "Timeout message round number: 1 does not match currentRound: 2", err.Error())
}

View file

@ -1,4 +1,4 @@
package bfter
package bft
import (
"github.com/XinFinOrg/XDPoSChain/consensus"
@ -79,9 +79,9 @@ func (b *Bfter) SetConsensusFuns(engine consensus.Engine) {
// TODO: rename
func (b *Bfter) Vote(vote *utils.Vote) error {
log.Trace("Receive Vote", "vote", vote)
log.Info("Receive Vote", "voted block hash", vote.ProposedBlockInfo.Hash.Hex(), "number", vote.ProposedBlockInfo.Number, "round", vote.ProposedBlockInfo.Round)
if b.knownVotes.Contains(vote.Hash()) {
log.Trace("Discarded vote, known vote", "Signature", vote.Signature, "hash", vote.Hash())
log.Info("Discarded vote, known vote", "voted block hash", vote.ProposedBlockInfo.Hash.Hex(), "number", vote.ProposedBlockInfo.Number, "round", vote.ProposedBlockInfo.Round)
return nil
}
@ -116,6 +116,10 @@ func (b *Bfter) Timeout(timeout *utils.Timeout) error {
err = b.consensus.timeoutHandler(timeout)
if err != nil {
if _, ok := err.(*utils.ErrIncomingMessageRoundNotEqualCurrentRound); ok {
log.Debug("timeout message round not equal", "error", err)
return err
}
log.Error("handle BFT Timeout", "error", err)
return err
}

View file

@ -19,10 +19,11 @@ package fetcher
import (
"errors"
"github.com/hashicorp/golang-lru"
"math/rand"
"time"
lru "github.com/hashicorp/golang-lru"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/core/types"
@ -56,6 +57,8 @@ type bodyRequesterFn func([]common.Hash) error
// headerVerifierFn is a callback type to verify a block's header for fast propagation.
type headerVerifierFn func(header *types.Header) error
type proposeBlockHandlerFn func(header *types.Header) error
// blockBroadcasterFn is a callback type for broadcasting a block to connected peers.
type blockBroadcasterFn func(block *types.Block, propagate bool)
@ -133,13 +136,14 @@ type Fetcher struct {
queued map[common.Hash]*inject // Set of already queued blocks (to dedup imports)
knowns *lru.ARCCache
// Callbacks
getBlock blockRetrievalFn // Retrieves a block from the local chain
verifyHeader headerVerifierFn // Checks if a block's headers have a valid proof of work
broadcastBlock blockBroadcasterFn // Broadcasts a block to connected peers
chainHeight chainHeightFn // Retrieves the current chain's height
insertBlock blockInsertFn // Injects a batch of blocks into the chain
prepareBlock blockPrepareFn
dropPeer peerDropFn // Drops a peer for misbehaving
getBlock blockRetrievalFn // Retrieves a block from the local chain
verifyHeader headerVerifierFn // Checks if a block's headers have a valid proof of work
handleProposedBlock proposeBlockHandlerFn // Consensus v2 specific: Hanle new proposed block
broadcastBlock blockBroadcasterFn // Broadcasts a block to connected peers
chainHeight chainHeightFn // Retrieves the current chain's height
insertBlock blockInsertFn // Injects a batch of blocks into the chain
prepareBlock blockPrepareFn
dropPeer peerDropFn // Drops a peer for misbehaving
// Testing hooks
announceChangeHook func(common.Hash, bool) // Method to call upon adding or deleting a hash from the announce list
@ -151,32 +155,33 @@ type Fetcher struct {
}
// New creates a block fetcher to retrieve blocks based on hash announcements.
func New(getBlock blockRetrievalFn, verifyHeader headerVerifierFn, broadcastBlock blockBroadcasterFn, chainHeight chainHeightFn, insertBlock blockInsertFn, prepareBlock blockPrepareFn, dropPeer peerDropFn) *Fetcher {
func New(getBlock blockRetrievalFn, verifyHeader headerVerifierFn, handleProposedBlock proposeBlockHandlerFn, broadcastBlock blockBroadcasterFn, chainHeight chainHeightFn, insertBlock blockInsertFn, prepareBlock blockPrepareFn, dropPeer peerDropFn) *Fetcher {
knownBlocks, _ := lru.NewARC(blockLimit)
return &Fetcher{
notify: make(chan *announce),
inject: make(chan *inject),
blockFilter: make(chan chan []*types.Block),
headerFilter: make(chan chan *headerFilterTask),
bodyFilter: make(chan chan *bodyFilterTask),
done: make(chan common.Hash),
quit: make(chan struct{}),
announces: make(map[string]int),
announced: make(map[common.Hash][]*announce),
fetching: make(map[common.Hash]*announce),
fetched: make(map[common.Hash][]*announce),
completing: make(map[common.Hash]*announce),
queue: prque.New(),
queues: make(map[string]int),
queued: make(map[common.Hash]*inject),
knowns: knownBlocks,
getBlock: getBlock,
verifyHeader: verifyHeader,
broadcastBlock: broadcastBlock,
chainHeight: chainHeight,
insertBlock: insertBlock,
prepareBlock: prepareBlock,
dropPeer: dropPeer,
notify: make(chan *announce),
inject: make(chan *inject),
blockFilter: make(chan chan []*types.Block),
headerFilter: make(chan chan *headerFilterTask),
bodyFilter: make(chan chan *bodyFilterTask),
done: make(chan common.Hash),
quit: make(chan struct{}),
announces: make(map[string]int),
announced: make(map[common.Hash][]*announce),
fetching: make(map[common.Hash]*announce),
fetched: make(map[common.Hash][]*announce),
completing: make(map[common.Hash]*announce),
queue: prque.New(),
queues: make(map[string]int),
queued: make(map[common.Hash]*inject),
knowns: knownBlocks,
getBlock: getBlock,
verifyHeader: verifyHeader,
handleProposedBlock: handleProposedBlock,
broadcastBlock: broadcastBlock,
chainHeight: chainHeight,
insertBlock: insertBlock,
prepareBlock: prepareBlock,
dropPeer: dropPeer,
}
}
@ -721,6 +726,11 @@ func (f *Fetcher) insert(peer string, block *types.Block) {
return
}
}
err = f.handleProposedBlock(block.Header())
if err != nil {
log.Error("[insert] Unable to handle new proposed block", "err", err)
}
// TODO: (XIN-101) Add propose block handler
// If import succeeded, broadcast the block
propAnnounceOutTimer.UpdateSince(block.ReceivedAt)
if !fastBroadCast {

View file

@ -18,13 +18,14 @@ package fetcher
import (
"errors"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"math/big"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
"github.com/XinFinOrg/XDPoSChain/core"
@ -92,7 +93,7 @@ func newTester() *fetcherTester {
blocks: map[common.Hash]*types.Block{genesis.Hash(): genesis},
drops: make(map[string]bool),
}
tester.fetcher = New(tester.getBlock, tester.verifyHeader, tester.broadcastBlock, tester.chainHeight, tester.insertBlock, tester.prepareBlock, tester.dropPeer)
tester.fetcher = New(tester.getBlock, tester.verifyHeader, tester.handleProposedBlock, tester.broadcastBlock, tester.chainHeight, tester.insertBlock, tester.prepareBlock, tester.dropPeer)
tester.fetcher.Start()
return tester
@ -111,6 +112,10 @@ func (f *fetcherTester) verifyHeader(header *types.Header) error {
return nil
}
func (f *fetcherTester) handleProposedBlock(header *types.Header) error {
return nil
}
// broadcastBlock is a nop placeholder for the block broadcasting.
func (f *fetcherTester) broadcastBlock(block *types.Block, propagate bool) {
}
@ -296,6 +301,14 @@ func verifyImportDone(t *testing.T, imported chan *types.Block) {
}
}
func verifyProposeBlockHandlerCalled(t *testing.T, proposedBlockChan chan *types.Header) {
select {
case <-proposedBlockChan:
case <-time.After(50 * time.Millisecond):
t.Fatalf("did not call propose block handler")
}
}
// Tests that a fetcher accepts block announcements and initiates retrievals for
// them, successfully importing into the local chain.
func TestSequentialAnnouncements62(t *testing.T) { testSequentialAnnouncements(t, 62) }
@ -317,12 +330,18 @@ func testSequentialAnnouncements(t *testing.T, protocol int) {
imported <- block
return nil
}
handleProposedBlockChan := make(chan *types.Header)
tester.fetcher.handleProposedBlock = func(header *types.Header) error {
go func() { handleProposedBlockChan <- header }()
return nil
}
for i := len(hashes) - 2; i >= 0; i-- {
tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
verifyImportEvent(t, imported, true)
}
verifyImportDone(t, imported)
verifyProposeBlockHandlerCalled(t, handleProposedBlockChan)
}
// Tests that if blocks are announced by multiple peers (or even the same buggy

View file

@ -29,11 +29,12 @@ import (
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/consensus/misc"
"github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/eth/bfter"
"github.com/XinFinOrg/XDPoSChain/eth/bft"
"github.com/XinFinOrg/XDPoSChain/eth/downloader"
"github.com/XinFinOrg/XDPoSChain/eth/fetcher"
"github.com/XinFinOrg/XDPoSChain/ethdb"
@ -82,7 +83,7 @@ type ProtocolManager struct {
downloader *downloader.Downloader
fetcher *fetcher.Fetcher
peers *peerSet
bfter *bfter.Bfter
bft *bft.Bfter
SubProtocols []p2p.Protocol
@ -198,6 +199,10 @@ func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, ne
validator := func(header *types.Header) error {
return engine.VerifyHeader(blockchain, header, true)
}
handleProposedBlock := func(header *types.Header) error {
return engine.(*XDPoS.XDPoS).HandleProposedBlock(blockchain, header)
}
heighter := func() uint64 {
return blockchain.CurrentBlock().NumberU64()
}
@ -220,16 +225,16 @@ func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, ne
atomic.StoreUint32(&manager.acceptTxs, 1) // Mark initial sync done on any fetcher import
return manager.blockchain.PrepareBlock(block)
}
manager.fetcher = fetcher.New(blockchain.GetBlockByHash, validator, manager.BroadcastBlock, heighter, inserter, prepare, manager.removePeer)
manager.fetcher = fetcher.New(blockchain.GetBlockByHash, validator, handleProposedBlock, manager.BroadcastBlock, heighter, inserter, prepare, manager.removePeer)
//Define bft function
broadcasts := bfter.BroadcastFns{
broadcasts := bft.BroadcastFns{
Vote: manager.BroadcastVote,
Timeout: manager.BroadcastTimeout,
SyncInfo: manager.BroadcastSyncInfo,
}
manager.bfter = bfter.New(broadcasts, blockchain)
manager.bft = bft.New(broadcasts, blockchain)
if blockchain.Config().XDPoS != nil {
manager.bfter.SetConsensusFuns(engine)
manager.bft.SetConsensusFuns(engine)
}
return manager, nil
@ -827,7 +832,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
}
// Mark the peer as owning the vote and process it
p.MarkVote(vote.Hash())
pm.bfter.Vote(&vote)
pm.bft.Vote(&vote)
case msg.Code == TimeoutMsg:
var timeout utils.Timeout
if err := msg.Decode(&timeout); err != nil {
@ -836,7 +841,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
// Mark the peer as owning the timeout and process it
p.MarkTimeout(timeout.Hash())
pm.bfter.Timeout(&timeout)
pm.bft.Timeout(&timeout)
case msg.Code == SyncInfoMsg:
var syncInfo utils.SyncInfo
if err := msg.Decode(&syncInfo); err != nil {
@ -844,7 +849,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
}
// Mark the peer as owning the syncInfo and process it
p.MarkSyncInfo(syncInfo.Hash())
pm.bfter.SyncInfo(&syncInfo)
pm.bft.SyncInfo(&syncInfo)
default:
return errResp(ErrInvalidMsgCode, "%v", msg.Code)
@ -904,7 +909,7 @@ func (pm *ProtocolManager) BroadcastVote(vote *utils.Vote) {
for _, peer := range peers {
peer.SendVote(vote)
}
log.Trace("Propagated Vote", "hash", hash, "recipients", len(peers))
log.Info("Propagated Vote", "voted block hash", vote.ProposedBlockInfo.Hash.Hex(), "number", vote.ProposedBlockInfo.Number, "round", vote.ProposedBlockInfo.Round, "recipients", len(peers))
}
// BroadcastTimeout will propagate a Timeout to all peers which are not known to

View file

@ -41,7 +41,7 @@ var ProtocolName = "eth"
var ProtocolVersions = []uint{eth63, eth62}
// Number of implemented message corresponding to different protocol versions.
var ProtocolLengths = []uint64{17, 8}
var ProtocolLengths = []uint64{227, 8}
const ProtocolMaxMsgSize = 10 * 1024 * 1024 // Maximum cap on the size of a protocol message

View file

@ -134,9 +134,9 @@ func (pm *ProtocolManager) txsyncLoop() {
func (pm *ProtocolManager) syncer() {
// Start and ensure cleanup of sync mechanisms
pm.fetcher.Start()
pm.bfter.Start()
pm.bft.Start()
defer pm.fetcher.Stop()
defer pm.bfter.Stop()
defer pm.bft.Stop()
defer pm.downloader.Terminate()
// Wait for different events to fire synchronisation operations

1
go.mod
View file

@ -63,4 +63,5 @@ require (
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772
gopkg.in/urfave/cli.v1 v1.20.0
gotest.tools v2.2.0+incompatible
)

3
go.sum
View file

@ -118,6 +118,7 @@ github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
@ -339,6 +340,7 @@ gopkg.in/jcmturner/gokrb5.v7 v7.2.3/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuv
gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8=
gopkg.in/karalabe/cookiejar.v2 v2.0.0-20150724131613-8dcd6a7f4951 h1:DMTcQRFbEH62YPRWwOI647s2e5mHda3oBPMHfrLs2bw=
gopkg.in/karalabe/cookiejar.v2 v2.0.0-20150724131613-8dcd6a7f4951/go.mod h1:owOxCRGGeAx1uugABik6K9oeNu1cgxP/R9ItzLDxNWA=
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772 h1:hhsSf/5z74Ck/DJYc+R8zpq8KGm7uJvpdLRQED/IedA=
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns=
@ -353,5 +355,6 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View file

@ -523,6 +523,7 @@ func (self *worker) commitNewWork() {
// only go with XDPoS
if self.config.XDPoS != nil {
// get masternodes set from latest checkpoint
// TODO: refactor on yourturn with below condition for v1 v2
c := self.engine.(*XDPoS.XDPoS)
len, preIndex, curIndex, ok, err := c.YourTurn(self.chain, parent.Header(), self.coinbase)
if err != nil {
@ -581,6 +582,10 @@ func (self *worker) commitNewWork() {
}
if err := self.engine.Prepare(self.chain, header); err != nil {
if err == consensus.ErrNotReadyToPropose {
log.Info("Waiting...", "err", err)
return
}
log.Error("Failed to prepare header for new block", "err", err)
return
}

View file

@ -36,11 +36,11 @@ var (
var (
XDPoSV2Config = &V2{
TimeoutWorkerDuration: 50000,
TimeoutWorkerDuration: 50,
CertThreshold: common.MaxMasternodesV2*2/3 + 1,
}
TestXDPoSV2Config = &V2{
TimeoutWorkerDuration: 5000,
TimeoutWorkerDuration: 5,
CertThreshold: 3,
}
@ -195,12 +195,12 @@ type XDPoSConfig struct {
Gap uint64 `json:"gap"` // Gap time preparing for the next epoch
FoudationWalletAddr common.Address `json:"foudationWalletAddr"` // Foundation Address Wallet
SkipValidation bool //Skip Block Validation for testing purpose
XDPoSV2Block *big.Int
V2 V2
XDPoSV2Block *big.Int `json:"v2Block"`
V2 V2 `json:"v2"`
}
type V2 struct {
TimeoutWorkerDuration int64 `json:"TimeoutWorkerDuration"` // Duration in ms
TimeoutWorkerDuration int64 `json:"timeoutWorkerDuration"` // Duration in ms
CertThreshold int `json:"certificateThreshold"` // Necessary number of messages from master nodes to form a certificate
}