Skip to content

Commit

Permalink
Xin 127 initial v2 function and snapshot (ethereum#42)
Browse files Browse the repository at this point in the history
* initial v2 and v2 snapshot

* update based on pr feedback

* add test for initial v2 parameters

* remove comment
  • Loading branch information
liam-lai authored Jan 20, 2022
1 parent e72d486 commit 646042a
Show file tree
Hide file tree
Showing 7 changed files with 242 additions and 286 deletions.
17 changes: 14 additions & 3 deletions consensus/XDPoS/XDPoS.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
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:
return x.EngineV2.YourTurn(chain, parent, signer)
default: // Default "v1"
Expand All @@ -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 {
switch x.config.BlockConsensusVersion(header.Number) {
case params.ConsensusEngineVersion2:
return nil
return x.EngineV2.UpdateMasternodes(chain, header, ms)
default: // Default "v1"
return x.EngineV1.UpdateMasternodes(chain, header, ms)
}
Expand Down Expand Up @@ -361,7 +364,7 @@ func (x *XDPoS) GetSnapshot(chain consensus.ChainReader, header *types.Header) (
return &utils.PublicApiSnapshot{
Number: sp.Number,
Hash: sp.Hash,
Signers: sp.MasterNodes,
Signers: sp.GetMappedMasterNodes(),
}, err
default: // Default "v1"
sp, err := x.EngineV1.GetSnapshot(chain, header)
Expand Down Expand Up @@ -439,6 +442,14 @@ func (x *XDPoS) GetCachedSigningTxs(hash common.Hash) (interface{}, bool) {
}

//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 {
return nil
}
Expand Down
214 changes: 96 additions & 118 deletions consensus/XDPoS/engines/engine_v2/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ 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
snapshots *lru.ARCCache // Snapshots for gap block
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

Expand Down Expand Up @@ -60,7 +60,7 @@ func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS_v2 {
timer := countdown.NewCountDown(duration)
timeoutPool := utils.NewPool(config.V2.CertThreshold)

recents, _ := lru.NewARC(utils.InmemorySnapshots)
snapshots, _ := lru.NewARC(utils.InmemorySnapshots)
signatures, _ := lru.NewARC(utils.InmemorySnapshots)
epochSwitches, _ := lru.NewARC(int(utils.InmemoryEpochs))

Expand All @@ -70,7 +70,7 @@ func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS_v2 {
db: db,
signatures: signatures,

recents: recents,
snapshots: snapshots,
epochSwitches: epochSwitches,
timeoutWorker: timer,
BroadcastCh: make(chan interface{}),
Expand Down Expand Up @@ -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
signing. It is the hash of the entire header apart from the 65 byte signature
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) {
return sigHash(header)
}

// 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
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
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

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
// header for running the transactions on top.
func (x *XDPoS_v2) Prepare(chain consensus.ChainReader, header *types.Header) error {

x.lock.RLock()
currentRound := x.currentRound
highestQC := x.highestQuorumCert
x.lock.Unlock()
x.lock.RUnlock()

if (highestQC == nil) || (header.ParentHash != highestQC.ProposedBlockInfo.Hash) {
return consensus.ErrNotReadyToPropose
Expand Down Expand Up @@ -169,11 +184,11 @@ func (x *XDPoS_v2) Prepare(chain consensus.ChainReader, header *types.Header) er
return err
}
if isEpochSwitchBlock {
snap, err := x.snapshot(chain, number-1, header.ParentHash, nil)
snap, err := x.getSnapshot(chain, number-1)
if err != nil {
return err
}
masternodes := snap.GetMasterNodes()
masternodes := snap.NextEpochMasterNodes
//TODO: remove penalty nodes and add comeback nodes
for _, v := range masternodes {
header.Validators = append(header.Validators, v[:]...)
Expand All @@ -193,7 +208,7 @@ func (x *XDPoS_v2) Prepare(chain consensus.ChainReader, header *types.Header) er
// Ensure the timestamp has the correct delay

// 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))
if header.Time.Int64() < time.Now().Unix() {
header.Time = big.NewInt(time.Now().Unix())
Expand Down Expand Up @@ -273,23 +288,17 @@ func (x *XDPoS_v2) Seal(chain consensus.ChainReader, block *types.Block, stop <-
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
valid := false
for _, m := range masternodes {
if m == signer {
valid = true
break
}
}
if !valid {
return nil, utils.ErrUnauthorized
}

select {
case <-stop:
Expand Down Expand Up @@ -398,93 +407,62 @@ func (x *XDPoS_v2) IsAuthorisedAddress(chain consensus.ChainReader, header *type
// 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)
log.Trace("get snapshot", "number", number)
snap, err := x.getSnapshot(chain, number)
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)
func (x *XDPoS_v2) getSnapshot(chain consensus.ChainReader, number uint64) (*SnapshotV2, error) {
// checkpoint snapshot = checkpoint - gap
gapBlockNum := number - number%x.config.Epoch - x.config.Gap
gapBlockHash := chain.GetHeaderByNumber(gapBlockNum).Hash()
log.Debug("get snapshot from gap block", "number", gapBlockNum, "hash", gapBlockHash.Hex())

// If an in-memory SnapshotV2 was found, use that
if s, ok := x.snapshots.Get(gapBlockHash); ok {
snap := s.(*SnapshotV2)
log.Trace("Loaded snapshot from memory", "number", gapBlockNum, "hash", gapBlockHash)
return snap, nil
}
// If an on-disk checkpoint snapshot can be found, use that
snap, err := loadSnapshot(x.signatures, x.db, gapBlockHash)
if err != nil {
log.Error("Cannot find snapshot from last gap block", "err", err, "number", gapBlockNum, "hash", gapBlockHash)
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)
log.Trace("Loaded snapshot from disk", "number", gapBlockNum, "hash", gapBlockHash)
x.snapshots.Add(snap.Hash, snap)
return snap, nil
}

func (x *XDPoS_v2) UpdateMasternodes(chain consensus.ChainReader, header *types.Header, ms []utils.Masternode) error {
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 {
Expand Down
Loading

0 comments on commit 646042a

Please sign in to comment.