diff --git a/consensus/XDPoS/XDPoS.go b/consensus/XDPoS/XDPoS.go index 3e8621d523..bf8a16e169 100644 --- a/consensus/XDPoS/XDPoS.go +++ b/consensus/XDPoS/XDPoS.go @@ -175,7 +175,7 @@ func (x *XDPoS) VerifySeal(chain consensus.ChainReader, header *types.Header) er func (x *XDPoS) Prepare(chain consensus.ChainReader, header *types.Header) error { switch x.config.BlockConsensusVersion(header.Number) { case params.ConsensusEngineVersion2: - return nil + return x.EngineV2.Prepare(chain, header) default: // Default "v1" return x.EngineV1.Prepare(chain, header) } @@ -186,7 +186,7 @@ func (x *XDPoS) Prepare(chain consensus.ChainReader, header *types.Header) error func (x *XDPoS) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, parentState *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { switch x.config.BlockConsensusVersion(header.Number) { case params.ConsensusEngineVersion2: - return nil, nil + return x.EngineV2.Finalize(chain, header, state, parentState, txs, uncles, receipts) default: // Default "v1" return x.EngineV1.Finalize(chain, header, state, parentState, txs, uncles, receipts) } @@ -197,7 +197,7 @@ func (x *XDPoS) Finalize(chain consensus.ChainReader, header *types.Header, stat func (x *XDPoS) Seal(chain consensus.ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) { switch x.config.BlockConsensusVersion(block.Number()) { case params.ConsensusEngineVersion2: - return nil, nil + return x.EngineV2.Seal(chain, block, stop) default: // Default "v1" return x.EngineV1.Seal(chain, block, stop) } @@ -209,12 +209,21 @@ func (x *XDPoS) Seal(chain consensus.ChainReader, block *types.Block, stop <-cha func (x *XDPoS) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int { switch x.config.BlockConsensusVersion(parent.Number) { case params.ConsensusEngineVersion2: - return nil + return x.EngineV2.CalcDifficulty(chain, time, parent) default: // Default "v1" return x.EngineV1.CalcDifficulty(chain, time, parent) } } +func (x *XDPoS) HandleProposedBlock(chain consensus.ChainReader, header *types.Header) error { + switch x.config.BlockConsensusVersion(header.Number) { + case params.ConsensusEngineVersion2: + return x.EngineV2.ProposedBlockHandler(chain, header) + default: // Default "v1" + return nil + } +} + /* XDC specific methods */ @@ -243,7 +252,7 @@ func (x *XDPoS) IsAuthorisedAddress(header *types.Header, chain consensus.ChainR func (x *XDPoS) GetMasternodes(chain consensus.ChainReader, header *types.Header) []common.Address { switch x.config.BlockConsensusVersion(header.Number) { case params.ConsensusEngineVersion2: - return []common.Address{} + return x.EngineV2.GetMasternodes(chain, header) default: // Default "v1" return x.EngineV1.GetMasternodes(chain, header) } @@ -251,6 +260,8 @@ func (x *XDPoS) GetMasternodes(chain consensus.ChainReader, header *types.Header func (x *XDPoS) YourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (int, int, int, bool, error) { switch x.config.BlockConsensusVersion(parent.Number) { + case params.ConsensusEngineVersion2: + return x.EngineV2.YourTurn(chain, parent, signer) default: // Default "v1" return x.EngineV1.YourTurn(chain, parent, signer) } @@ -258,7 +269,7 @@ func (x *XDPoS) YourTurn(chain consensus.ChainReader, parent *types.Header, sign func (x *XDPoS) GetValidator(creator common.Address, chain consensus.ChainReader, header *types.Header) (common.Address, error) { switch x.config.BlockConsensusVersion(header.Number) { - default: // Default "v1" + default: // Default "v1", v2 does not need this function return x.EngineV1.GetValidator(creator, chain, header) } } @@ -307,6 +318,13 @@ func (x *XDPoS) GetDb() ethdb.Database { func (x *XDPoS) GetSnapshot(chain consensus.ChainReader, header *types.Header) (*utils.PublicApiSnapshot, error) { switch x.config.BlockConsensusVersion(header.Number) { + case params.ConsensusEngineVersion2: + sp, err := x.EngineV2.GetSnapshot(chain, header) + return &utils.PublicApiSnapshot{ + Number: sp.Number, + Hash: sp.Hash, + Signers: sp.MasterNodes, + }, err default: // Default "v1" sp, err := x.EngineV1.GetSnapshot(chain, header) // Convert to a standard PublicApiSnapshot type, otherwise it's a breaking change to API diff --git a/consensus/XDPoS/engines/engine_v1/engine.go b/consensus/XDPoS/engines/engine_v1/engine.go index 2a955e0429..0ba47d36db 100644 --- a/consensus/XDPoS/engines/engine_v1/engine.go +++ b/consensus/XDPoS/engines/engine_v1/engine.go @@ -28,31 +28,6 @@ import ( lru "github.com/hashicorp/golang-lru" ) -// ecrecover extracts the Ethereum account address from a signed header. -func ecrecover(header *types.Header, sigcache *lru.ARCCache) (common.Address, error) { - // If the signature's already cached, return that - hash := header.Hash() - if address, known := sigcache.Get(hash); known { - return address.(common.Address), nil - } - // Retrieve the signature from the header extra-data - if len(header.Extra) < utils.ExtraSeal { - return common.Address{}, utils.ErrMissingSignature - } - signature := header.Extra[len(header.Extra)-utils.ExtraSeal:] - - // Recover the public key and the Ethereum address - pubkey, err := crypto.Ecrecover(utils.SigHash(header).Bytes(), signature) - if err != nil { - return common.Address{}, err - } - var signer common.Address - copy(signer[:], crypto.Keccak256(pubkey[1:])[12:]) - - sigcache.Add(hash, signer) - return signer, nil -} - // XDPoS is the delegated-proof-of-stake consensus engine proposed to support the // Ethereum testnet following the Ropsten attacks. type XDPoS_v1 struct { @@ -106,7 +81,7 @@ func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS_v1 { // Author implements consensus.Engine, returning the Ethereum address recovered // from the signature in the header's extra-data section. func (x *XDPoS_v1) Author(header *types.Header) (common.Address, error) { - return ecrecover(header, x.signatures) + return utils.Ecrecover(header, x.signatures) } // VerifyHeader checks whether a header conforms to the consensus rules. @@ -384,7 +359,7 @@ func whoIsCreator(snap *SnapshotV1, header *types.Header) (common.Address, error if header.Number.Uint64() == 0 { return common.Address{}, errors.New("Don't take block 0") } - m, err := ecrecover(header, snap.sigcache) + m, err := utils.Ecrecover(header, snap.sigcache) if err != nil { return common.Address{}, err } @@ -549,7 +524,7 @@ func (x *XDPoS_v1) verifySeal(chain consensus.ChainReader, header *types.Header, } // Resolve the authorization key and check against signers - creator, err := ecrecover(header, x.signatures) + creator, err := utils.Ecrecover(header, x.signatures) if err != nil { return err } @@ -643,7 +618,7 @@ func (x *XDPoS_v1) GetValidator(creator common.Address, chain consensus.ChainRea return common.Address{}, fmt.Errorf("couldn't find checkpoint header") } } - m, err := GetM1M2FromCheckpointHeader(cpHeader, header, chain.Config()) + m, err := utils.GetM1M2FromCheckpointHeader(cpHeader, header, chain.Config()) if err != nil { return common.Address{}, err } @@ -911,7 +886,7 @@ func (x *XDPoS_v1) calcDifficulty(chain consensus.ChainReader, parent *types.Hea } func (x *XDPoS_v1) RecoverSigner(header *types.Header) (common.Address, error) { - return ecrecover(header, x.signatures) + return utils.Ecrecover(header, x.signatures) } func (x *XDPoS_v1) RecoverValidator(header *types.Header) (common.Address, error) { @@ -979,21 +954,6 @@ func GetMasternodesFromCheckpointHeader(checkpointHeader *types.Header) []common return masternodes } -// Get m2 list from checkpoint block. -func GetM1M2FromCheckpointHeader(checkpointHeader *types.Header, currentHeader *types.Header, config *params.ChainConfig) (map[common.Address]common.Address, error) { - if checkpointHeader.Number.Uint64()%common.EpocBlockRandomize != 0 { - return nil, errors.New("This block is not checkpoint block epoc.") - } - // Get signers from this block. - masternodes := GetMasternodesFromCheckpointHeader(checkpointHeader) - validators := utils.ExtractValidatorsFromBytes(checkpointHeader.Validators) - m1m2, _, err := utils.GetM1M2(masternodes, validators, currentHeader, config) - if err != nil { - return map[common.Address]common.Address{}, err - } - return m1m2, nil -} - func (x *XDPoS_v1) getSignersFromContract(chain consensus.ChainReader, checkpointHeader *types.Header) ([]common.Address, error) { startGapBlockHeader := checkpointHeader number := checkpointHeader.Number.Uint64() diff --git a/consensus/XDPoS/engines/engine_v1/snapshot.go b/consensus/XDPoS/engines/engine_v1/snapshot.go index 3ad8b22189..3aa7ad030d 100644 --- a/consensus/XDPoS/engines/engine_v1/snapshot.go +++ b/consensus/XDPoS/engines/engine_v1/snapshot.go @@ -187,7 +187,7 @@ func (s *SnapshotV1) apply(headers []*types.Header) (*SnapshotV1, error) { delete(snap.Recents, number-limit) } // Resolve the authorization key and check against signers - signer, err := ecrecover(header, s.sigcache) + signer, err := utils.Ecrecover(header, s.sigcache) if err != nil { return nil, err } diff --git a/consensus/XDPoS/engines/engine_v2/engine.go b/consensus/XDPoS/engines/engine_v2/engine.go index 53ab0b5937..1cf768a9fa 100644 --- a/consensus/XDPoS/engines/engine_v2/engine.go +++ b/consensus/XDPoS/engines/engine_v2/engine.go @@ -1,8 +1,12 @@ package engine_v2 import ( + "encoding/json" + "errors" "fmt" + "io/ioutil" "math/big" + "path/filepath" "sync" "time" @@ -12,25 +16,30 @@ import ( "github.com/XinFinOrg/XDPoSChain/consensus" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" "github.com/XinFinOrg/XDPoSChain/consensus/clique" + "github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/ethdb" "github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/params" + lru "github.com/hashicorp/golang-lru" ) type XDPoS_v2 struct { config *params.XDPoSConfig // Consensus engine configuration parameters db ethdb.Database // Database to store and retrieve snapshot checkpoints + recents *lru.ARCCache // Snapshots for recent block to speed up reorgs + signatures *lru.ARCCache // Signatures of recent blocks to speed up mining + signer common.Address // Ethereum address of the signing key signFn clique.SignerFn // Signer function to authorize hashes with + lock sync.RWMutex // Protects the signer fields signLock sync.RWMutex // Protects the signer fields BroadcastCh chan interface{} timeoutWorker *countdown.CountdownTimer // Timer to generate broadcast timeout msg if threashold reached - lock sync.RWMutex // Protects the currentRound fields etc timeoutPool *utils.Pool votePool *utils.Pool currentRound utils.Round @@ -40,23 +49,33 @@ type XDPoS_v2 struct { lockQuorumCert *utils.QuorumCert highestTimeoutCert *utils.TimeoutCert highestCommitBlock *utils.BlockInfo + + HookReward func(chain consensus.ChainReader, state *state.StateDB, parentState *state.StateDB, header *types.Header) (error, map[string]interface{}) } func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS_v2 { // Setup Timer - duration := time.Duration(config.V2.TimeoutWorkerDuration) * time.Millisecond + duration := time.Duration(config.V2.TimeoutWorkerDuration) * time.Second timer := countdown.NewCountDown(duration) timeoutPool := utils.NewPool(config.V2.CertThreshold) + + recents, _ := lru.NewARC(utils.InmemorySnapshots) + signatures, _ := lru.NewARC(utils.InmemorySnapshots) + votePool := utils.NewPool(config.V2.CertThreshold) engine := &XDPoS_v2{ - config: config, - db: db, - timeoutWorker: timer, - BroadcastCh: make(chan interface{}), - timeoutPool: timeoutPool, - votePool: votePool, - highestTimeoutCert: &utils.TimeoutCert{}, - highestQuorumCert: &utils.QuorumCert{}, + config: config, + db: db, + signatures: signatures, + + recents: recents, + timeoutWorker: timer, + BroadcastCh: make(chan interface{}), + timeoutPool: timeoutPool, + votePool: votePool, + + highestTimeoutCert: nil, + highestQuorumCert: nil, highestVotedRound: utils.Round(0), } // Add callback to the timer @@ -65,24 +84,118 @@ func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS_v2 { return engine } -/* - Testing tools -*/ -func (x *XDPoS_v2) SetNewRoundFaker(newRound utils.Round, resetTimer bool) { +// Prepare implements consensus.Engine, preparing all the consensus fields of the +// header for running the transactions on top. +func (x *XDPoS_v2) Prepare(chain consensus.ChainReader, header *types.Header) error { + // Verify mined block parent matches highest QC x.lock.Lock() - defer x.lock.Unlock() - // Reset a bunch of things - if resetTimer { - x.timeoutWorker.Reset() + // Check header if it is the first consensus v2 block, if so, assign initial values to current round and highestQC + if header.Number.Cmp(big.NewInt(0).Add(x.config.XDPoSV2Block, big.NewInt(1))) == 0 { + log.Info("[Prepare] Initilising highest QC for consensus v2 first block", "Block Num", header.Number.String(), "BlockHash", header.Hash()) + // Generate new parent blockInfo and put it into QC + parentBlockInfo := &utils.BlockInfo{ + Hash: header.ParentHash, + Round: utils.Round(0), + Number: big.NewInt(0).Sub(header.Number, big.NewInt(1)), + } + quorumCert := &utils.QuorumCert{ + ProposedBlockInfo: parentBlockInfo, + Signatures: nil, + } + x.currentRound = 1 + x.highestQuorumCert = quorumCert } - x.currentRound = newRound + + currentRound := x.currentRound + highestQC := x.highestQuorumCert + x.lock.Unlock() + //parentRound := highestQC.ProposedBlockInfo.Round + if (highestQC == nil) || (header.ParentHash != highestQC.ProposedBlockInfo.Hash) { + return consensus.ErrNotReadyToPropose + } + + extra := utils.ExtraFields_v2{ + Round: currentRound, + QuorumCert: highestQC, + } + + header.Nonce = types.BlockNonce{} + + number := header.Number.Uint64() + parent := chain.GetHeader(header.ParentHash, number-1) + log.Info("Preparing new block!", "Number", number, "Parent Hash", parent.Hash()) + if parent == nil { + return consensus.ErrUnknownAncestor + } + // Set the correct difficulty + header.Difficulty = x.calcDifficulty(chain, parent, x.signer) + log.Debug("CalcDifficulty ", "number", header.Number, "difficulty", header.Difficulty) + + // TODO: previous round should sit on previous Epoch and x.currentRound should >= Epoch number + if number%x.config.Epoch == 0 { + snap, err := x.snapshot(chain, number-1, header.ParentHash, nil) + if err != nil { + return err + } + masternodes := snap.GetMasterNodes() + //TODO: remove penalty nodes and add comeback nodes + for _, v := range masternodes { + header.Validators = append(header.Validators, v[:]...) + } + } + + extraBytes, err := extra.EncodeToBytes() + if err != nil { + return err + } + + header.Extra = extraBytes + + // Mix digest is reserved for now, set to empty + header.MixDigest = common.Hash{} + + // Ensure the timestamp has the correct delay + + // TODO: if timestamp > current time, how to deal with future timestamp + header.Time = new(big.Int).Add(parent.Time, new(big.Int).SetUint64(x.config.Period)) + if header.Time.Int64() < time.Now().Unix() { + header.Time = big.NewInt(time.Now().Unix()) + } + + return nil } -// Utils for test to check currentRound value -func (x *XDPoS_v2) GetProperties() (utils.Round, *utils.QuorumCert, *utils.QuorumCert) { - x.lock.Lock() - defer x.lock.Unlock() - return x.currentRound, x.lockQuorumCert, x.highestQuorumCert +// Finalize implements consensus.Engine, ensuring no uncles are set, nor block +// rewards given, and returns the final block. +func (x *XDPoS_v2) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, parentState *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { + // set block reward + number := header.Number.Uint64() + rCheckpoint := chain.Config().XDPoS.RewardCheckpoint + + // _ = c.CacheData(header, txs, receipts) + + if x.HookReward != nil && number%rCheckpoint == 0 { + err, rewards := x.HookReward(chain, state, parentState, header) + if err != nil { + return nil, err + } + if len(common.StoreRewardFolder) > 0 { + data, err := json.Marshal(rewards) + if err == nil { + err = ioutil.WriteFile(filepath.Join(common.StoreRewardFolder, header.Number.String()+"."+header.Hash().Hex()), data, 0644) + } + if err != nil { + log.Error("Error when save reward info ", "number", header.Number, "hash", header.Hash().Hex(), "err", err) + } + } + } + + // the state remains as is and uncles are dropped + header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) + header.UncleHash = types.CalcUncleHash(nil) + + // Assemble and return the final block for sealing + return types.NewBlock(header, txs, nil, receipts), nil } // Authorize injects a private key into the consensus engine to mint new blocks with. @@ -95,13 +208,241 @@ func (x *XDPoS_v2) Authorize(signer common.Address, signFn clique.SignerFn) { } func (x *XDPoS_v2) Author(header *types.Header) (common.Address, error) { - return common.Address{}, nil + return utils.EcrecoverV2(header, x.signatures) +} + +// Seal implements consensus.Engine, attempting to create a sealed block using +// the local signing credentials. +func (x *XDPoS_v2) Seal(chain consensus.ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) { + header := block.Header() + + // Sealing the genesis block is not supported + number := header.Number.Uint64() + if number == 0 { + return nil, utils.ErrUnknownBlock + } + // For 0-period chains, refuse to seal empty blocks (no reward but would spin sealing) + // checkpoint blocks have no tx + if x.config.Period == 0 && len(block.Transactions()) == 0 && number%x.config.Epoch != 0 { + return nil, utils.ErrWaitTransactions + } + // Don't hold the signer fields for the entire sealing procedure + x.signLock.RLock() + signer, signFn := x.signer, x.signFn + x.signLock.RUnlock() + + // Bail out if we're unauthorized to sign a block + snap, err := x.snapshot(chain, number-1, header.ParentHash, nil) + if err != nil { + return nil, err + } + masternodes := x.GetMasternodes(chain, header) + if _, authorized := snap.MasterNodes[signer]; !authorized { + valid := false + for _, m := range masternodes { + if m == signer { + valid = true + break + } + } + if !valid { + return nil, utils.ErrUnauthorized + } + } + + select { + case <-stop: + return nil, nil + default: + } + + // Sign all the things! + signature, err := signFn(accounts.Account{Address: signer}, utils.SigHashV2(header).Bytes()) + if err != nil { + return nil, err + } + header.Validator = signature + + return block.WithSeal(header), nil +} + +// CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty +// that a new block should have based on the previous blocks in the chain and the +// current signer. +func (x *XDPoS_v2) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int { + return x.calcDifficulty(chain, parent, x.signer) +} + +// TODO: what should be new difficulty +func (x *XDPoS_v2) calcDifficulty(chain consensus.ChainReader, parent *types.Header, signer common.Address) *big.Int { + // TODO: The difference of round number between parent round and current round + return big.NewInt(1) +} + +// Copy from v1 +func (x *XDPoS_v2) YourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (int, int, int, bool, error) { + snap, err := x.GetSnapshot(chain, parent) + if err != nil { + log.Error("[YourTurn] Failed while getting snapshot", "parentHash", parent.Hash(), "err", err) + return 0, -1, -1, false, err + } + masternodes := x.GetMasternodes(chain, parent) + if len(masternodes) == 0 { + return 0, -1, -1, false, errors.New("Masternodes not found") + } + pre := common.Address{} + // masternode[0] has chance to create block 1 + preIndex := -1 + if parent.Number.Uint64() != 0 { + pre, err = whoIsCreator(snap, parent) + if err != nil { + return 0, 0, 0, false, err + } + preIndex = utils.Position(masternodes, pre) + } + curIndex := utils.Position(masternodes, signer) + if signer == x.signer { + log.Debug("Masternodes cycle info", "number of masternodes", len(masternodes), "previous", pre, "position", preIndex, "current", signer, "position", curIndex) + } + for i, s := range masternodes { + log.Debug("Masternode:", "index", i, "address", s.String()) + } + if (preIndex+1)%len(masternodes) == curIndex { + return len(masternodes), preIndex, curIndex, true, nil + } + return len(masternodes), preIndex, curIndex, false, nil +} + +// Copy from v1 +func whoIsCreator(snap *SnapshotV2, header *types.Header) (common.Address, error) { + if header.Number.Uint64() == 0 { + return common.Address{}, errors.New("Don't take block 0") + } + m, err := utils.EcrecoverV2(header, snap.sigcache) + if err != nil { + return common.Address{}, err + } + return m, nil +} + +// Copy from v1 +func (x *XDPoS_v2) GetMasternodes(chain consensus.ChainReader, header *types.Header) []common.Address { + n := header.Number.Uint64() + e := x.config.Epoch + switch { + case n%e == 0: + return utils.GetMasternodesFromCheckpointHeader(header) + case n%e != 0: + h := chain.GetHeaderByNumber(n - (n % e)) + return utils.GetMasternodesFromCheckpointHeader(h) + default: + return []common.Address{} + } +} + +// Copy from v1 +func (x *XDPoS_v2) GetSnapshot(chain consensus.ChainReader, header *types.Header) (*SnapshotV2, error) { + number := header.Number.Uint64() + log.Trace("get snapshot", "number", number, "hash", header.Hash()) + snap, err := x.snapshot(chain, number, header.Hash(), nil) + if err != nil { + return nil, err + } + return snap, nil +} + +// snapshot retrieves the authorization snapshot at a given point in time. +func (x *XDPoS_v2) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*SnapshotV2, error) { + // Search for a SnapshotV2 in memory or on disk for checkpoints + var ( + headers []*types.Header + snap *SnapshotV2 + ) + for snap == nil { + // If an in-memory SnapshotV2 was found, use that + if s, ok := x.recents.Get(hash); ok { + snap = s.(*SnapshotV2) + break + } + // If an on-disk checkpoint snapshot can be found, use that + // checkpoint snapshot = checkpoint - gap + if (number+x.config.Gap)%x.config.Epoch == 0 { + if s, err := loadSnapshot(x.signatures, x.db, hash); err == nil { + log.Trace("Loaded snapshot form disk", "number", number, "hash", hash) + snap = s + break + } + } + // If we're at 0 block, make a snapshot + // TODO: We may need to store snapshot at the v1 -> v2 switch block + if number == 0 { + genesis := chain.GetHeaderByNumber(0) + if err := x.VerifyHeader(chain, genesis, true); err != nil { + return nil, err + } + signers := make([]common.Address, (len(genesis.Extra)-utils.ExtraVanity-utils.ExtraSeal)/common.AddressLength) + for i := 0; i < len(signers); i++ { + copy(signers[i][:], genesis.Extra[utils.ExtraVanity+i*common.AddressLength:]) + } + snap = newSnapshot(x.signatures, 0, genesis.Hash(), x.currentRound, x.highestQuorumCert, signers) + if err := storeSnapshot(snap, x.db); err != nil { + return nil, err + } + log.Trace("Stored genesis voting snapshot to disk") + break + } + // No snapshot for this header, gather the header and move backward + var header *types.Header + if len(parents) > 0 { + // If we have explicit parents, pick from there (enforced) + header = parents[len(parents)-1] + if header.Hash() != hash || header.Number.Uint64() != number { + return nil, consensus.ErrUnknownAncestor + } + parents = parents[:len(parents)-1] + } else { + // No explicit parents (or no more left), reach out to the database + header = chain.GetHeader(hash, number) + if header == nil { + log.Error("[Seal] Failed due to no header found", "hash", hash, "number", number) + return nil, consensus.ErrUnknownAncestor + } + } + headers = append(headers, header) + number, hash = number-1, header.ParentHash + } + // Previous snapshot found, apply any pending headers on top of it + for i := 0; i < len(headers)/2; i++ { + headers[i], headers[len(headers)-1-i] = headers[len(headers)-1-i], headers[i] + } + snap, err := snap.apply(headers) + if err != nil { + return nil, err + } + x.recents.Add(snap.Hash, snap) + + // If we've generated a new checkpoint snapshot, save to disk + // TODO how to save correct snapshot + if uint64(snap.Round)%x.config.Epoch == x.config.Gap { + if err = storeSnapshot(snap, x.db); err != nil { + return nil, err + } + log.Trace("Stored snapshot to disk", "round number", snap.Round, "hash", snap.Hash) + } + return snap, err } func (x *XDPoS_v2) VerifyHeader(chain consensus.ChainReader, header *types.Header, fullVerify bool) error { return nil } +// Utils for test to check currentRound value +func (x *XDPoS_v2) GetProperties() (utils.Round, *utils.QuorumCert, *utils.QuorumCert) { + x.lock.Lock() + defer x.lock.Unlock() + return x.currentRound, x.lockQuorumCert, x.highestQuorumCert +} + /* SyncInfo workflow */ @@ -152,7 +493,7 @@ func (x *XDPoS_v2) VerifyVoteMessage(vote *utils.Vote) (bool, error) { 2. Verify blockInfo 3. Broadcast(Not part of consensus) */ - return x.verifyMsgSignature(utils.VoteSigHash(&vote.ProposedBlockInfo), vote.Signature) + return x.verifyMsgSignature(utils.VoteSigHash(vote.ProposedBlockInfo), vote.Signature) } // Consensus entry point for processing vote message to produce QC @@ -228,7 +569,7 @@ func (x *XDPoS_v2) TimeoutHandler(timeout *utils.Timeout) error { // 1. checkRoundNumber if timeout.Round != x.currentRound { - return fmt.Errorf("Timeout message round number: %v does not match currentRound: %v", timeout.Round, x.currentRound) + return &utils.ErrIncomingMessageRoundNotEqualCurrentRound{timeout.Round, x.currentRound} } // Collect timeout, generate TC isThresholdReached, numberOfTimeoutsInPool, pooledTimeouts := x.timeoutPool.Add(timeout) @@ -277,19 +618,43 @@ func (x *XDPoS_v2) onTimeoutPoolThresholdReached(pooledTimeouts map[common.Hash] /* Proposed Block workflow */ -func (x *XDPoS_v2) ProposedBlockHandler(blockChainReader consensus.ChainReader, blockInfo *utils.BlockInfo, quorumCert *utils.QuorumCert) error { +func (x *XDPoS_v2) ProposedBlockHandler(blockChainReader consensus.ChainReader, blockHeader *types.Header) error { x.lock.Lock() defer x.lock.Unlock() /* - 1. processQC(): process the QC inside the proposed block - 2. verifyVotingRule(): the proposed block's info is extracted into BlockInfo and verified for voting - 3. sendVote() + 1. Verify QC + 2. Generate blockInfo + 3. processQC(): process the QC inside the proposed block + 4. verifyVotingRule(): the proposed block's info is extracted into BlockInfo and verified for voting + 5. sendVote() */ - err := x.processQC(blockChainReader, quorumCert) + // Get QC and Round from Extra + var decodedExtraField utils.ExtraFields_v2 + err := utils.DecodeBytesExtraFields(blockHeader.Extra, &decodedExtraField) if err != nil { return err } + quorumCert := decodedExtraField.QuorumCert + round := decodedExtraField.Round + + err = x.verifyQC(quorumCert) + if err != nil { + log.Error("[ProposedBlockHandler] Fail to verify QC", "Extra round", round, "QC proposed BlockInfo Hash", quorumCert.ProposedBlockInfo.Hash) + return err + } + + // Generate blockInfo + blockInfo := &utils.BlockInfo{ + Hash: blockHeader.Hash(), + Round: round, + Number: blockHeader.Number, + } + err = x.processQC(blockChainReader, quorumCert) + if err != nil { + log.Error("[ProposedBlockHandler] Fail to processQC", "QC proposed blockInfo round number", quorumCert.ProposedBlockInfo.Round, "QC proposed blockInfo hash", quorumCert.ProposedBlockInfo.Hash) + return err + } verified, err := x.verifyVotingRule(blockChainReader, blockInfo, quorumCert) if err != nil { return err @@ -335,33 +700,41 @@ func (x *XDPoS_v2) verifyTC(timeoutCert *utils.TimeoutCert) error { // Update local QC variables including highestQC & lockQuorumCert, as well as commit the blocks that satisfy the algorithm requirements func (x *XDPoS_v2) processQC(blockChainReader consensus.ChainReader, quorumCert *utils.QuorumCert) error { + log.Trace("[ProcessQC][Before]", "HighQC", x.highestQuorumCert) // 1. Update HighestQC if x.highestQuorumCert == nil || (quorumCert.ProposedBlockInfo.Round > x.highestQuorumCert.ProposedBlockInfo.Round) { x.highestQuorumCert = quorumCert } // 2. Get QC from header and update lockQuorumCert(lockQuorumCert is the parent of highestQC) proposedBlockHeader := blockChainReader.GetHeaderByHash(quorumCert.ProposedBlockInfo.Hash) - // Extra field contain parent information - var decodedExtraField utils.ExtraFields_v2 - err := utils.DecodeBytesExtraFields(proposedBlockHeader.Extra, &decodedExtraField) - if err != nil { - return err - } - x.lockQuorumCert = &decodedExtraField.QuorumCert + if proposedBlockHeader.Number.Cmp(x.config.XDPoSV2Block) > 0 { + // Extra field contain parent information + var decodedExtraField utils.ExtraFields_v2 + err := utils.DecodeBytesExtraFields(proposedBlockHeader.Extra, &decodedExtraField) + if err != nil { + return err + } + if x.lockQuorumCert == nil || decodedExtraField.QuorumCert.ProposedBlockInfo.Round > x.lockQuorumCert.ProposedBlockInfo.Round { + x.lockQuorumCert = decodedExtraField.QuorumCert + } - proposedBlockRound := &decodedExtraField.Round - // 3. Update commit block info - _, err = x.commitBlocks(blockChainReader, proposedBlockHeader, proposedBlockRound) - if err != nil { - return err + proposedBlockRound := &decodedExtraField.Round + // 3. Update commit block info + _, err = x.commitBlocks(blockChainReader, proposedBlockHeader, proposedBlockRound) + if err != nil { + log.Error("[processQC] Fail to commitBlocks", "proposedBlockRound", proposedBlockRound) + return err + } } // 4. Set new round if quorumCert.ProposedBlockInfo.Round >= x.currentRound { err := x.setNewRound(quorumCert.ProposedBlockInfo.Round + 1) if err != nil { + log.Error("[processQC] Fail to setNewRound", "new round to set", quorumCert.ProposedBlockInfo.Round+1) return err } } + log.Trace("[ProcessQC][After]", "HighQC", x.highestQuorumCert) return nil } @@ -411,7 +784,11 @@ func (x *XDPoS_v2) verifyVotingRule(blockChainReader consensus.ChainReader, bloc if blockInfo.Round != x.currentRound { return false, nil } - isExtended, err := x.isExtendingFromAncestor(blockChainReader, blockInfo, &x.lockQuorumCert.ProposedBlockInfo) + // XDPoS v1.0 switch to v2.0, the proposed block can always pass voting rule + if x.lockQuorumCert == nil { + return true, nil + } + isExtended, err := x.isExtendingFromAncestor(blockChainReader, blockInfo, x.lockQuorumCert.ProposedBlockInfo) if err != nil { return false, err } @@ -436,7 +813,7 @@ func (x *XDPoS_v2) sendVote(blockInfo *utils.BlockInfo) error { x.highestVotedRound = x.currentRound voteMsg := &utils.Vote{ - ProposedBlockInfo: *blockInfo, + ProposedBlockInfo: blockInfo, Signature: signedHash, } x.broadcastToBftChannel(voteMsg) @@ -524,17 +901,44 @@ func (x *XDPoS_v2) getCurrentRoundMasterNodes() []common.Address { return []common.Address{} } -func (x *XDPoS_v2) getSyncInfo() utils.SyncInfo { - return utils.SyncInfo{ +/* + Testing tools +*/ + +func (x *XDPoS_v2) SetHighestQuorumCert(qc *utils.QuorumCert) { + x.highestQuorumCert = qc +} + +func (x *XDPoS_v2) getSyncInfo() *utils.SyncInfo { + return &utils.SyncInfo{ HighestQuorumCert: x.highestQuorumCert, HighestTimeoutCert: x.highestTimeoutCert, } } +func (x *XDPoS_v2) SetNewRoundFaker(newRound utils.Round, resetTimer bool) { + x.lock.Lock() + defer x.lock.Unlock() + // Reset a bunch of things + if resetTimer { + x.timeoutWorker.Reset() + } + x.currentRound = newRound +} + +// Utils for test to check currentRound value +func (x *XDPoS_v2) GetCurrentRound() utils.Round { + return x.currentRound +} + //TODO: find parent and grandparent and grandgrandparent block, check round number, if so, commit grandgrandparent -func (x *XDPoS_v2) commitBlocks(blockChainReader consensus.ChainReader, proposedBlockHeader *types.Header, proposedBlockRound *utils.Round) (bool, error) { +func (x *XDPoS_v2) commitBlocks(blockCahinReader consensus.ChainReader, proposedBlockHeader *types.Header, proposedBlockRound *utils.Round) (bool, error) { + // XDPoS v1.0 switch to v2.0, skip commit + if big.NewInt(0).Sub(proposedBlockHeader.Number, big.NewInt(2)).Cmp(x.config.XDPoSV2Block) <= 0 { + return false, nil + } // Find the last two parent block and check their rounds are the continous - parentBlock := blockChainReader.GetHeaderByHash(proposedBlockHeader.ParentHash) + parentBlock := blockCahinReader.GetHeaderByHash(proposedBlockHeader.ParentHash) var decodedExtraField utils.ExtraFields_v2 err := utils.DecodeBytesExtraFields(parentBlock.Extra, &decodedExtraField) @@ -546,7 +950,7 @@ func (x *XDPoS_v2) commitBlocks(blockChainReader consensus.ChainReader, proposed } // If parent round is continous, we check grandparent - grandParentBlock := blockChainReader.GetHeaderByHash(parentBlock.ParentHash) + grandParentBlock := blockCahinReader.GetHeaderByHash(parentBlock.ParentHash) err = utils.DecodeBytesExtraFields(grandParentBlock.Extra, &decodedExtraField) if err != nil { return false, err diff --git a/consensus/XDPoS/engines/engine_v2/snapshot.go b/consensus/XDPoS/engines/engine_v2/snapshot.go index da3b04244d..8a70fdb1ab 100644 --- a/consensus/XDPoS/engines/engine_v2/snapshot.go +++ b/consensus/XDPoS/engines/engine_v2/snapshot.go @@ -1 +1,133 @@ package engine_v2 + +import ( + "encoding/json" + "sort" + + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" + "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/ethdb" + lru "github.com/hashicorp/golang-lru" +) + +// Snapshot is the state of the smart contract validator list +type SnapshotV2 struct { + sigcache *lru.ARCCache // Cache of recent block signatures to speed up ecrecover + + Round utils.Round `json:"round"` // Round number + Number uint64 `json:"number"` // Block number where the snapshot was created + Hash common.Hash `json:"hash"` // Block hash where the snapshot was created + + // MasterNodes will get assigned on updateM1 + MasterNodes map[common.Address]struct{} `json:"masterNodes"` // Set of authorized master nodes at this moment +} + +// newSnapshot creates a new snapshot with the specified startup parameters. This +// method does not initialize the set of recent signers, so only ever use if for +// the genesis block. +func newSnapshot(sigcache *lru.ARCCache, number uint64, hash common.Hash, round utils.Round, qc *utils.QuorumCert, masternodes []common.Address) *SnapshotV2 { + snap := &SnapshotV2{ + sigcache: sigcache, + Round: round, + Number: number, + Hash: hash, + + MasterNodes: make(map[common.Address]struct{}), + } + for _, signer := range masternodes { + snap.MasterNodes[signer] = struct{}{} + } + return snap +} + +// loadSnapshot loads an existing snapshot from the database. +func loadSnapshot(sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash) (*SnapshotV2, error) { + blob, err := db.Get(append([]byte("XDPoS-"), hash[:]...)) + if err != nil { + return nil, err + } + snap := new(SnapshotV2) + if err := json.Unmarshal(blob, snap); err != nil { + return nil, err + } + snap.sigcache = sigcache + + return snap, nil +} + +// store inserts the SnapshotV2 into the database. +func storeSnapshot(s *SnapshotV2, db ethdb.Database) error { + blob, err := json.Marshal(s) + if err != nil { + return err + } + return db.Put(append([]byte("XDPoS-"), s.Hash[:]...), blob) +} + +// copy creates a deep copy of the SnapshotV2, though not the individual votes. +func (s *SnapshotV2) copy() *SnapshotV2 { + cpy := &SnapshotV2{ + sigcache: s.sigcache, + Round: s.Round, + Number: s.Number, + Hash: s.Hash, + MasterNodes: make(map[common.Address]struct{}), + } + for signer := range s.MasterNodes { + cpy.MasterNodes[signer] = struct{}{} + } + + return cpy +} + +// apply creates a new authorization SnapshotV2 by applying the given headers to +// the original one. +// TODO: XIN-100 +func (s *SnapshotV2) apply(headers []*types.Header) (*SnapshotV2, error) { + return s, nil + + // Allow passing in no headers for cleaner code + // if len(headers) == 0 { + // return s, nil + // } + // // Sanity check that the headers can be applied + // for i := 0; i < len(headers)-1; i++ { + // if headers[i+1].Number.Uint64() != headers[i].Number.Uint64()+1 { + // return nil, utils.ErrInvalidHeaderOrder + // } + // } + // if headers[0].Number.Uint64() != s.Number+1 { + // return nil, utils.ErrInvalidChild + // } + // // Iterate through the headers and create a new SnapshotV2 + // snap := s.copy() + // lastHeader := headers[len(headers)-1] + + // snap.Number += uint64(len(headers)) + // snap.Hash = lastHeader.Hash() + + // extraV2 := new(utils.ExtraFields_v2) + // err := utils.DecodeBytesExtraFields(lastHeader.Extra, &extraV2) + // if err != nil { + // return nil, err + // } + // snap.Round = extraV2.Round + // return snap, nil +} + +// signers retrieves the list of authorized signers in ascending order, convert into strings then use native sort lib +func (s *SnapshotV2) GetMasterNodes() []common.Address { + nodes := make([]common.Address, 0, len(s.MasterNodes)) + nodeStrs := make([]string, 0, len(s.MasterNodes)) + + for node := range s.MasterNodes { + nodeStrs = append(nodeStrs, node.Str()) + } + sort.Strings(nodeStrs) + for _, str := range nodeStrs { + nodes = append(nodes, common.StringToAddress(str)) + } + + return nodes +} diff --git a/consensus/XDPoS/engines/engine_v2/snapshot_test.go b/consensus/XDPoS/engines/engine_v2/snapshot_test.go new file mode 100644 index 0000000000..b29c29ff8f --- /dev/null +++ b/consensus/XDPoS/engines/engine_v2/snapshot_test.go @@ -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) + } +} diff --git a/consensus/XDPoS/utils/errors.go b/consensus/XDPoS/utils/errors.go index 5d7faf1a81..a79e110332 100644 --- a/consensus/XDPoS/utils/errors.go +++ b/consensus/XDPoS/utils/errors.go @@ -1,6 +1,9 @@ package utils -import "errors" +import ( + "errors" + "fmt" +) // Various error messages to mark blocks invalid. These should be private to // prevent engine specific errors from being referenced in the remainder of the @@ -60,6 +63,9 @@ var ( // be modified via out-of-range or non-contiguous headers. ErrInvalidVotingChain = errors.New("invalid voting chain") + ErrInvalidHeaderOrder = errors.New("invalid header order") + ErrInvalidChild = errors.New("invalid header child") + // errUnauthorized is returned if a header is signed by a non-authorized entity. ErrUnauthorized = errors.New("unauthorized") @@ -72,3 +78,12 @@ var ( ErrInvalidCheckpointValidators = errors.New("invalid validators list on checkpoint block") ) + +type ErrIncomingMessageRoundNotEqualCurrentRound struct { + IncomingRound Round + CurrentRound Round +} + +func (e *ErrIncomingMessageRoundNotEqualCurrentRound) Error() string { + return fmt.Sprintf("Timeout message round number: %v does not match currentRound: %v", e.IncomingRound, e.CurrentRound) +} diff --git a/consensus/XDPoS/utils/types.go b/consensus/XDPoS/utils/types.go index 8286f49960..722f4cf8d7 100644 --- a/consensus/XDPoS/utils/types.go +++ b/consensus/XDPoS/utils/types.go @@ -73,7 +73,7 @@ type BlockInfo struct { // Vote message in XDPoS 2.0 type Vote struct { - ProposedBlockInfo BlockInfo + ProposedBlockInfo *BlockInfo Signature Signature } @@ -91,7 +91,7 @@ type SyncInfo struct { // Quorum Certificate struct in XDPoS 2.0 type QuorumCert struct { - ProposedBlockInfo BlockInfo + ProposedBlockInfo *BlockInfo Signatures []Signature } @@ -105,7 +105,17 @@ type TimeoutCert struct { // The version byte (consensus version) is the first byte in header's extra and it's only valid with value >= 2 type ExtraFields_v2 struct { Round Round - QuorumCert QuorumCert + QuorumCert *QuorumCert +} + +// Encode XDPoS 2.0 extra fields into bytes +func (e *ExtraFields_v2) EncodeToBytes() ([]byte, error) { + bytes, err := rlp.EncodeToBytes(e) + if err != nil { + return nil, err + } + versionByte := []byte{2} + return append(versionByte, bytes...), nil } func rlpHash(x interface{}) (h common.Hash) { diff --git a/consensus/XDPoS/utils/types_test.go b/consensus/XDPoS/utils/types_test.go index 98fe4ed3a0..a50836bac8 100644 --- a/consensus/XDPoS/utils/types_test.go +++ b/consensus/XDPoS/utils/types_test.go @@ -10,10 +10,10 @@ import ( func toyExtraFields() *ExtraFields_v2 { round := Round(307) - blockInfo := BlockInfo{Hash: common.BigToHash(big.NewInt(2047)), Round: round - 1, Number: big.NewInt(1)} + blockInfo := &BlockInfo{Hash: common.BigToHash(big.NewInt(2047)), Round: round - 1, Number: big.NewInt(1)} signature := []byte{1, 2, 3, 4, 5, 6, 7, 8} signatures := []Signature{signature} - quorumCert := QuorumCert{ProposedBlockInfo: blockInfo, Signatures: signatures} + quorumCert := &QuorumCert{ProposedBlockInfo: blockInfo, Signatures: signatures} e := &ExtraFields_v2{Round: round, QuorumCert: quorumCert} return e } @@ -35,14 +35,14 @@ func TestExtraFieldsEncodeDecode(t *testing.T) { func TestHashAndSigHash(t *testing.T) { round := Round(307) - blockInfo1 := BlockInfo{Hash: common.BigToHash(big.NewInt(2047)), Round: round - 1, Number: big.NewInt(1)} - blockInfo2 := BlockInfo{Hash: common.BigToHash(big.NewInt(4095)), Round: round - 1, Number: big.NewInt(1)} + blockInfo1 := &BlockInfo{Hash: common.BigToHash(big.NewInt(2047)), Round: round - 1, Number: big.NewInt(1)} + blockInfo2 := &BlockInfo{Hash: common.BigToHash(big.NewInt(4095)), Round: round - 1, Number: big.NewInt(1)} signature1 := []byte{1, 2, 3, 4, 5, 6, 7, 8} signature2 := []byte{1, 2, 3, 4, 5, 6, 7, 7} signatures1 := []Signature{signature1} - quorumCert1 := QuorumCert{ProposedBlockInfo: blockInfo1, Signatures: signatures1} signatures2 := []Signature{signature2} - quorumCert2 := QuorumCert{ProposedBlockInfo: blockInfo1, Signatures: signatures2} + quorumCert1 := &QuorumCert{ProposedBlockInfo: blockInfo1, Signatures: signatures1} + quorumCert2 := &QuorumCert{ProposedBlockInfo: blockInfo1, Signatures: signatures2} vote1 := Vote{ProposedBlockInfo: blockInfo1, Signature: signature1} vote2 := Vote{ProposedBlockInfo: blockInfo1, Signature: signature2} if vote1.Hash() == vote2.Hash() { @@ -53,12 +53,12 @@ func TestHashAndSigHash(t *testing.T) { if timeout1.Hash() == timeout2.Hash() { t.Fatalf("Hash of two timeouts shouldn't equal") } - syncInfo1 := SyncInfo{HighestQuorumCert: &quorumCert1} - syncInfo2 := SyncInfo{HighestQuorumCert: &quorumCert2} + syncInfo1 := SyncInfo{HighestQuorumCert: quorumCert1} + syncInfo2 := SyncInfo{HighestQuorumCert: quorumCert2} if syncInfo1.Hash() == syncInfo2.Hash() { t.Fatalf("Hash of two sync info shouldn't equal") } - if VoteSigHash(&blockInfo1) == VoteSigHash(&blockInfo2) { + if VoteSigHash(blockInfo1) == VoteSigHash(blockInfo2) { t.Fatalf("SigHash of two block info shouldn't equal") } round2 := Round(999) diff --git a/consensus/XDPoS/utils/utils.go b/consensus/XDPoS/utils/utils.go index 4dfae64093..504c72a474 100644 --- a/consensus/XDPoS/utils/utils.go +++ b/consensus/XDPoS/utils/utils.go @@ -10,10 +10,12 @@ import ( "github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto/sha3" "github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/rlp" + lru "github.com/hashicorp/golang-lru" ) func Position(list []common.Address, x common.Address) int { @@ -151,14 +153,33 @@ func SigHash(header *types.Header) (hash common.Hash) { return hash } -// Encode XDPoS 2.0 extra fields into bytes -func (e *ExtraFields_v2) EncodeToBytes() ([]byte, error) { - bytes, err := rlp.EncodeToBytes(e) +func SigHashV2(header *types.Header) (hash common.Hash) { + hasher := sha3.NewKeccak256() + + err := rlp.Encode(hasher, []interface{}{ + header.ParentHash, + header.UncleHash, + header.Coinbase, + header.Root, + header.TxHash, + header.ReceiptHash, + header.Bloom, + header.Difficulty, + header.Number, + header.GasLimit, + header.GasUsed, + header.Time, + header.Extra, + header.MixDigest, + header.Nonce, + header.Validators, + header.Penalties, + }) if err != nil { - return nil, err + log.Debug("Fail to encode", err) } - versionByte := []byte{2} - return append(versionByte, bytes...), nil + hasher.Sum(hash[:0]) + return hash } // Decode extra fields for consensus version >= 2 (XDPoS 2.0 and future versions) @@ -174,4 +195,48 @@ func DecodeBytesExtraFields(b []byte, val interface{}) error { default: return fmt.Errorf("consensus version %d is not defined", b[0]) } -} \ No newline at end of file +} + +// 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 +} diff --git a/consensus/errors.go b/consensus/errors.go index cb3d7eecb0..77380ea15d 100644 --- a/consensus/errors.go +++ b/consensus/errors.go @@ -38,4 +38,6 @@ var ( ErrFailValidatorSignature = errors.New("missing validator in header") ErrNoValidatorSignature = errors.New("no validator in header") + + ErrNotReadyToPropose = errors.New("not ready to propose, QC is not ready") ) diff --git a/tests/consensus/adaptor_test.go b/consensus/tests/adaptor_test.go similarity index 73% rename from tests/consensus/adaptor_test.go rename to consensus/tests/adaptor_test.go index b96bf7d435..eae5ce9d86 100644 --- a/tests/consensus/adaptor_test.go +++ b/consensus/tests/adaptor_test.go @@ -1,16 +1,19 @@ -package consensus +package tests import ( "fmt" + "math/big" "testing" + "github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS" + "github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/params" "github.com/stretchr/testify/assert" ) func TestAdaptorShouldGetAuthorForDifferentConsensusVersion(t *testing.T) { - blockchain, _, currentBlock, _ := PrepareXDCTestBlockChain(t, 10, params.TestXDPoSMockChainConfigWithV2Engine) + blockchain, backend, currentBlock, _ := PrepareXDCTestBlockChainForV2Engine(t, 10, params.TestXDPoSMockChainConfigWithV2Engine) adaptor := blockchain.Engine().(*XDPoS.XDPoS) addressFromAdaptor, errorAdaptor := adaptor.Author(currentBlock.Header()) @@ -26,9 +29,17 @@ func TestAdaptorShouldGetAuthorForDifferentConsensusVersion(t *testing.T) { // Insert one more block to make it above 10, which means now we are on v2 of consensus engine // Insert block 11 + blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", 11) merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930" - block11, err := insertBlock(blockchain, 11, blockCoinBase, currentBlock, merkleRoot, nil, 1) + header := &types.Header{ + Root: common.HexToHash(merkleRoot), + Number: big.NewInt(int64(11)), + ParentHash: currentBlock.Hash(), + Coinbase: common.HexToAddress(blockCoinBase), + } + generateSignature(backend, header) + block11, err := insertBlock(blockchain, header) if err != nil { t.Fatal(err) } diff --git a/tests/consensus/block_signer_test.go b/consensus/tests/block_signer_test.go similarity index 78% rename from tests/consensus/block_signer_test.go rename to consensus/tests/block_signer_test.go index 35c799996a..4fdf41fa38 100644 --- a/tests/consensus/block_signer_test.go +++ b/consensus/tests/block_signer_test.go @@ -1,4 +1,4 @@ -package consensus +package tests import ( "fmt" @@ -6,6 +6,7 @@ import ( "reflect" "testing" + "github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/params" ) @@ -26,7 +27,13 @@ func TestNotUpdateSignerListIfNotOnGapBlock(t *testing.T) { //Get from block validator error message merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772" - blockA, err := insertBlockTxs(blockchain, 401, blockCoinbaseA, parentBlock, []*types.Transaction{tx}, merkleRoot, 1) + header := &types.Header{ + Root: common.HexToHash(merkleRoot), + Number: big.NewInt(int64(401)), + ParentHash: parentBlock.Hash(), + Coinbase: common.HexToAddress(blockCoinbaseA), + } + blockA, err := insertBlockTxs(blockchain, header, []*types.Transaction{tx}) if err != nil { t.Fatal(err) } @@ -54,7 +61,13 @@ func TestNotChangeSingerListIfNothingProposedOrVoted(t *testing.T) { // Insert block 450 blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", 450) merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930" - block, err := insertBlock(blockchain, 450, blockCoinBase, parentBlock, merkleRoot, nil, 1) + header := &types.Header{ + Root: common.HexToHash(merkleRoot), + Number: big.NewInt(int64(450)), + ParentHash: parentBlock.Hash(), + Coinbase: common.HexToAddress(blockCoinBase), + } + block, err := insertBlock(blockchain, header) if err != nil { t.Fatal(err) } @@ -89,7 +102,13 @@ func TestUpdateSignerListIfVotedBeforeGap(t *testing.T) { //Get from block validator error message merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772" - block449, err := insertBlockTxs(blockchain, 449, blockCoinbaseA, parentBlock, []*types.Transaction{tx}, merkleRoot, 1) + header := &types.Header{ + Root: common.HexToHash(merkleRoot), + Number: big.NewInt(int64(449)), + ParentHash: parentBlock.Hash(), + Coinbase: common.HexToAddress(blockCoinbaseA), + } + block449, err := insertBlockTxs(blockchain, header, []*types.Transaction{tx}) if err != nil { t.Fatal(err) } @@ -113,7 +132,13 @@ func TestUpdateSignerListIfVotedBeforeGap(t *testing.T) { // Now, let's mine another block to trigger the GAP block signerList update block450CoinbaseAddress := "0xaaa0000000000000000000000000000000000450" merkleRoot = "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772" - block450, err := insertBlock(blockchain, 450, block450CoinbaseAddress, parentBlock, merkleRoot, nil, 1) + header = &types.Header{ + Root: common.HexToHash(merkleRoot), + Number: big.NewInt(int64(450)), + ParentHash: parentBlock.Hash(), + Coinbase: common.HexToAddress(block450CoinbaseAddress), + } + block450, err := insertBlock(blockchain, header) if err != nil { t.Fatal(err) } @@ -147,7 +172,13 @@ func TestCallUpdateM1WithSmartContractTranscation(t *testing.T) { //Get from block validator error message merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772" - blockA, err := insertBlockTxs(blockchain, 450, blockCoinbaseA, currentBlock, []*types.Transaction{tx}, merkleRoot, 1) + header := &types.Header{ + Root: common.HexToHash(merkleRoot), + Number: big.NewInt(int64(450)), + ParentHash: currentBlock.Hash(), + Coinbase: common.HexToAddress(blockCoinbaseA), + } + blockA, err := insertBlockTxs(blockchain, header, []*types.Transaction{tx}) if err != nil { t.Fatal(err) } @@ -189,7 +220,13 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) { } merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772" - blockA, err := insertBlockTxs(blockchain, 450, blockCoinbaseA, currentBlock, []*types.Transaction{tx}, merkleRoot, 1) + header := &types.Header{ + Root: common.HexToHash(merkleRoot), + Number: big.NewInt(int64(450)), + ParentHash: currentBlock.Hash(), + Coinbase: common.HexToAddress(blockCoinbaseA), + } + blockA, err := insertBlockTxs(blockchain, header, []*types.Transaction{tx}) if err != nil { t.Fatal(err) } @@ -217,7 +254,13 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) { } merkleRoot = "068dfa09d7b4093441c0cc4d9807a71bc586f6101c072d939b214c21cd136eb3" - block450B, err := insertBlockTxs(blockchain, 450, blockCoinBase450B, currentBlock, []*types.Transaction{tx}, merkleRoot, 1) + header = &types.Header{ + Root: common.HexToHash(merkleRoot), + Number: big.NewInt(int64(450)), + ParentHash: currentBlock.Hash(), + Coinbase: common.HexToAddress(blockCoinBase450B), + } + block450B, err := insertBlockTxs(blockchain, header, []*types.Transaction{tx}) if err != nil { t.Fatal(err) } @@ -240,7 +283,13 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) { blockCoinBase451B := "0xbbb0000000000000000000000000000000000451" merkleRoot = "068dfa09d7b4093441c0cc4d9807a71bc586f6101c072d939b214c21cd136eb3" - block451B, err := insertBlock(blockchain, 451, blockCoinBase451B, block450B, merkleRoot, nil, 1) + header = &types.Header{ + Root: common.HexToHash(merkleRoot), + Number: big.NewInt(int64(451)), + ParentHash: block450B.Hash(), + Coinbase: common.HexToAddress(blockCoinBase451B), + } + block451B, err := insertBlock(blockchain, header) if err != nil { t.Fatal(err) @@ -316,7 +365,13 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin transferTransaction := transferTx(t, acc1Addr, 999) merkleRoot := "ea465415b60d88429f181fec9fae67c0f19cbf5a4fa10971d96d4faa57d96ffa" - blockA, err := insertBlockTxs(blockchain, 450, blockCoinbaseA, parentBlock, []*types.Transaction{tx, transferTransaction}, merkleRoot, 1) + header := &types.Header{ + Root: common.HexToHash(merkleRoot), + Number: big.NewInt(int64(450)), + ParentHash: parentBlock.Hash(), + Coinbase: common.HexToAddress(blockCoinbaseA), + } + blockA, err := insertBlockTxs(blockchain, header, []*types.Transaction{tx, transferTransaction}) if err != nil { t.Fatal(err) } @@ -351,7 +406,13 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin transferTransaction = transferTx(t, acc1Addr, 888) merkleRoot = "184edaddeafc2404248f896ae46be503ae68949896c8eb6b6ad43695581e5022" - block450B, err := insertBlockTxs(blockchain, 450, blockCoinBase450B, parentBlock, []*types.Transaction{tx, transferTransaction}, merkleRoot, 1) + header = &types.Header{ + Root: common.HexToHash(merkleRoot), + Number: big.NewInt(int64(450)), + ParentHash: parentBlock.Hash(), + Coinbase: common.HexToAddress(blockCoinBase450B), + } + block450B, err := insertBlockTxs(blockchain, header, []*types.Transaction{tx, transferTransaction}) if err != nil { t.Fatal(err) } @@ -378,7 +439,13 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin blockCoinBase451B := "0xbbb0000000000000000000000000000000000451" merkleRoot = "184edaddeafc2404248f896ae46be503ae68949896c8eb6b6ad43695581e5022" - block451B, err := insertBlock(blockchain, 451, blockCoinBase451B, block450B, merkleRoot, nil, 1) + header = &types.Header{ + Root: common.HexToHash(merkleRoot), + Number: big.NewInt(int64(451)), + ParentHash: block450B.Hash(), + Coinbase: common.HexToAddress(blockCoinBase451B), + } + block451B, err := insertBlock(blockchain, header) if err != nil { t.Fatal(err) @@ -440,7 +507,13 @@ func TestVoteShouldNotBeAffectedByFork(t *testing.T) { // Insert normal blocks 450 A blockCoinBase450A := "0xaaa0000000000000000000000000000000000450" merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930" - block450A, err := insertBlock(blockchain, 450, blockCoinBase450A, parentBlock, merkleRoot, nil, 1) + header := &types.Header{ + Root: common.HexToHash(merkleRoot), + Number: big.NewInt(int64(450)), + ParentHash: parentBlock.Hash(), + Coinbase: common.HexToAddress(blockCoinBase450A), + } + block450A, err := insertBlock(blockchain, header) if err != nil { t.Fatal(err) } @@ -453,7 +526,13 @@ func TestVoteShouldNotBeAffectedByFork(t *testing.T) { } merkleRoot = "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772" - block451A, err := insertBlockTxs(blockchain, 451, blockCoinbase451A, block450A, []*types.Transaction{tx}, merkleRoot, 1) + header = &types.Header{ + Root: common.HexToHash(merkleRoot), + Number: big.NewInt(int64(451)), + ParentHash: block450A.Hash(), + Coinbase: common.HexToAddress(blockCoinbase451A), + } + block451A, err := insertBlockTxs(blockchain, header, []*types.Transaction{tx}) if err != nil { t.Fatal(err) } @@ -476,21 +555,39 @@ func TestVoteShouldNotBeAffectedByFork(t *testing.T) { // Insert forked Block 450 B blockCoinBase450B := "0xbbb0000000000000000000000000000000000450" merkleRoot = "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930" - block450B, err := insertBlock(blockchain, 450, blockCoinBase450B, parentBlock, merkleRoot, nil, 1) + header = &types.Header{ + Root: common.HexToHash(merkleRoot), + Number: big.NewInt(int64(450)), + ParentHash: parentBlock.Hash(), + Coinbase: common.HexToAddress(blockCoinBase450B), + } + block450B, err := insertBlock(blockchain, header) if err != nil { t.Fatal(err) } blockCoinBase451B := "0xbbb0000000000000000000000000000000000451" merkleRoot = "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930" - block451B, err := insertBlock(blockchain, 451, blockCoinBase451B, block450B, merkleRoot, nil, 1) + header = &types.Header{ + Root: common.HexToHash(merkleRoot), + Number: big.NewInt(int64(451)), + ParentHash: block450B.Hash(), + Coinbase: common.HexToAddress(blockCoinBase451B), + } + block451B, err := insertBlock(blockchain, header) if err != nil { t.Fatal(err) } blockCoinBase452B := "0xbbb0000000000000000000000000000000000452" merkleRoot = "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930" - block452B, err := insertBlock(blockchain, 452, blockCoinBase452B, block451B, merkleRoot, nil, 1) + header = &types.Header{ + Root: common.HexToHash(merkleRoot), + Number: big.NewInt(int64(452)), + ParentHash: block451B.Hash(), + Coinbase: common.HexToAddress(blockCoinBase452B), + } + block452B, err := insertBlock(blockchain, header) if err != nil { t.Fatal(err) } diff --git a/tests/consensus/blockchain_race_condition_test.go b/consensus/tests/blockchain_race_condition_test.go similarity index 82% rename from tests/consensus/blockchain_race_condition_test.go rename to consensus/tests/blockchain_race_condition_test.go index 409dfef7c3..2156d78096 100644 --- a/tests/consensus/blockchain_race_condition_test.go +++ b/consensus/tests/blockchain_race_condition_test.go @@ -1,9 +1,10 @@ -package consensus +package tests import ( "math/big" "testing" + "github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/params" ) @@ -39,7 +40,15 @@ func TestRaceConditionOnBlockchainReadAndWrite(t *testing.T) { transferTransaction := transferTx(t, acc1Addr, 999) merkleRoot := "ea465415b60d88429f181fec9fae67c0f19cbf5a4fa10971d96d4faa57d96ffa" - blockA, err := insertBlockTxs(blockchain, 450, blockCoinbaseA, parentBlock, []*types.Transaction{tx, transferTransaction}, merkleRoot, 1) + + header := &types.Header{ + Root: common.HexToHash(merkleRoot), + Number: big.NewInt(int64(450)), + ParentHash: parentBlock.Hash(), + Coinbase: common.HexToAddress(blockCoinbaseA), + } + + blockA, err := insertBlockTxs(blockchain, header, []*types.Transaction{tx, transferTransaction}) if err != nil { t.Fatal(err) } @@ -74,7 +83,16 @@ func TestRaceConditionOnBlockchainReadAndWrite(t *testing.T) { transferTransaction = transferTx(t, acc1Addr, 888) merkleRoot = "184edaddeafc2404248f896ae46be503ae68949896c8eb6b6ad43695581e5022" - block450B, err := insertBlockTxs(blockchain, 450, blockCoinBase450B, parentBlock, []*types.Transaction{tx, transferTransaction}, merkleRoot, 2) + + header = &types.Header{ + Root: common.HexToHash(merkleRoot), + Number: big.NewInt(int64(450)), + ParentHash: parentBlock.Hash(), + Coinbase: common.HexToAddress(blockCoinBase450B), + Difficulty: big.NewInt(2), + } + + block450B, err := insertBlockTxs(blockchain, header, []*types.Transaction{tx, transferTransaction}) if err != nil { t.Fatal(err) } @@ -104,7 +122,14 @@ func TestRaceConditionOnBlockchainReadAndWrite(t *testing.T) { blockCoinBase451B := "0xbbb0000000000000000000000000000000000451" merkleRoot = "184edaddeafc2404248f896ae46be503ae68949896c8eb6b6ad43695581e5022" - block451B, err := insertBlock(blockchain, 451, blockCoinBase451B, block450B, merkleRoot, nil, 3) + header = &types.Header{ + Root: common.HexToHash(merkleRoot), + Number: big.NewInt(int64(451)), + ParentHash: block450B.Hash(), + Coinbase: common.HexToAddress(blockCoinBase451B), + Difficulty: big.NewInt(3), + } + block451B, err := insertBlock(blockchain, header) if err != nil { t.Fatal(err) diff --git a/tests/consensus/countdown_test.go b/consensus/tests/countdown_test.go similarity index 86% rename from tests/consensus/countdown_test.go rename to consensus/tests/countdown_test.go index e0f0f9418f..cfc4c373c8 100644 --- a/tests/consensus/countdown_test.go +++ b/consensus/tests/countdown_test.go @@ -1,4 +1,4 @@ -package consensus +package tests import ( "testing" @@ -10,7 +10,7 @@ import ( ) func TestCountdownTimeoutToSendTimeoutMessage(t *testing.T) { - blockchain, _, _, _ := PrepareXDCTestBlockChain(t, 11, params.TestXDPoSMockChainConfigWithV2Engine) + blockchain, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 11, params.TestXDPoSMockChainConfigWithV2Engine) engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2 engineV2.SetNewRoundFaker(utils.Round(1), true) diff --git a/tests/consensus/proposed_block_test.go b/consensus/tests/proposed_block_test.go similarity index 76% rename from tests/consensus/proposed_block_test.go rename to consensus/tests/proposed_block_test.go index 50e4584a3b..dc76d9f22e 100644 --- a/tests/consensus/proposed_block_test.go +++ b/consensus/tests/proposed_block_test.go @@ -1,7 +1,6 @@ -package consensus +package tests import ( - "math/big" "testing" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS" @@ -24,20 +23,14 @@ func TestProposedBlockMessageHandlerSuccessfullyGenerateVote(t *testing.T) { t.Fatal("Fail to decode extra data", err) } - proposedBlockInfo := &utils.BlockInfo{ - Hash: currentBlock.Hash(), - Round: utils.Round(11), - Number: big.NewInt(11), - } - - err = engineV2.ProposedBlockHandler(blockchain, proposedBlockInfo, &extraField.QuorumCert) + err = engineV2.ProposedBlockHandler(blockchain, currentBlock.Header()) if err != nil { t.Fatal("Fail propose proposedBlock handler", err) } voteMsg := <-engineV2.BroadcastCh assert.NotNil(t, voteMsg) - assert.Equal(t, proposedBlockInfo.Hash, voteMsg.(*utils.Vote).ProposedBlockInfo.Hash) + assert.Equal(t, currentBlock.Hash(), voteMsg.(*utils.Vote).ProposedBlockInfo.Hash) round, _, highestQC := engineV2.GetProperties() // Shoud not trigger setNewRound diff --git a/tests/consensus/test_helper.go b/consensus/tests/test_helper.go similarity index 79% rename from tests/consensus/test_helper.go rename to consensus/tests/test_helper.go index 9514b44a6d..5a134cc5d9 100644 --- a/tests/consensus/test_helper.go +++ b/consensus/tests/test_helper.go @@ -1,4 +1,4 @@ -package consensus +package tests import ( "bytes" @@ -245,8 +245,13 @@ func PrepareXDCTestBlockChain(t *testing.T, numOfBlocks int, chainConfig *params for i := 1; i <= numOfBlocks; i++ { blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", i) merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930" - - block, err := insertBlock(blockchain, i, blockCoinBase, currentBlock, merkleRoot, nil, 1) + header := &types.Header{ + Root: common.HexToHash(merkleRoot), + Number: big.NewInt(int64(i)), + ParentHash: currentBlock.Hash(), + Coinbase: common.HexToAddress(blockCoinBase), + } + block, err := insertBlock(blockchain, header) if err != nil { t.Fatal(err) } @@ -283,19 +288,19 @@ func PrepareXDCTestBlockChainForV2Engine(t *testing.T, numOfBlocks int, chainCon merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930" // Build engine v2 compatible extra data field - proposedBlockInfo := utils.BlockInfo{ + proposedBlockInfo := &utils.BlockInfo{ Hash: currentBlock.Hash(), Round: utils.Round(i - 1), Number: big.NewInt(int64(i - 1)), } // Genrate QC - signedHash, err := signFn(accounts.Account{Address: signer}, utils.VoteSigHash(&proposedBlockInfo).Bytes()) + signedHash, err := signFn(accounts.Account{Address: signer}, utils.VoteSigHash(proposedBlockInfo).Bytes()) if err != nil { panic(fmt.Errorf("Error generate QC by creating signedHash: %v", err)) } var signatures []utils.Signature signatures = append(signatures, signedHash) - quorumCert := utils.QuorumCert{ + quorumCert := &utils.QuorumCert{ ProposedBlockInfo: proposedBlockInfo, Signatures: signatures, } @@ -309,7 +314,15 @@ func PrepareXDCTestBlockChainForV2Engine(t *testing.T, numOfBlocks int, chainCon panic(fmt.Errorf("Error encode extra into bytes: %v", err)) } - block, err := insertBlock(blockchain, i, blockCoinBase, currentBlock, merkleRoot, extraInBytes, 1) + header := &types.Header{ + Root: common.HexToHash(merkleRoot), + Number: big.NewInt(int64(i)), + ParentHash: currentBlock.Hash(), + Coinbase: common.HexToAddress(blockCoinBase), + Extra: extraInBytes, + Validator: signedHash, + } + block, err := insertBlock(blockchain, header) if err != nil { t.Fatal(err) } @@ -324,16 +337,27 @@ func PrepareXDCTestBlockChainForV2Engine(t *testing.T, numOfBlocks int, chainCon return blockchain, backend, currentBlock, signer } +func generateSignature(backend *backends.SimulatedBackend, header *types.Header) error { + signer, signFn, err := backends.SimulateWalletAddressAndSignFn() + if err != nil { + panic(fmt.Errorf("Error while creating simulated wallet for generating singer address and signer fn: %v", err)) + } + + signature, err := signFn(accounts.Account{Address: signer}, utils.SigHashV2(header).Bytes()) + if err != nil { + return err + } + header.Validator = signature + return nil +} + // insert Block without transcation attached -func insertBlock(blockchain *BlockChain, blockNum int, blockCoinBase string, parentBlock *types.Block, root string, customExtra []byte, difficulty int64) (*types.Block, error) { +func insertBlock(blockchain *BlockChain, header *types.Header) (*types.Block, error) { + header.ReceiptHash = common.HexToHash("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") block, err := createXDPoSTestBlock( blockchain, - parentBlock.Hash().Hex(), - blockCoinBase, blockNum, nil, - "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - common.HexToHash(root), - customExtra, - difficulty, + header, + nil, ) if err != nil { return nil, err @@ -347,15 +371,20 @@ func insertBlock(blockchain *BlockChain, blockNum int, blockCoinBase string, par } // insert Block with transcation attached -func insertBlockTxs(blockchain *BlockChain, blockNum int, blockCoinBase string, parentBlock *types.Block, txs []*types.Transaction, root string, difficulty int64) (*types.Block, error) { +func insertBlockTxs(blockchain *BlockChain, header *types.Header, txs []*types.Transaction) (*types.Block, error) { + /* + header := types.Header{ + Root: common.HexToHash(root), + Number: big.NewInt(int64(blockNum)), + ParentHash: parentBlock.Hash(), + Coinbase: common.HexToAddress(blockCoinBase), + } + */ + header.ReceiptHash = common.HexToHash("0x9319777b782ba2c83a33c995481ff894ac96d9a92a1963091346a3e1e386705c") block, err := createXDPoSTestBlock( blockchain, - parentBlock.Hash().Hex(), - blockCoinBase, blockNum, txs, - "0x9319777b782ba2c83a33c995481ff894ac96d9a92a1963091346a3e1e386705c", - common.HexToHash(root), - nil, - difficulty, + header, + txs, ) if err != nil { return nil, err @@ -368,35 +397,56 @@ func insertBlockTxs(blockchain *BlockChain, blockNum int, blockCoinBase string, return block, nil } -func createXDPoSTestBlock(bc *BlockChain, parentHash, coinbase string, number int, txs []*types.Transaction, receiptHash string, root common.Hash, customExtra []byte, difficulty int64) (*types.Block, error) { - if customExtra == nil { +//func createXDPoSTestBlock(bc *BlockChain, parentHash, coinbase string, number int, txs []*types.Transaction, receiptHash string, root common.Hash, customExtra []byte, signer common.Address) (*types.Block, error) { +func createXDPoSTestBlock(bc *BlockChain, customHeader *types.Header, txs []*types.Transaction) (*types.Block, error) { + if customHeader.Extra == nil { extraSubstring := "d7830100018358444388676f312e31342e31856c696e75780000000000000000b185dc0d0e917d18e5dbf0746be6597d3331dd27ea0554e6db433feb2e81730b20b2807d33a1527bf43cd3bc057aa7f641609c2551ebe2fd575f4db704fbf38101" // Grabbed from existing mainnet block, it does not have any meaning except for the length validation - //ReceiptHash = "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" - //Root := "0xc99c095e53ff1afe3b86750affd13c7550a2d24d51fb8e41b3c3ef2ea8274bcc" - customExtra, _ = hex.DecodeString(extraSubstring) + customHeader.Extra, _ = hex.DecodeString(extraSubstring) } - + var difficulty *big.Int + if customHeader.Difficulty == nil { + difficulty = big.NewInt(1) + } else { + difficulty = customHeader.Difficulty + } + /* + header := types.Header{ + ParentHash: common.HexToHash(parentHash), + UncleHash: types.EmptyUncleHash, + TxHash: types.EmptyRootHash, + // ReceiptHash: types.EmptyRootHash, + ReceiptHash: common.HexToHash(receiptHash), + Root: root, + Coinbase: common.HexToAddress(coinbase), + Difficulty: big.NewInt(int64(1)), + Number: big.NewInt(int64(number)), + GasLimit: 1200000000, + Time: big.NewInt(int64(number * 10)), + Extra: customExtra, + Validator: signer[:], + } + */ header := types.Header{ - ParentHash: common.HexToHash(parentHash), + ParentHash: customHeader.ParentHash, UncleHash: types.EmptyUncleHash, TxHash: types.EmptyRootHash, // ReceiptHash: types.EmptyRootHash, - ReceiptHash: common.HexToHash(receiptHash), - Root: root, - Coinbase: common.HexToAddress(coinbase), - Difficulty: big.NewInt(difficulty), - Number: big.NewInt(int64(number)), + ReceiptHash: customHeader.ReceiptHash, + Root: customHeader.Root, + Coinbase: customHeader.Coinbase, + Difficulty: difficulty, + Number: customHeader.Number, GasLimit: 1200000000, - Time: big.NewInt(int64(number * 10)), - Extra: customExtra, + Time: big.NewInt(customHeader.Number.Int64() * 10), + Extra: customHeader.Extra, + Validator: customHeader.Validator, } - var block *types.Block if len(txs) == 0 { block = types.NewBlockWithHeader(&header) } else { // Prepare Receipt - statedb, err := bc.StateAt(bc.GetBlockByNumber(uint64(number - 1)).Root()) //Get parent root + statedb, err := bc.StateAt(bc.GetBlockByNumber(customHeader.Number.Uint64() - 1).Root()) //Get parent root if err != nil { return nil, fmt.Errorf("%v when get state", err) } diff --git a/tests/consensus/timeout_test.go b/consensus/tests/timeout_test.go similarity index 83% rename from tests/consensus/timeout_test.go rename to consensus/tests/timeout_test.go index 119da0bf3d..8041803197 100644 --- a/tests/consensus/timeout_test.go +++ b/consensus/tests/timeout_test.go @@ -1,4 +1,4 @@ -package consensus +package tests import ( "testing" @@ -11,7 +11,7 @@ import ( // Timeout handler func TestTimeoutMessageHandlerSuccessfullyGenerateTCandSyncInfo(t *testing.T) { - blockchain, _, _, _ := PrepareXDCTestBlockChain(t, 11, params.TestXDPoSMockChainConfigWithV2Engine) + blockchain, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 11, params.TestXDPoSMockChainConfigWithV2Engine) engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2 // Set round to 1 @@ -49,11 +49,11 @@ func TestTimeoutMessageHandlerSuccessfullyGenerateTCandSyncInfo(t *testing.T) { assert.NotNil(t, syncInfoMsg) - // Should have QC, however, we did not inilise it, hence will show default empty value - qc := syncInfoMsg.(utils.SyncInfo).HighestQuorumCert - assert.NotNil(t, qc) + // Shouldn't have QC, however, we did not inilise it, hence will show default empty value + qc := syncInfoMsg.(*utils.SyncInfo).HighestQuorumCert + assert.Nil(t, qc) - tc := syncInfoMsg.(utils.SyncInfo).HighestTimeoutCert + tc := syncInfoMsg.(*utils.SyncInfo).HighestTimeoutCert assert.NotNil(t, tc) assert.Equal(t, tc.Round, utils.Round(1)) sigatures := []utils.Signature{[]byte{1}, []byte{2}, []byte{3}} @@ -62,7 +62,7 @@ func TestTimeoutMessageHandlerSuccessfullyGenerateTCandSyncInfo(t *testing.T) { } func TestThrowErrorIfTimeoutMsgRoundNotEqualToCurrentRound(t *testing.T) { - blockchain, _, _, _ := PrepareXDCTestBlockChain(t, 11, params.TestXDPoSMockChainConfigWithV2Engine) + blockchain, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 11, params.TestXDPoSMockChainConfigWithV2Engine) engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2 // Set round to 3 diff --git a/tests/consensus/vote_test.go b/consensus/tests/vote_test.go similarity index 91% rename from tests/consensus/vote_test.go rename to consensus/tests/vote_test.go index 572d92eaa4..857e264695 100644 --- a/tests/consensus/vote_test.go +++ b/consensus/tests/vote_test.go @@ -1,4 +1,4 @@ -package consensus +package tests import ( "math/big" @@ -26,7 +26,7 @@ func TestVoteMessageHandlerSuccessfullyGeneratedAndProcessQC(t *testing.T) { engineV2.SetNewRoundFaker(utils.Round(11), false) // Create two timeout message which will not reach vote pool threshold voteMsg := &utils.Vote{ - ProposedBlockInfo: *blockInfo, + ProposedBlockInfo: blockInfo, Signature: []byte{1}, } @@ -35,10 +35,10 @@ func TestVoteMessageHandlerSuccessfullyGeneratedAndProcessQC(t *testing.T) { currentRound, lockQuorumCert, highestQuorumCert := engineV2.GetProperties() // Inilised with nil and 0 round assert.Nil(t, lockQuorumCert) - assert.Equal(t, utils.Round(0), highestQuorumCert.ProposedBlockInfo.Round) + assert.Nil(t, highestQuorumCert) assert.Equal(t, utils.Round(11), currentRound) voteMsg = &utils.Vote{ - ProposedBlockInfo: *blockInfo, + ProposedBlockInfo: blockInfo, Signature: []byte{2}, } err = engineV2.VoteHandler(blockchain, voteMsg) @@ -46,13 +46,13 @@ func TestVoteMessageHandlerSuccessfullyGeneratedAndProcessQC(t *testing.T) { currentRound, lockQuorumCert, highestQuorumCert = engineV2.GetProperties() // Still using the initlised value because we did not yet go to the next round assert.Nil(t, lockQuorumCert) - assert.Equal(t, utils.Round(0), highestQuorumCert.ProposedBlockInfo.Round) + assert.Nil(t, highestQuorumCert) assert.Equal(t, utils.Round(11), currentRound) // Create a vote message that should trigger vote pool hook and increment the round to 12 voteMsg = &utils.Vote{ - ProposedBlockInfo: *blockInfo, + ProposedBlockInfo: blockInfo, Signature: []byte{3}, } @@ -80,7 +80,7 @@ func TestThrowErrorIfVoteMsgRoundNotEqualToCurrentRound(t *testing.T) { // Set round to 13 engineV2.SetNewRoundFaker(utils.Round(13), false) voteMsg := &utils.Vote{ - ProposedBlockInfo: *blockInfo, + ProposedBlockInfo: blockInfo, Signature: []byte{1}, } @@ -112,7 +112,7 @@ func TestProcessVoteMsgThenTimeoutMsg(t *testing.T) { } // Create two vote message which will not reach vote pool threshold voteMsg := &utils.Vote{ - ProposedBlockInfo: *blockInfo, + ProposedBlockInfo: blockInfo, Signature: []byte{1}, } @@ -121,11 +121,11 @@ func TestProcessVoteMsgThenTimeoutMsg(t *testing.T) { currentRound, lockQuorumCert, highestQuorumCert := engineV2.GetProperties() // Inilised with nil and 0 round assert.Nil(t, lockQuorumCert) - assert.Equal(t, utils.Round(0), highestQuorumCert.ProposedBlockInfo.Round) + assert.Nil(t, highestQuorumCert) assert.Equal(t, utils.Round(11), currentRound) voteMsg = &utils.Vote{ - ProposedBlockInfo: *blockInfo, + ProposedBlockInfo: blockInfo, Signature: []byte{2}, } err = engineV2.VoteHandler(blockchain, voteMsg) @@ -135,7 +135,7 @@ func TestProcessVoteMsgThenTimeoutMsg(t *testing.T) { // Create a vote message that should trigger vote pool hook voteMsg = &utils.Vote{ - ProposedBlockInfo: *blockInfo, + ProposedBlockInfo: blockInfo, Signature: []byte{3}, } @@ -194,11 +194,11 @@ func TestProcessVoteMsgThenTimeoutMsg(t *testing.T) { assert.NotNil(t, syncInfoMsg) // Should have HighestQuorumCert from previous round votes - qc := syncInfoMsg.(utils.SyncInfo).HighestQuorumCert + qc := syncInfoMsg.(*utils.SyncInfo).HighestQuorumCert assert.NotNil(t, qc) assert.Equal(t, utils.Round(11), qc.ProposedBlockInfo.Round) - tc := syncInfoMsg.(utils.SyncInfo).HighestTimeoutCert + tc := syncInfoMsg.(*utils.SyncInfo).HighestTimeoutCert assert.NotNil(t, tc) assert.Equal(t, utils.Round(12), tc.Round) sigatures := []utils.Signature{[]byte{1}, []byte{2}, []byte{3}} diff --git a/eth/bfter/bft_test.go b/eth/bft/bft_hander_test.go similarity index 68% rename from eth/bfter/bft_test.go rename to eth/bft/bft_hander_test.go index 887613229b..3d09571cda 100644 --- a/eth/bfter/bft_test.go +++ b/eth/bft/bft_hander_test.go @@ -1,4 +1,4 @@ -package bfter +package bft import ( "fmt" @@ -11,13 +11,17 @@ import ( "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/engines/engine_v2" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" "github.com/XinFinOrg/XDPoSChain/core" + "github.com/stretchr/testify/assert" ) // make different votes based on Signatures func makeVotes(n int) []utils.Vote { var votes []utils.Vote for i := 0; i < n; i++ { - votes = append(votes, utils.Vote{Signature: []byte{byte(i)}}) + votes = append(votes, utils.Vote{ + ProposedBlockInfo: &utils.BlockInfo{}, + Signature: []byte{byte(i)}, + }) } return votes } @@ -101,7 +105,7 @@ func TestDuplicateVotes(t *testing.T) { atomic.AddUint32(&broadcastCounter, 1) } - vote := utils.Vote{} + vote := utils.Vote{ProposedBlockInfo: &utils.BlockInfo{}} // send twice tester.bfter.Vote(&vote) @@ -132,7 +136,7 @@ func TestNotBoardcastInvalidVote(t *testing.T) { atomic.AddUint32(&broadcastCounter, 1) } - vote := utils.Vote{} + vote := utils.Vote{ProposedBlockInfo: &utils.BlockInfo{}} tester.bfter.Vote(&vote) time.Sleep(50 * time.Millisecond) @@ -143,3 +147,59 @@ func TestNotBoardcastInvalidVote(t *testing.T) { // TODO: SyncInfo and Timeout Test, should be same as Vote. // Once all test on vote covered, then duplicate to others + +func TestTimeoutHandler(t *testing.T) { + tester := newTester() + verifyCounter := uint32(0) + handlerCounter := uint32(0) + broadcastCounter := uint32(0) + targetVotes := 1 + + tester.bfter.consensus.verifyTimeout = func(timeout *utils.Timeout) error { + atomic.AddUint32(&verifyCounter, 1) + return nil + } + + tester.bfter.consensus.timeoutHandler = func(timeout *utils.Timeout) error { + atomic.AddUint32(&handlerCounter, 1) + return nil + } + + tester.bfter.broadcast.Timeout = func(*utils.Timeout) { + atomic.AddUint32(&broadcastCounter, 1) + } + + timeoutMsg := &utils.Timeout{} + + err := tester.bfter.Timeout(timeoutMsg) + if err != nil { + t.Fatal(err) + } + + time.Sleep(100 * time.Millisecond) + + if int(verifyCounter) != targetVotes || int(handlerCounter) != targetVotes || int(broadcastCounter) != targetVotes { + t.Fatalf("count mismatch: have %v on verify, %v on handler, %v on broadcast, want %v", verifyCounter, handlerCounter, broadcastCounter, targetVotes) + } +} + +func TestTimeoutHandlerRoundNotEqual(t *testing.T) { + tester := newTester() + + tester.bfter.consensus.verifyTimeout = func(timeout *utils.Timeout) error { + return nil + } + + tester.bfter.consensus.timeoutHandler = func(timeout *utils.Timeout) error { + return &utils.ErrIncomingMessageRoundNotEqualCurrentRound{utils.Round(1), utils.Round(2)} + } + + tester.bfter.broadcast.Timeout = func(*utils.Timeout) { + return + } + + timeoutMsg := &utils.Timeout{} + + err := tester.bfter.Timeout(timeoutMsg) + assert.Equal(t, "Timeout message round number: 1 does not match currentRound: 2", err.Error()) +} diff --git a/eth/bfter/bft.go b/eth/bft/bft_handler.go similarity index 89% rename from eth/bfter/bft.go rename to eth/bft/bft_handler.go index 7cfc2d7d63..4d57bf3d23 100644 --- a/eth/bfter/bft.go +++ b/eth/bft/bft_handler.go @@ -1,4 +1,4 @@ -package bfter +package bft import ( "github.com/XinFinOrg/XDPoSChain/consensus" @@ -79,9 +79,9 @@ func (b *Bfter) SetConsensusFuns(engine consensus.Engine) { // TODO: rename func (b *Bfter) Vote(vote *utils.Vote) error { - log.Trace("Receive Vote", "vote", vote) + log.Info("Receive Vote", "voted block hash", vote.ProposedBlockInfo.Hash.Hex(), "number", vote.ProposedBlockInfo.Number, "round", vote.ProposedBlockInfo.Round) if b.knownVotes.Contains(vote.Hash()) { - log.Trace("Discarded vote, known vote", "Signature", vote.Signature, "hash", vote.Hash()) + log.Info("Discarded vote, known vote", "voted block hash", vote.ProposedBlockInfo.Hash.Hex(), "number", vote.ProposedBlockInfo.Number, "round", vote.ProposedBlockInfo.Round) return nil } @@ -116,6 +116,10 @@ func (b *Bfter) Timeout(timeout *utils.Timeout) error { err = b.consensus.timeoutHandler(timeout) if err != nil { + if _, ok := err.(*utils.ErrIncomingMessageRoundNotEqualCurrentRound); ok { + log.Debug("timeout message round not equal", "error", err) + return err + } log.Error("handle BFT Timeout", "error", err) return err } diff --git a/eth/fetcher/fetcher.go b/eth/fetcher/fetcher.go index d3f3fb140f..7693c41f07 100644 --- a/eth/fetcher/fetcher.go +++ b/eth/fetcher/fetcher.go @@ -19,10 +19,11 @@ package fetcher import ( "errors" - "github.com/hashicorp/golang-lru" "math/rand" "time" + lru "github.com/hashicorp/golang-lru" + "github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/consensus" "github.com/XinFinOrg/XDPoSChain/core/types" @@ -56,6 +57,8 @@ type bodyRequesterFn func([]common.Hash) error // headerVerifierFn is a callback type to verify a block's header for fast propagation. type headerVerifierFn func(header *types.Header) error +type proposeBlockHandlerFn func(header *types.Header) error + // blockBroadcasterFn is a callback type for broadcasting a block to connected peers. type blockBroadcasterFn func(block *types.Block, propagate bool) @@ -133,13 +136,14 @@ type Fetcher struct { queued map[common.Hash]*inject // Set of already queued blocks (to dedup imports) knowns *lru.ARCCache // Callbacks - getBlock blockRetrievalFn // Retrieves a block from the local chain - verifyHeader headerVerifierFn // Checks if a block's headers have a valid proof of work - broadcastBlock blockBroadcasterFn // Broadcasts a block to connected peers - chainHeight chainHeightFn // Retrieves the current chain's height - insertBlock blockInsertFn // Injects a batch of blocks into the chain - prepareBlock blockPrepareFn - dropPeer peerDropFn // Drops a peer for misbehaving + getBlock blockRetrievalFn // Retrieves a block from the local chain + verifyHeader headerVerifierFn // Checks if a block's headers have a valid proof of work + handleProposedBlock proposeBlockHandlerFn // Consensus v2 specific: Hanle new proposed block + broadcastBlock blockBroadcasterFn // Broadcasts a block to connected peers + chainHeight chainHeightFn // Retrieves the current chain's height + insertBlock blockInsertFn // Injects a batch of blocks into the chain + prepareBlock blockPrepareFn + dropPeer peerDropFn // Drops a peer for misbehaving // Testing hooks announceChangeHook func(common.Hash, bool) // Method to call upon adding or deleting a hash from the announce list @@ -151,32 +155,33 @@ type Fetcher struct { } // New creates a block fetcher to retrieve blocks based on hash announcements. -func New(getBlock blockRetrievalFn, verifyHeader headerVerifierFn, broadcastBlock blockBroadcasterFn, chainHeight chainHeightFn, insertBlock blockInsertFn, prepareBlock blockPrepareFn, dropPeer peerDropFn) *Fetcher { +func New(getBlock blockRetrievalFn, verifyHeader headerVerifierFn, handleProposedBlock proposeBlockHandlerFn, broadcastBlock blockBroadcasterFn, chainHeight chainHeightFn, insertBlock blockInsertFn, prepareBlock blockPrepareFn, dropPeer peerDropFn) *Fetcher { knownBlocks, _ := lru.NewARC(blockLimit) return &Fetcher{ - notify: make(chan *announce), - inject: make(chan *inject), - blockFilter: make(chan chan []*types.Block), - headerFilter: make(chan chan *headerFilterTask), - bodyFilter: make(chan chan *bodyFilterTask), - done: make(chan common.Hash), - quit: make(chan struct{}), - announces: make(map[string]int), - announced: make(map[common.Hash][]*announce), - fetching: make(map[common.Hash]*announce), - fetched: make(map[common.Hash][]*announce), - completing: make(map[common.Hash]*announce), - queue: prque.New(), - queues: make(map[string]int), - queued: make(map[common.Hash]*inject), - knowns: knownBlocks, - getBlock: getBlock, - verifyHeader: verifyHeader, - broadcastBlock: broadcastBlock, - chainHeight: chainHeight, - insertBlock: insertBlock, - prepareBlock: prepareBlock, - dropPeer: dropPeer, + notify: make(chan *announce), + inject: make(chan *inject), + blockFilter: make(chan chan []*types.Block), + headerFilter: make(chan chan *headerFilterTask), + bodyFilter: make(chan chan *bodyFilterTask), + done: make(chan common.Hash), + quit: make(chan struct{}), + announces: make(map[string]int), + announced: make(map[common.Hash][]*announce), + fetching: make(map[common.Hash]*announce), + fetched: make(map[common.Hash][]*announce), + completing: make(map[common.Hash]*announce), + queue: prque.New(), + queues: make(map[string]int), + queued: make(map[common.Hash]*inject), + knowns: knownBlocks, + getBlock: getBlock, + verifyHeader: verifyHeader, + handleProposedBlock: handleProposedBlock, + broadcastBlock: broadcastBlock, + chainHeight: chainHeight, + insertBlock: insertBlock, + prepareBlock: prepareBlock, + dropPeer: dropPeer, } } @@ -721,6 +726,11 @@ func (f *Fetcher) insert(peer string, block *types.Block) { return } } + err = f.handleProposedBlock(block.Header()) + if err != nil { + log.Error("[insert] Unable to handle new proposed block", "err", err) + } + // TODO: (XIN-101) Add propose block handler // If import succeeded, broadcast the block propAnnounceOutTimer.UpdateSince(block.ReceivedAt) if !fastBroadCast { diff --git a/eth/fetcher/fetcher_test.go b/eth/fetcher/fetcher_test.go index 9ba3a4377b..484d62d872 100644 --- a/eth/fetcher/fetcher_test.go +++ b/eth/fetcher/fetcher_test.go @@ -18,13 +18,14 @@ package fetcher import ( "errors" - "github.com/XinFinOrg/XDPoSChain/core/rawdb" "math/big" "sync" "sync/atomic" "testing" "time" + "github.com/XinFinOrg/XDPoSChain/core/rawdb" + "github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/consensus/ethash" "github.com/XinFinOrg/XDPoSChain/core" @@ -92,7 +93,7 @@ func newTester() *fetcherTester { blocks: map[common.Hash]*types.Block{genesis.Hash(): genesis}, drops: make(map[string]bool), } - tester.fetcher = New(tester.getBlock, tester.verifyHeader, tester.broadcastBlock, tester.chainHeight, tester.insertBlock, tester.prepareBlock, tester.dropPeer) + tester.fetcher = New(tester.getBlock, tester.verifyHeader, tester.handleProposedBlock, tester.broadcastBlock, tester.chainHeight, tester.insertBlock, tester.prepareBlock, tester.dropPeer) tester.fetcher.Start() return tester @@ -111,6 +112,10 @@ func (f *fetcherTester) verifyHeader(header *types.Header) error { return nil } +func (f *fetcherTester) handleProposedBlock(header *types.Header) error { + return nil +} + // broadcastBlock is a nop placeholder for the block broadcasting. func (f *fetcherTester) broadcastBlock(block *types.Block, propagate bool) { } @@ -296,6 +301,14 @@ func verifyImportDone(t *testing.T, imported chan *types.Block) { } } +func verifyProposeBlockHandlerCalled(t *testing.T, proposedBlockChan chan *types.Header) { + select { + case <-proposedBlockChan: + case <-time.After(50 * time.Millisecond): + t.Fatalf("did not call propose block handler") + } +} + // Tests that a fetcher accepts block announcements and initiates retrievals for // them, successfully importing into the local chain. func TestSequentialAnnouncements62(t *testing.T) { testSequentialAnnouncements(t, 62) } @@ -317,12 +330,18 @@ func testSequentialAnnouncements(t *testing.T, protocol int) { imported <- block return nil } + handleProposedBlockChan := make(chan *types.Header) + tester.fetcher.handleProposedBlock = func(header *types.Header) error { + go func() { handleProposedBlockChan <- header }() + return nil + } for i := len(hashes) - 2; i >= 0; i-- { tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) verifyImportEvent(t, imported, true) } verifyImportDone(t, imported) + verifyProposeBlockHandlerCalled(t, handleProposedBlockChan) } // Tests that if blocks are announced by multiple peers (or even the same buggy diff --git a/eth/handler.go b/eth/handler.go index 43e385dda0..88e4d1e44f 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -29,11 +29,12 @@ import ( "github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/consensus" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" "github.com/XinFinOrg/XDPoSChain/consensus/misc" "github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/core/types" - "github.com/XinFinOrg/XDPoSChain/eth/bfter" + "github.com/XinFinOrg/XDPoSChain/eth/bft" "github.com/XinFinOrg/XDPoSChain/eth/downloader" "github.com/XinFinOrg/XDPoSChain/eth/fetcher" "github.com/XinFinOrg/XDPoSChain/ethdb" @@ -82,7 +83,7 @@ type ProtocolManager struct { downloader *downloader.Downloader fetcher *fetcher.Fetcher peers *peerSet - bfter *bfter.Bfter + bft *bft.Bfter SubProtocols []p2p.Protocol @@ -198,6 +199,10 @@ func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, ne validator := func(header *types.Header) error { return engine.VerifyHeader(blockchain, header, true) } + handleProposedBlock := func(header *types.Header) error { + return engine.(*XDPoS.XDPoS).HandleProposedBlock(blockchain, header) + } + heighter := func() uint64 { return blockchain.CurrentBlock().NumberU64() } @@ -220,16 +225,16 @@ func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, ne atomic.StoreUint32(&manager.acceptTxs, 1) // Mark initial sync done on any fetcher import return manager.blockchain.PrepareBlock(block) } - manager.fetcher = fetcher.New(blockchain.GetBlockByHash, validator, manager.BroadcastBlock, heighter, inserter, prepare, manager.removePeer) + manager.fetcher = fetcher.New(blockchain.GetBlockByHash, validator, handleProposedBlock, manager.BroadcastBlock, heighter, inserter, prepare, manager.removePeer) //Define bft function - broadcasts := bfter.BroadcastFns{ + broadcasts := bft.BroadcastFns{ Vote: manager.BroadcastVote, Timeout: manager.BroadcastTimeout, SyncInfo: manager.BroadcastSyncInfo, } - manager.bfter = bfter.New(broadcasts, blockchain) + manager.bft = bft.New(broadcasts, blockchain) if blockchain.Config().XDPoS != nil { - manager.bfter.SetConsensusFuns(engine) + manager.bft.SetConsensusFuns(engine) } return manager, nil @@ -827,7 +832,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { } // Mark the peer as owning the vote and process it p.MarkVote(vote.Hash()) - pm.bfter.Vote(&vote) + pm.bft.Vote(&vote) case msg.Code == TimeoutMsg: var timeout utils.Timeout if err := msg.Decode(&timeout); err != nil { @@ -836,7 +841,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { // Mark the peer as owning the timeout and process it p.MarkTimeout(timeout.Hash()) - pm.bfter.Timeout(&timeout) + pm.bft.Timeout(&timeout) case msg.Code == SyncInfoMsg: var syncInfo utils.SyncInfo if err := msg.Decode(&syncInfo); err != nil { @@ -844,7 +849,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { } // Mark the peer as owning the syncInfo and process it p.MarkSyncInfo(syncInfo.Hash()) - pm.bfter.SyncInfo(&syncInfo) + pm.bft.SyncInfo(&syncInfo) default: return errResp(ErrInvalidMsgCode, "%v", msg.Code) @@ -904,7 +909,7 @@ func (pm *ProtocolManager) BroadcastVote(vote *utils.Vote) { for _, peer := range peers { peer.SendVote(vote) } - log.Trace("Propagated Vote", "hash", hash, "recipients", len(peers)) + log.Info("Propagated Vote", "voted block hash", vote.ProposedBlockInfo.Hash.Hex(), "number", vote.ProposedBlockInfo.Number, "round", vote.ProposedBlockInfo.Round, "recipients", len(peers)) } // BroadcastTimeout will propagate a Timeout to all peers which are not known to diff --git a/eth/protocol.go b/eth/protocol.go index 6495408f4f..cb4d21bbbb 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -41,7 +41,7 @@ var ProtocolName = "eth" var ProtocolVersions = []uint{eth63, eth62} // Number of implemented message corresponding to different protocol versions. -var ProtocolLengths = []uint64{17, 8} +var ProtocolLengths = []uint64{227, 8} const ProtocolMaxMsgSize = 10 * 1024 * 1024 // Maximum cap on the size of a protocol message diff --git a/eth/sync.go b/eth/sync.go index b0f2c74fe9..cbe6d421c8 100644 --- a/eth/sync.go +++ b/eth/sync.go @@ -134,9 +134,9 @@ func (pm *ProtocolManager) txsyncLoop() { func (pm *ProtocolManager) syncer() { // Start and ensure cleanup of sync mechanisms pm.fetcher.Start() - pm.bfter.Start() + pm.bft.Start() defer pm.fetcher.Stop() - defer pm.bfter.Stop() + defer pm.bft.Stop() defer pm.downloader.Terminate() // Wait for different events to fire synchronisation operations diff --git a/go.mod b/go.mod index d14175b809..938ddfc3e3 100644 --- a/go.mod +++ b/go.mod @@ -63,4 +63,5 @@ require ( gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772 gopkg.in/urfave/cli.v1 v1.20.0 + gotest.tools v2.2.0+incompatible ) diff --git a/go.sum b/go.sum index 906738a840..7caf629ce9 100644 --- a/go.sum +++ b/go.sum @@ -118,6 +118,7 @@ github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA= @@ -339,6 +340,7 @@ gopkg.in/jcmturner/gokrb5.v7 v7.2.3/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuv gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= gopkg.in/karalabe/cookiejar.v2 v2.0.0-20150724131613-8dcd6a7f4951 h1:DMTcQRFbEH62YPRWwOI647s2e5mHda3oBPMHfrLs2bw= gopkg.in/karalabe/cookiejar.v2 v2.0.0-20150724131613-8dcd6a7f4951/go.mod h1:owOxCRGGeAx1uugABik6K9oeNu1cgxP/R9ItzLDxNWA= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772 h1:hhsSf/5z74Ck/DJYc+R8zpq8KGm7uJvpdLRQED/IedA= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= @@ -353,5 +355,6 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/miner/worker.go b/miner/worker.go index 114511b4d6..ec4e2e3a19 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -523,6 +523,7 @@ func (self *worker) commitNewWork() { // only go with XDPoS if self.config.XDPoS != nil { // get masternodes set from latest checkpoint + // TODO: refactor on yourturn with below condition for v1 v2 c := self.engine.(*XDPoS.XDPoS) len, preIndex, curIndex, ok, err := c.YourTurn(self.chain, parent.Header(), self.coinbase) if err != nil { @@ -581,6 +582,10 @@ func (self *worker) commitNewWork() { } if err := self.engine.Prepare(self.chain, header); err != nil { + if err == consensus.ErrNotReadyToPropose { + log.Info("Waiting...", "err", err) + return + } log.Error("Failed to prepare header for new block", "err", err) return } diff --git a/params/config.go b/params/config.go index fa4957a9c3..fb0846da51 100644 --- a/params/config.go +++ b/params/config.go @@ -36,11 +36,11 @@ var ( var ( XDPoSV2Config = &V2{ - TimeoutWorkerDuration: 50000, + TimeoutWorkerDuration: 50, CertThreshold: common.MaxMasternodesV2*2/3 + 1, } TestXDPoSV2Config = &V2{ - TimeoutWorkerDuration: 5000, + TimeoutWorkerDuration: 5, CertThreshold: 3, } @@ -195,12 +195,12 @@ type XDPoSConfig struct { Gap uint64 `json:"gap"` // Gap time preparing for the next epoch FoudationWalletAddr common.Address `json:"foudationWalletAddr"` // Foundation Address Wallet SkipValidation bool //Skip Block Validation for testing purpose - XDPoSV2Block *big.Int - V2 V2 + XDPoSV2Block *big.Int `json:"v2Block"` + V2 V2 `json:"v2"` } type V2 struct { - TimeoutWorkerDuration int64 `json:"TimeoutWorkerDuration"` // Duration in ms + TimeoutWorkerDuration int64 `json:"timeoutWorkerDuration"` // Duration in ms CertThreshold int `json:"certificateThreshold"` // Necessary number of messages from master nodes to form a certificate }