Xin 127 initial v2 function and snapshot (#42)

* initial v2 and v2 snapshot

* update based on pr feedback

* add test for initial v2 parameters

* remove comment
This commit is contained in:
Liam 2022-01-20 13:27:51 +03:00 committed by GitHub
parent e72d4866df
commit 646042a651
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 241 additions and 285 deletions

View file

@ -278,7 +278,10 @@ func (x *XDPoS) GetMasternodesByNumber(chain consensus.ChainReader, blockNumber
} }
func (x *XDPoS) YourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (bool, error) { func (x *XDPoS) YourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (bool, error) {
switch x.config.BlockConsensusVersion(parent.Number) { if parent.Number.Cmp(x.config.XDPoSV2Block) == 0 {
x.initialV2(chain, parent)
}
switch x.config.BlockConsensusVersion(big.NewInt(parent.Number.Int64() + 1)) {
case params.ConsensusEngineVersion2: case params.ConsensusEngineVersion2:
return x.EngineV2.YourTurn(chain, parent, signer) return x.EngineV2.YourTurn(chain, parent, signer)
default: // Default "v1" default: // Default "v1"
@ -296,7 +299,7 @@ func (x *XDPoS) GetValidator(creator common.Address, chain consensus.ChainReader
func (x *XDPoS) UpdateMasternodes(chain consensus.ChainReader, header *types.Header, ms []utils.Masternode) error { func (x *XDPoS) UpdateMasternodes(chain consensus.ChainReader, header *types.Header, ms []utils.Masternode) error {
switch x.config.BlockConsensusVersion(header.Number) { switch x.config.BlockConsensusVersion(header.Number) {
case params.ConsensusEngineVersion2: case params.ConsensusEngineVersion2:
return nil return x.EngineV2.UpdateMasternodes(chain, header, ms)
default: // Default "v1" default: // Default "v1"
return x.EngineV1.UpdateMasternodes(chain, header, ms) return x.EngineV1.UpdateMasternodes(chain, header, ms)
} }
@ -361,7 +364,7 @@ func (x *XDPoS) GetSnapshot(chain consensus.ChainReader, header *types.Header) (
return &utils.PublicApiSnapshot{ return &utils.PublicApiSnapshot{
Number: sp.Number, Number: sp.Number,
Hash: sp.Hash, Hash: sp.Hash,
Signers: sp.MasterNodes, Signers: sp.GetMappedMasterNodes(),
}, err }, err
default: // Default "v1" default: // Default "v1"
sp, err := x.EngineV1.GetSnapshot(chain, header) sp, err := x.EngineV1.GetSnapshot(chain, header)
@ -439,6 +442,14 @@ func (x *XDPoS) GetCachedSigningTxs(hash common.Hash) (interface{}, bool) {
} }
//V2 //V2
func (x *XDPoS) initialV2(chain consensus.ChainReader, header *types.Header) error {
checkpointBlockNumber := header.Number.Uint64() - header.Number.Uint64()%x.config.Epoch
checkpointHeader := chain.GetHeaderByNumber(checkpointBlockNumber)
masternodes := x.EngineV1.GetMasternodesFromCheckpointHeader(checkpointHeader)
x.EngineV2.Initial(chain, header, masternodes)
return nil
}
func (x *XDPoS) VerifyVote(*utils.Vote) error { func (x *XDPoS) VerifyVote(*utils.Vote) error {
return nil return nil
} }

View file

@ -29,7 +29,7 @@ type XDPoS_v2 struct {
config *params.XDPoSConfig // Consensus engine configuration parameters config *params.XDPoSConfig // Consensus engine configuration parameters
db ethdb.Database // Database to store and retrieve snapshot checkpoints db ethdb.Database // Database to store and retrieve snapshot checkpoints
recents *lru.ARCCache // Snapshots for recent block to speed up reorgs snapshots *lru.ARCCache // Snapshots for gap block
signatures *lru.ARCCache // Signatures of recent blocks to speed up mining signatures *lru.ARCCache // Signatures of recent blocks to speed up mining
epochSwitches *lru.ARCCache // infos of epoch: master nodes, epoch switch block info, parent of that info epochSwitches *lru.ARCCache // infos of epoch: master nodes, epoch switch block info, parent of that info
@ -60,7 +60,7 @@ func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS_v2 {
timer := countdown.NewCountDown(duration) timer := countdown.NewCountDown(duration)
timeoutPool := utils.NewPool(config.V2.CertThreshold) timeoutPool := utils.NewPool(config.V2.CertThreshold)
recents, _ := lru.NewARC(utils.InmemorySnapshots) snapshots, _ := lru.NewARC(utils.InmemorySnapshots)
signatures, _ := lru.NewARC(utils.InmemorySnapshots) signatures, _ := lru.NewARC(utils.InmemorySnapshots)
epochSwitches, _ := lru.NewARC(int(utils.InmemoryEpochs)) epochSwitches, _ := lru.NewARC(int(utils.InmemoryEpochs))
@ -70,7 +70,7 @@ func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS_v2 {
db: db, db: db,
signatures: signatures, signatures: signatures,
recents: recents, snapshots: snapshots,
epochSwitches: epochSwitches, epochSwitches: epochSwitches,
timeoutWorker: timer, timeoutWorker: timer,
BroadcastCh: make(chan interface{}), BroadcastCh: make(chan interface{}),
@ -106,40 +106,55 @@ type SignerFn func(accounts.Account, []byte) ([]byte, error)
sigHash returns the hash which is used as input for the delegated-proof-of-stake sigHash returns the hash which is used as input for the delegated-proof-of-stake
signing. It is the hash of the entire header apart from the 65 byte signature signing. It is the hash of the entire header apart from the 65 byte signature
contained at the end of the extra data. contained at the end of the extra data.
Note, the method requires the extra data to be at least 65 bytes, otherwise it
panics. This is done to avoid accidentally using both forms (signature present
or not), which could be abused to produce different hashes for the same header.
*/ */
func (x *XDPoS_v2) SignHash(header *types.Header) (hash common.Hash) { func (x *XDPoS_v2) SignHash(header *types.Header) (hash common.Hash) {
return sigHash(header) return sigHash(header)
} }
func (x *XDPoS_v2) Initial(chain consensus.ChainReader, header *types.Header, masternodes []common.Address) error {
log.Info("[Initial] initial v2 related parameters")
if x.highestQuorumCert.ProposedBlockInfo.Round != 0 { //already initialized
log.Warn("[Initial] Already initialized")
return nil
}
x.lock.Lock()
defer x.lock.Unlock()
// Check header if it is the first consensus v2 block, if so, assign initial values to current round and highestQC
log.Info("[Initial] 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
// Initial snapshot
lastGapNum := header.Number.Uint64() - header.Number.Uint64()%x.config.Epoch - x.config.Gap
lastGapHeader := chain.GetHeaderByNumber(lastGapNum)
snap := newSnapshot(lastGapNum, lastGapHeader.Hash(), x.currentRound, x.highestQuorumCert, masternodes)
x.snapshots.Add(snap.Hash, snap)
storeSnapshot(snap, x.db)
return nil
}
// Prepare implements consensus.Engine, preparing all the consensus fields of the // Prepare implements consensus.Engine, preparing all the consensus fields of the
// header for running the transactions on top. // header for running the transactions on top.
func (x *XDPoS_v2) Prepare(chain consensus.ChainReader, header *types.Header) error { func (x *XDPoS_v2) Prepare(chain consensus.ChainReader, header *types.Header) error {
// Verify mined block parent matches highest QC
x.lock.Lock()
// 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.lock.RLock()
currentRound := x.currentRound currentRound := x.currentRound
highestQC := x.highestQuorumCert highestQC := x.highestQuorumCert
x.lock.Unlock() x.lock.RUnlock()
if (highestQC == nil) || (header.ParentHash != highestQC.ProposedBlockInfo.Hash) { if (highestQC == nil) || (header.ParentHash != highestQC.ProposedBlockInfo.Hash) {
return consensus.ErrNotReadyToPropose return consensus.ErrNotReadyToPropose
@ -169,11 +184,11 @@ func (x *XDPoS_v2) Prepare(chain consensus.ChainReader, header *types.Header) er
return err return err
} }
if isEpochSwitchBlock { if isEpochSwitchBlock {
snap, err := x.snapshot(chain, number-1, header.ParentHash, nil) snap, err := x.getSnapshot(chain, number-1)
if err != nil { if err != nil {
return err return err
} }
masternodes := snap.GetMasterNodes() masternodes := snap.NextEpochMasterNodes
//TODO: remove penalty nodes and add comeback nodes //TODO: remove penalty nodes and add comeback nodes
for _, v := range masternodes { for _, v := range masternodes {
header.Validators = append(header.Validators, v[:]...) header.Validators = append(header.Validators, v[:]...)
@ -193,7 +208,7 @@ func (x *XDPoS_v2) Prepare(chain consensus.ChainReader, header *types.Header) er
// Ensure the timestamp has the correct delay // Ensure the timestamp has the correct delay
// TODO: Proper deal with time // TODO: Proper deal with time
// TODO: if timestamp > current time, how to deal with future timestamp // 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)) header.Time = new(big.Int).Add(parent.Time, new(big.Int).SetUint64(x.config.Period))
if header.Time.Int64() < time.Now().Unix() { if header.Time.Int64() < time.Now().Unix() {
header.Time = big.NewInt(time.Now().Unix()) header.Time = big.NewInt(time.Now().Unix())
@ -273,23 +288,17 @@ func (x *XDPoS_v2) Seal(chain consensus.ChainReader, block *types.Block, stop <-
x.signLock.RUnlock() x.signLock.RUnlock()
// Bail out if we're unauthorized to sign a block // 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) masternodes := x.GetMasternodes(chain, header)
if _, authorized := snap.MasterNodes[signer]; !authorized { valid := false
valid := false for _, m := range masternodes {
for _, m := range masternodes { if m == signer {
if m == signer { valid = true
valid = true break
break
}
}
if !valid {
return nil, utils.ErrUnauthorized
} }
} }
if !valid {
return nil, utils.ErrUnauthorized
}
select { select {
case <-stop: case <-stop:
@ -398,8 +407,8 @@ func (x *XDPoS_v2) IsAuthorisedAddress(chain consensus.ChainReader, header *type
// Copy from v1 // Copy from v1
func (x *XDPoS_v2) GetSnapshot(chain consensus.ChainReader, header *types.Header) (*SnapshotV2, error) { func (x *XDPoS_v2) GetSnapshot(chain consensus.ChainReader, header *types.Header) (*SnapshotV2, error) {
number := header.Number.Uint64() number := header.Number.Uint64()
log.Trace("get snapshot", "number", number, "hash", header.Hash()) log.Trace("get snapshot", "number", number)
snap, err := x.snapshot(chain, number, header.Hash(), nil) snap, err := x.getSnapshot(chain, number)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -407,84 +416,53 @@ func (x *XDPoS_v2) GetSnapshot(chain consensus.ChainReader, header *types.Header
} }
// snapshot retrieves the authorization snapshot at a given point in time. // 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) { func (x *XDPoS_v2) getSnapshot(chain consensus.ChainReader, number uint64) (*SnapshotV2, error) {
// Search for a SnapshotV2 in memory or on disk for checkpoints // checkpoint snapshot = checkpoint - gap
var ( gapBlockNum := number - number%x.config.Epoch - x.config.Gap
headers []*types.Header gapBlockHash := chain.GetHeaderByNumber(gapBlockNum).Hash()
snap *SnapshotV2 log.Debug("get snapshot from gap block", "number", gapBlockNum, "hash", gapBlockHash.Hex())
)
for snap == nil { // If an in-memory SnapshotV2 was found, use that
// If an in-memory SnapshotV2 was found, use that if s, ok := x.snapshots.Get(gapBlockHash); ok {
if s, ok := x.recents.Get(hash); ok { snap := s.(*SnapshotV2)
snap = s.(*SnapshotV2) log.Trace("Loaded snapshot from memory", "number", gapBlockNum, "hash", gapBlockHash)
break return snap, nil
}
// 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 // If an on-disk checkpoint snapshot can be found, use that
for i := 0; i < len(headers)/2; i++ { snap, err := loadSnapshot(x.signatures, x.db, gapBlockHash)
headers[i], headers[len(headers)-1-i] = headers[len(headers)-1-i], headers[i]
}
snap, err := snap.apply(headers)
if err != nil { if err != nil {
log.Error("Cannot find snapshot from last gap block", "err", err, "number", gapBlockNum, "hash", gapBlockHash)
return nil, err return nil, err
} }
x.recents.Add(snap.Hash, snap)
// If we've generated a new checkpoint snapshot, save to disk log.Trace("Loaded snapshot from disk", "number", gapBlockNum, "hash", gapBlockHash)
// TODO how to save correct snapshot x.snapshots.Add(snap.Hash, snap)
if uint64(snap.Round)%x.config.Epoch == x.config.Gap { return snap, nil
if err = storeSnapshot(snap, x.db); err != nil { }
return nil, err
} func (x *XDPoS_v2) UpdateMasternodes(chain consensus.ChainReader, header *types.Header, ms []utils.Masternode) error {
log.Trace("Stored snapshot to disk", "round number", snap.Round, "hash", snap.Hash) number := header.Number.Uint64()
log.Trace("take snapshot", "number", number, "hash", header.Hash())
masterNodes := []common.Address{}
for _, m := range ms {
masterNodes = append(masterNodes, m.Address)
} }
return snap, err
x.lock.RLock()
snap := newSnapshot(number, header.Hash(), x.currentRound, x.highestQuorumCert, masterNodes)
x.lock.RUnlock()
storeSnapshot(snap, x.db)
x.snapshots.Add(snap.Hash, snap)
nm := []string{}
for _, n := range ms {
nm = append(nm, n.Address.String())
}
log.Info("New set of masternodes has been updated to snapshot", "number", snap.Number, "hash", snap.Hash, "new masternodes", nm)
return nil
} }
func (x *XDPoS_v2) VerifyHeader(chain consensus.ChainReader, header *types.Header, fullVerify bool) error { func (x *XDPoS_v2) VerifyHeader(chain consensus.ChainReader, header *types.Header, fullVerify bool) error {

View file

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

View file

@ -3,103 +3,28 @@ package engine_v2
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"math/big"
"testing" "testing"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/ethdb/leveldb" "github.com/XinFinOrg/XDPoSChain/ethdb/leveldb"
"github.com/stretchr/testify/assert"
) )
func TestGetMasterNodes(t *testing.T) { func TestGetMasterNodes(t *testing.T) {
masterNodes := []common.Address{ masterNodes := []common.Address{{0x4}, {0x3}, {0x2}, {0x1}}
{4}, {3}, {2}, {1}, snap := newSnapshot(1, common.Hash{}, utils.Round(1), nil, masterNodes)
}
snap := newSnapshot(nil, 1, common.Hash{}, utils.Round(1), nil, masterNodes) for _, address := range masterNodes {
sortedNodes := snap.GetMasterNodes() if _, ok := snap.GetMappedMasterNodes()[address]; !ok {
for i := range masterNodes { t.Error("should get master node from map", address.Hex(), snap.GetMappedMasterNodes())
if masterNodes[i] != sortedNodes[3-i] {
t.Error("should get sorted master nodes list", i, sortedNodes[i])
return 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) { func TestStoreLoadSnapshot(t *testing.T) {
snap := newSnapshot(nil, 1, common.Hash{0x1}, utils.Round(1), nil, nil) snap := newSnapshot(1, common.Hash{0x1}, utils.Round(1), nil, nil)
dir, err := ioutil.TempDir("", "snapshot-test") dir, err := ioutil.TempDir("", "snapshot-test")
if err != nil { if err != nil {
panic(fmt.Sprintf("can't create temporary directory: %v", err)) panic(fmt.Sprintf("can't create temporary directory: %v", err))

View file

@ -602,3 +602,63 @@ func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
t.Fatalf("account 3 should sit in the signer list as previos block result") t.Fatalf("account 3 should sit in the signer list as previos block result")
} }
} }
/*
V2 Consensus
*/
/*
// Pending for creating cross version blocks
func TestV2UpdateSignerListIfVotedBeforeGap(t *testing.T) {
config := params.TestXDPoSMockChainConfigWithV2EngineEpochSwitch
blockchain, backend, parentBlock, _ := PrepareXDCTestBlockChain(t, int(config.XDPoS.Epoch)+GAP-2, config)
// Insert first Block 1349
t.Logf("Inserting block with propose at 1349...")
blockCoinbaseA := "0xaaa0000000000000000000000000000000001349"
tx, err := voteTX(37117, 0, acc1Addr.String())
if err != nil {
t.Fatal(err)
}
//Get from block validator error message
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(1349)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinbaseA),
}
block1349, err := insertBlockTxs(blockchain, header, []*types.Transaction{tx})
if err != nil {
t.Fatal(err)
}
parentBlock = block1349
// Now, let's mine another block to trigger the GAP block signerList update
block1350CoinbaseAddress := "0xaaa0000000000000000000000000000000001350"
merkleRoot = "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
header = &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(1350)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(block1350CoinbaseAddress),
}
block1350, err := insertBlock(blockchain, header)
if err != nil {
t.Fatal(err)
}
signers, err := GetSnapshotSigner(blockchain, block1350.Header())
if err != nil {
t.Fatalf("Failed while trying to get signers")
}
// Now, we voted acc 1 to be in the signerList, which will kick out acc3 because it has less funds
if signers[acc3Addr.Hex()] == true {
debugMessage(backend, signers, t)
t.Fatalf("account 3 should NOT sit in the signer list")
}
if signers[acc1Addr.Hex()] != true {
debugMessage(backend, signers, t)
t.Fatalf("account 1 should sit in the signer list")
}
}
*/

View file

@ -0,0 +1,48 @@
package tests
import (
"math/big"
"testing"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/stretchr/testify/assert"
)
func TestYourTurnInitialV2(t *testing.T) {
config := params.TestXDPoSMockChainConfigWithV2EngineEpochSwitch
blockchain, _, parentBlock, _ := PrepareXDCTestBlockChain(t, int(config.XDPoS.Epoch)-1, config)
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
// Insert block 900
t.Logf("Inserting block with propose at 900...")
blockCoinbaseA := "0xaaa0000000000000000000000000000000000900"
//Get from block validator error message
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(900)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinbaseA),
Extra: common.Hex2Bytes("d7830100018358444388676f312e31352e38856c696e757800000000000000000278c350152e15fa6ffc712a5a73d704ce73e2e103d9e17ae3ff2c6712e44e25b09ac5ee91f6c9ff065551f0dcac6f00cae11192d462db709be3758ccef312ee5eea8d7bad5374c6a652150515d744508b61c1a4deb4e4e7bf057e4e3824c11fd2569bcb77a52905cda63b5a58507910bed335e4c9d87ae0ecdfafd400"),
}
block900, err := insertBlock(blockchain, header)
if err != nil {
t.Fatal(err)
}
// YourTurn is called before mine first v2 block
adaptor.YourTurn(blockchain, block900.Header(), common.HexToAddress("xdc0278C350152e15fa6FFC712a5A73D704Ce73E2E1"))
assert.Equal(t, adaptor.EngineV2.GetCurrentRound(), utils.Round(1))
snap, err := adaptor.EngineV2.GetSnapshot(blockchain, block900.Header())
assert.Nil(t, err)
assert.NotNil(t, snap)
masterNodes := adaptor.EngineV1.GetMasternodesFromCheckpointHeader(block900.Header())
for i := 0; i < len(masterNodes); i++ {
assert.Equal(t, masterNodes[i].Hex(), snap.NextEpochMasterNodes[i].Hex())
}
}

View file

@ -133,6 +133,8 @@ var (
TestXDPoSMockChainConfig = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil, &XDPoSConfig{Epoch: 900, Gap: 450, SkipValidation: true, V2: *TestXDPoSV2Config}} TestXDPoSMockChainConfig = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil, &XDPoSConfig{Epoch: 900, Gap: 450, SkipValidation: true, V2: *TestXDPoSV2Config}}
// XDPoS config with v2 engine after block 10 // XDPoS config with v2 engine after block 10
TestXDPoSMockChainConfigWithV2Engine = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil, &XDPoSConfig{Epoch: 900, Gap: 450, SkipValidation: true, XDPoSV2Block: big.NewInt(10), V2: *TestXDPoSV2Config}} TestXDPoSMockChainConfigWithV2Engine = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil, &XDPoSConfig{Epoch: 900, Gap: 450, SkipValidation: true, XDPoSV2Block: big.NewInt(10), V2: *TestXDPoSV2Config}}
// XDPoS config with v2 engine after block 901
TestXDPoSMockChainConfigWithV2EngineEpochSwitch = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil, &XDPoSConfig{Epoch: 900, Gap: 450, SkipValidation: true, XDPoSV2Block: big.NewInt(900), V2: *TestXDPoSV2Config}}
TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil, nil} TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil, nil}
TestRules = TestChainConfig.Rules(new(big.Int)) TestRules = TestChainConfig.Rules(new(big.Int))