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 { func (x *XDPoS) Prepare(chain consensus.ChainReader, header *types.Header) error {
switch x.config.BlockConsensusVersion(header.Number) { switch x.config.BlockConsensusVersion(header.Number) {
case params.ConsensusEngineVersion2: case params.ConsensusEngineVersion2:
return nil return x.EngineV2.Prepare(chain, header)
default: // Default "v1" default: // Default "v1"
return x.EngineV1.Prepare(chain, header) 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) { 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) { switch x.config.BlockConsensusVersion(header.Number) {
case params.ConsensusEngineVersion2: case params.ConsensusEngineVersion2:
return nil, nil return x.EngineV2.Finalize(chain, header, state, parentState, txs, uncles, receipts)
default: // Default "v1" default: // Default "v1"
return x.EngineV1.Finalize(chain, header, state, parentState, txs, uncles, receipts) 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) { func (x *XDPoS) Seal(chain consensus.ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) {
switch x.config.BlockConsensusVersion(block.Number()) { switch x.config.BlockConsensusVersion(block.Number()) {
case params.ConsensusEngineVersion2: case params.ConsensusEngineVersion2:
return nil, nil return x.EngineV2.Seal(chain, block, stop)
default: // Default "v1" default: // Default "v1"
return x.EngineV1.Seal(chain, block, stop) 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 { func (x *XDPoS) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int {
switch x.config.BlockConsensusVersion(parent.Number) { switch x.config.BlockConsensusVersion(parent.Number) {
case params.ConsensusEngineVersion2: case params.ConsensusEngineVersion2:
return nil return x.EngineV2.CalcDifficulty(chain, time, parent)
default: // Default "v1" default: // Default "v1"
return x.EngineV1.CalcDifficulty(chain, time, parent) 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 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 { func (x *XDPoS) GetMasternodes(chain consensus.ChainReader, header *types.Header) []common.Address {
switch x.config.BlockConsensusVersion(header.Number) { switch x.config.BlockConsensusVersion(header.Number) {
case params.ConsensusEngineVersion2: case params.ConsensusEngineVersion2:
return []common.Address{} return x.EngineV2.GetMasternodes(chain, header)
default: // Default "v1" default: // Default "v1"
return x.EngineV1.GetMasternodes(chain, header) 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) { func (x *XDPoS) YourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (int, int, int, bool, error) {
switch x.config.BlockConsensusVersion(parent.Number) { switch x.config.BlockConsensusVersion(parent.Number) {
case params.ConsensusEngineVersion2:
return x.EngineV2.YourTurn(chain, parent, signer)
default: // Default "v1" default: // Default "v1"
return x.EngineV1.YourTurn(chain, parent, signer) 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) { func (x *XDPoS) GetValidator(creator common.Address, chain consensus.ChainReader, header *types.Header) (common.Address, error) {
switch x.config.BlockConsensusVersion(header.Number) { 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) 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) { func (x *XDPoS) GetSnapshot(chain consensus.ChainReader, header *types.Header) (*utils.PublicApiSnapshot, error) {
switch x.config.BlockConsensusVersion(header.Number) { 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" default: // Default "v1"
sp, err := x.EngineV1.GetSnapshot(chain, header) sp, err := x.EngineV1.GetSnapshot(chain, header)
// Convert to a standard PublicApiSnapshot type, otherwise it's a breaking change to API // 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" 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 // XDPoS is the delegated-proof-of-stake consensus engine proposed to support the
// Ethereum testnet following the Ropsten attacks. // Ethereum testnet following the Ropsten attacks.
type XDPoS_v1 struct { 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 // Author implements consensus.Engine, returning the Ethereum address recovered
// from the signature in the header's extra-data section. // from the signature in the header's extra-data section.
func (x *XDPoS_v1) Author(header *types.Header) (common.Address, error) { 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. // 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 { if header.Number.Uint64() == 0 {
return common.Address{}, errors.New("Don't take block 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 { if err != nil {
return common.Address{}, err 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 // Resolve the authorization key and check against signers
creator, err := ecrecover(header, x.signatures) creator, err := utils.Ecrecover(header, x.signatures)
if err != nil { if err != nil {
return err 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") 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 { if err != nil {
return common.Address{}, err 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) { 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) { func (x *XDPoS_v1) RecoverValidator(header *types.Header) (common.Address, error) {
@ -979,21 +954,6 @@ func GetMasternodesFromCheckpointHeader(checkpointHeader *types.Header) []common
return masternodes 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) { func (x *XDPoS_v1) getSignersFromContract(chain consensus.ChainReader, checkpointHeader *types.Header) ([]common.Address, error) {
startGapBlockHeader := checkpointHeader startGapBlockHeader := checkpointHeader
number := checkpointHeader.Number.Uint64() number := checkpointHeader.Number.Uint64()

View file

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

View file

@ -1,8 +1,12 @@
package engine_v2 package engine_v2
import ( import (
"encoding/json"
"errors"
"fmt" "fmt"
"io/ioutil"
"math/big" "math/big"
"path/filepath"
"sync" "sync"
"time" "time"
@ -12,25 +16,30 @@ import (
"github.com/XinFinOrg/XDPoSChain/consensus" "github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/consensus/clique" "github.com/XinFinOrg/XDPoSChain/consensus/clique"
"github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/ethdb" "github.com/XinFinOrg/XDPoSChain/ethdb"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
lru "github.com/hashicorp/golang-lru"
) )
type XDPoS_v2 struct { type XDPoS_v2 struct {
config *params.XDPoSConfig // Consensus engine configuration parameters config *params.XDPoSConfig // Consensus engine configuration parameters
db ethdb.Database // Database to store and retrieve snapshot checkpoints db ethdb.Database // Database to store and retrieve snapshot checkpoints
recents *lru.ARCCache // Snapshots for recent block to speed up reorgs
signatures *lru.ARCCache // Signatures of recent blocks to speed up mining
signer common.Address // Ethereum address of the signing key signer common.Address // Ethereum address of the signing key
signFn clique.SignerFn // Signer function to authorize hashes with signFn clique.SignerFn // Signer function to authorize hashes with
lock sync.RWMutex // Protects the signer fields
signLock sync.RWMutex // Protects the signer fields signLock sync.RWMutex // Protects the signer fields
BroadcastCh chan interface{} BroadcastCh chan interface{}
timeoutWorker *countdown.CountdownTimer // Timer to generate broadcast timeout msg if threashold reached timeoutWorker *countdown.CountdownTimer // Timer to generate broadcast timeout msg if threashold reached
lock sync.RWMutex // Protects the currentRound fields etc
timeoutPool *utils.Pool timeoutPool *utils.Pool
votePool *utils.Pool votePool *utils.Pool
currentRound utils.Round currentRound utils.Round
@ -40,23 +49,33 @@ type XDPoS_v2 struct {
lockQuorumCert *utils.QuorumCert lockQuorumCert *utils.QuorumCert
highestTimeoutCert *utils.TimeoutCert highestTimeoutCert *utils.TimeoutCert
highestCommitBlock *utils.BlockInfo 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 { func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS_v2 {
// Setup Timer // Setup Timer
duration := time.Duration(config.V2.TimeoutWorkerDuration) * time.Millisecond duration := time.Duration(config.V2.TimeoutWorkerDuration) * time.Second
timer := countdown.NewCountDown(duration) timer := countdown.NewCountDown(duration)
timeoutPool := utils.NewPool(config.V2.CertThreshold) timeoutPool := utils.NewPool(config.V2.CertThreshold)
recents, _ := lru.NewARC(utils.InmemorySnapshots)
signatures, _ := lru.NewARC(utils.InmemorySnapshots)
votePool := utils.NewPool(config.V2.CertThreshold) votePool := utils.NewPool(config.V2.CertThreshold)
engine := &XDPoS_v2{ engine := &XDPoS_v2{
config: config, config: config,
db: db, db: db,
timeoutWorker: timer, signatures: signatures,
BroadcastCh: make(chan interface{}),
timeoutPool: timeoutPool, recents: recents,
votePool: votePool, timeoutWorker: timer,
highestTimeoutCert: &utils.TimeoutCert{}, BroadcastCh: make(chan interface{}),
highestQuorumCert: &utils.QuorumCert{}, timeoutPool: timeoutPool,
votePool: votePool,
highestTimeoutCert: nil,
highestQuorumCert: nil,
highestVotedRound: utils.Round(0), highestVotedRound: utils.Round(0),
} }
// Add callback to the timer // Add callback to the timer
@ -65,24 +84,118 @@ func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS_v2 {
return engine return engine
} }
/* // Prepare implements consensus.Engine, preparing all the consensus fields of the
Testing tools // header for running the transactions on top.
*/ func (x *XDPoS_v2) Prepare(chain consensus.ChainReader, header *types.Header) error {
func (x *XDPoS_v2) SetNewRoundFaker(newRound utils.Round, resetTimer bool) { // Verify mined block parent matches highest QC
x.lock.Lock() x.lock.Lock()
defer x.lock.Unlock() // Check header if it is the first consensus v2 block, if so, assign initial values to current round and highestQC
// Reset a bunch of things if header.Number.Cmp(big.NewInt(0).Add(x.config.XDPoSV2Block, big.NewInt(1))) == 0 {
if resetTimer { log.Info("[Prepare] Initilising highest QC for consensus v2 first block", "Block Num", header.Number.String(), "BlockHash", header.Hash())
x.timeoutWorker.Reset() // 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 // Finalize implements consensus.Engine, ensuring no uncles are set, nor block
func (x *XDPoS_v2) GetProperties() (utils.Round, *utils.QuorumCert, *utils.QuorumCert) { // rewards given, and returns the final block.
x.lock.Lock() 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) {
defer x.lock.Unlock() // set block reward
return x.currentRound, x.lockQuorumCert, x.highestQuorumCert 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. // 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) { 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 { func (x *XDPoS_v2) VerifyHeader(chain consensus.ChainReader, header *types.Header, fullVerify bool) error {
return nil 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 SyncInfo workflow
*/ */
@ -152,7 +493,7 @@ func (x *XDPoS_v2) VerifyVoteMessage(vote *utils.Vote) (bool, error) {
2. Verify blockInfo 2. Verify blockInfo
3. Broadcast(Not part of consensus) 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 // 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 // 1. checkRoundNumber
if timeout.Round != x.currentRound { 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 // Collect timeout, generate TC
isThresholdReached, numberOfTimeoutsInPool, pooledTimeouts := x.timeoutPool.Add(timeout) isThresholdReached, numberOfTimeoutsInPool, pooledTimeouts := x.timeoutPool.Add(timeout)
@ -277,19 +618,43 @@ func (x *XDPoS_v2) onTimeoutPoolThresholdReached(pooledTimeouts map[common.Hash]
/* /*
Proposed Block workflow 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() x.lock.Lock()
defer x.lock.Unlock() defer x.lock.Unlock()
/* /*
1. processQC(): process the QC inside the proposed block 1. Verify QC
2. verifyVotingRule(): the proposed block's info is extracted into BlockInfo and verified for voting 2. Generate blockInfo
3. sendVote() 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 { if err != nil {
return err 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) verified, err := x.verifyVotingRule(blockChainReader, blockInfo, quorumCert)
if err != nil { if err != nil {
return err 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 // 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 { func (x *XDPoS_v2) processQC(blockChainReader consensus.ChainReader, quorumCert *utils.QuorumCert) error {
log.Trace("[ProcessQC][Before]", "HighQC", x.highestQuorumCert)
// 1. Update HighestQC // 1. Update HighestQC
if x.highestQuorumCert == nil || (quorumCert.ProposedBlockInfo.Round > x.highestQuorumCert.ProposedBlockInfo.Round) { if x.highestQuorumCert == nil || (quorumCert.ProposedBlockInfo.Round > x.highestQuorumCert.ProposedBlockInfo.Round) {
x.highestQuorumCert = quorumCert x.highestQuorumCert = quorumCert
} }
// 2. Get QC from header and update lockQuorumCert(lockQuorumCert is the parent of highestQC) // 2. Get QC from header and update lockQuorumCert(lockQuorumCert is the parent of highestQC)
proposedBlockHeader := blockChainReader.GetHeaderByHash(quorumCert.ProposedBlockInfo.Hash) proposedBlockHeader := blockChainReader.GetHeaderByHash(quorumCert.ProposedBlockInfo.Hash)
// Extra field contain parent information if proposedBlockHeader.Number.Cmp(x.config.XDPoSV2Block) > 0 {
var decodedExtraField utils.ExtraFields_v2 // Extra field contain parent information
err := utils.DecodeBytesExtraFields(proposedBlockHeader.Extra, &decodedExtraField) var decodedExtraField utils.ExtraFields_v2
if err != nil { err := utils.DecodeBytesExtraFields(proposedBlockHeader.Extra, &decodedExtraField)
return err if err != nil {
} return err
x.lockQuorumCert = &decodedExtraField.QuorumCert }
if x.lockQuorumCert == nil || decodedExtraField.QuorumCert.ProposedBlockInfo.Round > x.lockQuorumCert.ProposedBlockInfo.Round {
x.lockQuorumCert = decodedExtraField.QuorumCert
}
proposedBlockRound := &decodedExtraField.Round proposedBlockRound := &decodedExtraField.Round
// 3. Update commit block info // 3. Update commit block info
_, err = x.commitBlocks(blockChainReader, proposedBlockHeader, proposedBlockRound) _, err = x.commitBlocks(blockChainReader, proposedBlockHeader, proposedBlockRound)
if err != nil { if err != nil {
return err log.Error("[processQC] Fail to commitBlocks", "proposedBlockRound", proposedBlockRound)
return err
}
} }
// 4. Set new round // 4. Set new round
if quorumCert.ProposedBlockInfo.Round >= x.currentRound { if quorumCert.ProposedBlockInfo.Round >= x.currentRound {
err := x.setNewRound(quorumCert.ProposedBlockInfo.Round + 1) err := x.setNewRound(quorumCert.ProposedBlockInfo.Round + 1)
if err != nil { if err != nil {
log.Error("[processQC] Fail to setNewRound", "new round to set", quorumCert.ProposedBlockInfo.Round+1)
return err return err
} }
} }
log.Trace("[ProcessQC][After]", "HighQC", x.highestQuorumCert)
return nil return nil
} }
@ -411,7 +784,11 @@ func (x *XDPoS_v2) verifyVotingRule(blockChainReader consensus.ChainReader, bloc
if blockInfo.Round != x.currentRound { if blockInfo.Round != x.currentRound {
return false, nil 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 { if err != nil {
return false, err return false, err
} }
@ -436,7 +813,7 @@ func (x *XDPoS_v2) sendVote(blockInfo *utils.BlockInfo) error {
x.highestVotedRound = x.currentRound x.highestVotedRound = x.currentRound
voteMsg := &utils.Vote{ voteMsg := &utils.Vote{
ProposedBlockInfo: *blockInfo, ProposedBlockInfo: blockInfo,
Signature: signedHash, Signature: signedHash,
} }
x.broadcastToBftChannel(voteMsg) x.broadcastToBftChannel(voteMsg)
@ -524,17 +901,44 @@ func (x *XDPoS_v2) getCurrentRoundMasterNodes() []common.Address {
return []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, HighestQuorumCert: x.highestQuorumCert,
HighestTimeoutCert: x.highestTimeoutCert, 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 //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 // 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 var decodedExtraField utils.ExtraFields_v2
err := utils.DecodeBytesExtraFields(parentBlock.Extra, &decodedExtraField) 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 // If parent round is continous, we check grandparent
grandParentBlock := blockChainReader.GetHeaderByHash(parentBlock.ParentHash) grandParentBlock := blockCahinReader.GetHeaderByHash(parentBlock.ParentHash)
err = utils.DecodeBytesExtraFields(grandParentBlock.Extra, &decodedExtraField) err = utils.DecodeBytesExtraFields(grandParentBlock.Extra, &decodedExtraField)
if err != nil { if err != nil {
return false, err return false, err

View file

@ -1 +1,133 @@
package engine_v2 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 package utils
import "errors" import (
"errors"
"fmt"
)
// Various error messages to mark blocks invalid. These should be private to // Various error messages to mark blocks invalid. These should be private to
// prevent engine specific errors from being referenced in the remainder of the // 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. // be modified via out-of-range or non-contiguous headers.
ErrInvalidVotingChain = errors.New("invalid voting chain") 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 is returned if a header is signed by a non-authorized entity.
ErrUnauthorized = errors.New("unauthorized") ErrUnauthorized = errors.New("unauthorized")
@ -72,3 +78,12 @@ var (
ErrInvalidCheckpointValidators = errors.New("invalid validators list on checkpoint block") 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 // Vote message in XDPoS 2.0
type Vote struct { type Vote struct {
ProposedBlockInfo BlockInfo ProposedBlockInfo *BlockInfo
Signature Signature Signature Signature
} }
@ -91,7 +91,7 @@ type SyncInfo struct {
// Quorum Certificate struct in XDPoS 2.0 // Quorum Certificate struct in XDPoS 2.0
type QuorumCert struct { type QuorumCert struct {
ProposedBlockInfo BlockInfo ProposedBlockInfo *BlockInfo
Signatures []Signature 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 // 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 { type ExtraFields_v2 struct {
Round Round 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) { func rlpHash(x interface{}) (h common.Hash) {

View file

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

View file

@ -10,10 +10,12 @@ import (
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/crypto/sha3" "github.com/XinFinOrg/XDPoSChain/crypto/sha3"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
"github.com/XinFinOrg/XDPoSChain/rlp" "github.com/XinFinOrg/XDPoSChain/rlp"
lru "github.com/hashicorp/golang-lru"
) )
func Position(list []common.Address, x common.Address) int { func Position(list []common.Address, x common.Address) int {
@ -151,14 +153,33 @@ func SigHash(header *types.Header) (hash common.Hash) {
return hash return hash
} }
// Encode XDPoS 2.0 extra fields into bytes func SigHashV2(header *types.Header) (hash common.Hash) {
func (e *ExtraFields_v2) EncodeToBytes() ([]byte, error) { hasher := sha3.NewKeccak256()
bytes, err := rlp.EncodeToBytes(e)
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 { if err != nil {
return nil, err log.Debug("Fail to encode", err)
} }
versionByte := []byte{2} hasher.Sum(hash[:0])
return append(versionByte, bytes...), nil return hash
} }
// Decode extra fields for consensus version >= 2 (XDPoS 2.0 and future versions) // Decode extra fields for consensus version >= 2 (XDPoS 2.0 and future versions)
@ -175,3 +196,47 @@ func DecodeBytesExtraFields(b []byte, val interface{}) error {
return fmt.Errorf("consensus version %d is not defined", b[0]) 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") ErrFailValidatorSignature = errors.New("missing validator in header")
ErrNoValidatorSignature = errors.New("no 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 ( import (
"fmt" "fmt"
"math/big"
"testing" "testing"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestAdaptorShouldGetAuthorForDifferentConsensusVersion(t *testing.T) { 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) adaptor := blockchain.Engine().(*XDPoS.XDPoS)
addressFromAdaptor, errorAdaptor := adaptor.Author(currentBlock.Header()) 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 one more block to make it above 10, which means now we are on v2 of consensus engine
// Insert block 11 // Insert block 11
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", 11) blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", 11)
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930" 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -1,4 +1,4 @@
package consensus package tests
import ( import (
"fmt" "fmt"
@ -6,6 +6,7 @@ import (
"reflect" "reflect"
"testing" "testing"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
) )
@ -26,7 +27,13 @@ func TestNotUpdateSignerListIfNotOnGapBlock(t *testing.T) {
//Get from block validator error message //Get from block validator error message
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772" 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -54,7 +61,13 @@ func TestNotChangeSingerListIfNothingProposedOrVoted(t *testing.T) {
// Insert block 450 // Insert block 450
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", 450) blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", 450)
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930" 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -89,7 +102,13 @@ func TestUpdateSignerListIfVotedBeforeGap(t *testing.T) {
//Get from block validator error message //Get from block validator error message
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772" 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 { if err != nil {
t.Fatal(err) 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 // Now, let's mine another block to trigger the GAP block signerList update
block450CoinbaseAddress := "0xaaa0000000000000000000000000000000000450" block450CoinbaseAddress := "0xaaa0000000000000000000000000000000000450"
merkleRoot = "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772" 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -147,7 +172,13 @@ func TestCallUpdateM1WithSmartContractTranscation(t *testing.T) {
//Get from block validator error message //Get from block validator error message
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772" 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -189,7 +220,13 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
} }
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772" 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -217,7 +254,13 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
} }
merkleRoot = "068dfa09d7b4093441c0cc4d9807a71bc586f6101c072d939b214c21cd136eb3" 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -240,7 +283,13 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451" blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
merkleRoot = "068dfa09d7b4093441c0cc4d9807a71bc586f6101c072d939b214c21cd136eb3" 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -316,7 +365,13 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin
transferTransaction := transferTx(t, acc1Addr, 999) transferTransaction := transferTx(t, acc1Addr, 999)
merkleRoot := "ea465415b60d88429f181fec9fae67c0f19cbf5a4fa10971d96d4faa57d96ffa" 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -351,7 +406,13 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin
transferTransaction = transferTx(t, acc1Addr, 888) transferTransaction = transferTx(t, acc1Addr, 888)
merkleRoot = "184edaddeafc2404248f896ae46be503ae68949896c8eb6b6ad43695581e5022" 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -378,7 +439,13 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451" blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
merkleRoot = "184edaddeafc2404248f896ae46be503ae68949896c8eb6b6ad43695581e5022" 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -440,7 +507,13 @@ func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
// Insert normal blocks 450 A // Insert normal blocks 450 A
blockCoinBase450A := "0xaaa0000000000000000000000000000000000450" blockCoinBase450A := "0xaaa0000000000000000000000000000000000450"
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930" 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -453,7 +526,13 @@ func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
} }
merkleRoot = "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772" 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -476,21 +555,39 @@ func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
// Insert forked Block 450 B // Insert forked Block 450 B
blockCoinBase450B := "0xbbb0000000000000000000000000000000000450" blockCoinBase450B := "0xbbb0000000000000000000000000000000000450"
merkleRoot = "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930" 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451" blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
merkleRoot = "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930" 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
blockCoinBase452B := "0xbbb0000000000000000000000000000000000452" blockCoinBase452B := "0xbbb0000000000000000000000000000000000452"
merkleRoot = "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930" 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -1,9 +1,10 @@
package consensus package tests
import ( import (
"math/big" "math/big"
"testing" "testing"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
) )
@ -39,7 +40,15 @@ func TestRaceConditionOnBlockchainReadAndWrite(t *testing.T) {
transferTransaction := transferTx(t, acc1Addr, 999) transferTransaction := transferTx(t, acc1Addr, 999)
merkleRoot := "ea465415b60d88429f181fec9fae67c0f19cbf5a4fa10971d96d4faa57d96ffa" 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -74,7 +83,16 @@ func TestRaceConditionOnBlockchainReadAndWrite(t *testing.T) {
transferTransaction = transferTx(t, acc1Addr, 888) transferTransaction = transferTx(t, acc1Addr, 888)
merkleRoot = "184edaddeafc2404248f896ae46be503ae68949896c8eb6b6ad43695581e5022" 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -104,7 +122,14 @@ func TestRaceConditionOnBlockchainReadAndWrite(t *testing.T) {
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451" blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
merkleRoot = "184edaddeafc2404248f896ae46be503ae68949896c8eb6b6ad43695581e5022" 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 { if err != nil {
t.Fatal(err) t.Fatal(err)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
package bfter package bft
import ( import (
"fmt" "fmt"
@ -11,13 +11,17 @@ import (
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/engines/engine_v2" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/engines/engine_v2"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/core"
"github.com/stretchr/testify/assert"
) )
// make different votes based on Signatures // make different votes based on Signatures
func makeVotes(n int) []utils.Vote { func makeVotes(n int) []utils.Vote {
var votes []utils.Vote var votes []utils.Vote
for i := 0; i < n; i++ { 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 return votes
} }
@ -101,7 +105,7 @@ func TestDuplicateVotes(t *testing.T) {
atomic.AddUint32(&broadcastCounter, 1) atomic.AddUint32(&broadcastCounter, 1)
} }
vote := utils.Vote{} vote := utils.Vote{ProposedBlockInfo: &utils.BlockInfo{}}
// send twice // send twice
tester.bfter.Vote(&vote) tester.bfter.Vote(&vote)
@ -132,7 +136,7 @@ func TestNotBoardcastInvalidVote(t *testing.T) {
atomic.AddUint32(&broadcastCounter, 1) atomic.AddUint32(&broadcastCounter, 1)
} }
vote := utils.Vote{} vote := utils.Vote{ProposedBlockInfo: &utils.BlockInfo{}}
tester.bfter.Vote(&vote) tester.bfter.Vote(&vote)
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
@ -143,3 +147,59 @@ func TestNotBoardcastInvalidVote(t *testing.T) {
// TODO: SyncInfo and Timeout Test, should be same as Vote. // TODO: SyncInfo and Timeout Test, should be same as Vote.
// Once all test on vote covered, then duplicate to others // 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 ( import (
"github.com/XinFinOrg/XDPoSChain/consensus" "github.com/XinFinOrg/XDPoSChain/consensus"
@ -79,9 +79,9 @@ func (b *Bfter) SetConsensusFuns(engine consensus.Engine) {
// TODO: rename // TODO: rename
func (b *Bfter) Vote(vote *utils.Vote) error { 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()) { 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 return nil
} }
@ -116,6 +116,10 @@ func (b *Bfter) Timeout(timeout *utils.Timeout) error {
err = b.consensus.timeoutHandler(timeout) err = b.consensus.timeoutHandler(timeout)
if err != nil { 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) log.Error("handle BFT Timeout", "error", err)
return err return err
} }

View file

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

View file

@ -18,13 +18,14 @@ package fetcher
import ( import (
"errors" "errors"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"math/big" "math/big"
"sync" "sync"
"sync/atomic" "sync/atomic"
"testing" "testing"
"time" "time"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus/ethash" "github.com/XinFinOrg/XDPoSChain/consensus/ethash"
"github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/core"
@ -92,7 +93,7 @@ func newTester() *fetcherTester {
blocks: map[common.Hash]*types.Block{genesis.Hash(): genesis}, blocks: map[common.Hash]*types.Block{genesis.Hash(): genesis},
drops: make(map[string]bool), 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() tester.fetcher.Start()
return tester return tester
@ -111,6 +112,10 @@ func (f *fetcherTester) verifyHeader(header *types.Header) error {
return nil return nil
} }
func (f *fetcherTester) handleProposedBlock(header *types.Header) error {
return nil
}
// broadcastBlock is a nop placeholder for the block broadcasting. // broadcastBlock is a nop placeholder for the block broadcasting.
func (f *fetcherTester) broadcastBlock(block *types.Block, propagate bool) { 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 // Tests that a fetcher accepts block announcements and initiates retrievals for
// them, successfully importing into the local chain. // them, successfully importing into the local chain.
func TestSequentialAnnouncements62(t *testing.T) { testSequentialAnnouncements(t, 62) } func TestSequentialAnnouncements62(t *testing.T) { testSequentialAnnouncements(t, 62) }
@ -317,12 +330,18 @@ func testSequentialAnnouncements(t *testing.T, protocol int) {
imported <- block imported <- block
return nil 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-- { 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) tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
verifyImportEvent(t, imported, true) verifyImportEvent(t, imported, true)
} }
verifyImportDone(t, imported) verifyImportDone(t, imported)
verifyProposeBlockHandlerCalled(t, handleProposedBlockChan)
} }
// Tests that if blocks are announced by multiple peers (or even the same buggy // 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/common"
"github.com/XinFinOrg/XDPoSChain/consensus" "github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/consensus/misc" "github.com/XinFinOrg/XDPoSChain/consensus/misc"
"github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/types" "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/downloader"
"github.com/XinFinOrg/XDPoSChain/eth/fetcher" "github.com/XinFinOrg/XDPoSChain/eth/fetcher"
"github.com/XinFinOrg/XDPoSChain/ethdb" "github.com/XinFinOrg/XDPoSChain/ethdb"
@ -82,7 +83,7 @@ type ProtocolManager struct {
downloader *downloader.Downloader downloader *downloader.Downloader
fetcher *fetcher.Fetcher fetcher *fetcher.Fetcher
peers *peerSet peers *peerSet
bfter *bfter.Bfter bft *bft.Bfter
SubProtocols []p2p.Protocol SubProtocols []p2p.Protocol
@ -198,6 +199,10 @@ func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, ne
validator := func(header *types.Header) error { validator := func(header *types.Header) error {
return engine.VerifyHeader(blockchain, header, true) return engine.VerifyHeader(blockchain, header, true)
} }
handleProposedBlock := func(header *types.Header) error {
return engine.(*XDPoS.XDPoS).HandleProposedBlock(blockchain, header)
}
heighter := func() uint64 { heighter := func() uint64 {
return blockchain.CurrentBlock().NumberU64() 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 atomic.StoreUint32(&manager.acceptTxs, 1) // Mark initial sync done on any fetcher import
return manager.blockchain.PrepareBlock(block) 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 //Define bft function
broadcasts := bfter.BroadcastFns{ broadcasts := bft.BroadcastFns{
Vote: manager.BroadcastVote, Vote: manager.BroadcastVote,
Timeout: manager.BroadcastTimeout, Timeout: manager.BroadcastTimeout,
SyncInfo: manager.BroadcastSyncInfo, SyncInfo: manager.BroadcastSyncInfo,
} }
manager.bfter = bfter.New(broadcasts, blockchain) manager.bft = bft.New(broadcasts, blockchain)
if blockchain.Config().XDPoS != nil { if blockchain.Config().XDPoS != nil {
manager.bfter.SetConsensusFuns(engine) manager.bft.SetConsensusFuns(engine)
} }
return manager, nil return manager, nil
@ -827,7 +832,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
} }
// Mark the peer as owning the vote and process it // Mark the peer as owning the vote and process it
p.MarkVote(vote.Hash()) p.MarkVote(vote.Hash())
pm.bfter.Vote(&vote) pm.bft.Vote(&vote)
case msg.Code == TimeoutMsg: case msg.Code == TimeoutMsg:
var timeout utils.Timeout var timeout utils.Timeout
if err := msg.Decode(&timeout); err != nil { 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 // Mark the peer as owning the timeout and process it
p.MarkTimeout(timeout.Hash()) p.MarkTimeout(timeout.Hash())
pm.bfter.Timeout(&timeout) pm.bft.Timeout(&timeout)
case msg.Code == SyncInfoMsg: case msg.Code == SyncInfoMsg:
var syncInfo utils.SyncInfo var syncInfo utils.SyncInfo
if err := msg.Decode(&syncInfo); err != nil { 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 // Mark the peer as owning the syncInfo and process it
p.MarkSyncInfo(syncInfo.Hash()) p.MarkSyncInfo(syncInfo.Hash())
pm.bfter.SyncInfo(&syncInfo) pm.bft.SyncInfo(&syncInfo)
default: default:
return errResp(ErrInvalidMsgCode, "%v", msg.Code) return errResp(ErrInvalidMsgCode, "%v", msg.Code)
@ -904,7 +909,7 @@ func (pm *ProtocolManager) BroadcastVote(vote *utils.Vote) {
for _, peer := range peers { for _, peer := range peers {
peer.SendVote(vote) 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 // 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} var ProtocolVersions = []uint{eth63, eth62}
// Number of implemented message corresponding to different protocol versions. // 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 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() { func (pm *ProtocolManager) syncer() {
// Start and ensure cleanup of sync mechanisms // Start and ensure cleanup of sync mechanisms
pm.fetcher.Start() pm.fetcher.Start()
pm.bfter.Start() pm.bft.Start()
defer pm.fetcher.Stop() defer pm.fetcher.Stop()
defer pm.bfter.Stop() defer pm.bft.Stop()
defer pm.downloader.Terminate() defer pm.downloader.Terminate()
// Wait for different events to fire synchronisation operations // 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/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772 gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772
gopkg.in/urfave/cli.v1 v1.20.0 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/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.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.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/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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA= 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/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 h1:DMTcQRFbEH62YPRWwOI647s2e5mHda3oBPMHfrLs2bw=
gopkg.in/karalabe/cookiejar.v2 v2.0.0-20150724131613-8dcd6a7f4951/go.mod h1:owOxCRGGeAx1uugABik6K9oeNu1cgxP/R9ItzLDxNWA= 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/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 h1:hhsSf/5z74Ck/DJYc+R8zpq8KGm7uJvpdLRQED/IedA=
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= 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.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 h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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= 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= 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 // only go with XDPoS
if self.config.XDPoS != nil { if self.config.XDPoS != nil {
// get masternodes set from latest checkpoint // get masternodes set from latest checkpoint
// TODO: refactor on yourturn with below condition for v1 v2
c := self.engine.(*XDPoS.XDPoS) c := self.engine.(*XDPoS.XDPoS)
len, preIndex, curIndex, ok, err := c.YourTurn(self.chain, parent.Header(), self.coinbase) len, preIndex, curIndex, ok, err := c.YourTurn(self.chain, parent.Header(), self.coinbase)
if err != nil { if err != nil {
@ -581,6 +582,10 @@ func (self *worker) commitNewWork() {
} }
if err := self.engine.Prepare(self.chain, header); err != nil { 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) log.Error("Failed to prepare header for new block", "err", err)
return return
} }

View file

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