Skip to content

Commit

Permalink
Merge pull request ethereum#265 from nextyio/stateless
Browse files Browse the repository at this point in the history
Stateless header chain verification
  • Loading branch information
Zergity authored Aug 28, 2019
2 parents 450a85a + e1802b6 commit 67fec1a
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 106 deletions.
268 changes: 194 additions & 74 deletions consensus/dccs/1_dccs.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package dccs
import (
"bytes"
"errors"
"fmt"
"math"
"math/big"
"time"
Expand All @@ -42,6 +43,14 @@ import (
"github.com/ethereum/go-ethereum/params"
)

var (
errSnapshotNotAvailable = errors.New("Snapshot contract not available")
errNotCheckpoint = errors.New("Not a checkpoint block")
errNotSnapshot = errors.New("Not a snapshot block")
errMissingHeaderExtra = errors.New("missing extra data in header")
errMissingCheckpointSigners = errors.New("missing signer list on checkpoint block")
)

var (
rewards = []*big.Int{
big.NewInt(1e+4),
Expand Down Expand Up @@ -148,7 +157,7 @@ func (d *Dccs) verifyCascadingFields1(chain consensus.ChainReader, header *types
return ErrInvalidTimestamp
}
// Retrieve the snapshot needed to verify this header and cache it
snap, err := d.snapshot1(chain, number-1, header.ParentHash, parents)
snap, err := d.snapshot1(chain, header, parents)
if err != nil {
return err
}
Expand All @@ -168,66 +177,150 @@ func (d *Dccs) verifyCascadingFields1(chain consensus.ChainReader, header *types
}

// snapshot1 retrieves the authorization snapshot at a given point in time.
func (d *Dccs) snapshot1(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) {
// Search for a snapshot in memory or on disk for checkpoints
var (
snap *Snapshot
)
cp := d.config.Snapshot(number + 1)
// looping until data/state are available on local
for snap == nil {
// Get signers from Nexty staking smart contract at the latest epoch checkpoint from block number
checkpoint := chain.GetHeaderByNumber(cp)
if checkpoint == nil {
log.Trace("snapshot header not available", "number", cp)
continue
}
hash := checkpoint.Hash()
log.Trace("Reading signers from epoch checkpoint", "number", cp, "hash", hash)
// If an in-memory snapshot was found, use that
if s, ok := d.recents.Get(hash); ok && number+1 != d.config.ThangLongBlock.Uint64() {
snap = s.(*Snapshot)
log.Trace("Loading snapshot from mem-cache", "hash", snap.Hash, "length", len(snap.signers()))
break
}
state, err := chain.StateAt(checkpoint.Root)
if state == nil || err != nil {
log.Trace("snapshot state not available", "number", cp, "err", err)
continue
}
size := state.GetCodeSize(chain.Config().Dccs.Contract)
if size <= 0 || state.Error() != nil {
log.Trace("snapshot contract state not available", "number", cp, "err", state.Error())
continue
}
index := common.BigToHash(common.Big0)
result := state.GetState(chain.Config().Dccs.Contract, index)
var length int64
if (result == common.Hash{}) {
length = 0
} else {
length = result.Big().Int64()
}
log.Trace("Total number of signer from staking smart contract", "length", length)
signers := make([]common.Address, length)
key := crypto.Keccak256Hash(hexutil.MustDecode(index.String()))
for i := 0; i < len(signers); i++ {
log.Trace("key hash", "key", key)
singer := state.GetState(chain.Config().Dccs.Contract, key)
signers[i] = common.HexToAddress(singer.Hex())
key = key.Plus()
//
// Note: signers list for checkpoint block is gotten from it's snapshot block,
// which is (checkpoint-canonicalDepth). This list is then recorded to the
// checkpoint block extra itself.
// So the signers list for block n can be retrieved from either:
// 1. header extra of block checkpoint(n), or
// 2. state of block snapshot(n) => this is required for miner
func (d *Dccs) snapshot1(chain consensus.ChainReader, header *types.Header, parents []*types.Header) (*Snapshot, error) {
number := header.Number.Uint64()
ssNumber := d.config.Snapshot(number)
ssHeader := getAvailableHeader(ssNumber, header, parents, chain)
if ssHeader == nil {
log.Error("💀 Snapshot header missing", "snapshot number", ssNumber, "for number", number)
return nil, errSnapshotNotAvailable
}
if s, ok := d.recents.Get(ssHeader.Hash()); ok {
// in-memory snapshot found
snap := s.(*Snapshot)
log.Trace("Snapshot loaded from mem-cache", "snapshot number", snap.Number, "hash", snap.Hash, "signers length", len(snap.Signers), "for number", header.Number)
return snap, nil
}

if d.config.IsCheckpoint(number) {
snap, err := d.getStateSnapshot(chain, ssHeader)
if err == nil && snap != nil {
log.Trace("Snapshot retrieved from state and cached", "for number", header.Number, "snapshot number", snap.Number, "hash", snap.Hash)
// Store found snapshot into mem-cache
d.recents.Add(snap.Hash, snap)
return snap, nil
}
snap = newSnapshot(d.config, d.signatures, number, hash, signers)
// Store found snapshot into mem-cache
d.recents.Add(snap.Hash, snap)
break
log.Warn("☢ Snapshot state missing for checkpoint block, continue at your own risk", "snapshot number", ssNumber, "for number", number, "err", err)
}

// Set current block number for snapshot to calculate the inturn & difficulty
snap.Number = number
snap, err := d.getHeaderSnapshotFor(header, chain, parents)
if err != nil || snap == nil {
return nil, err
}
// Store found snapshot into mem-cache
d.recents.Add(snap.Hash, snap)
return snap, nil
}

func (d *Dccs) getStateSnapshot(chain consensus.ChainReader, header *types.Header) (*Snapshot, error) {
number := header.Number.Uint64()
state, err := chain.StateAt(header.Root)
if state == nil || err != nil {
log.Trace("Snapshot state not available", "number", number, "err", err)
return nil, errSnapshotNotAvailable
}
size := state.GetCodeSize(chain.Config().Dccs.Contract)
if size <= 0 || state.Error() != nil {
log.Trace("Snapshot contract state not available", "number", number, "err", state.Error())
return nil, errSnapshotNotAvailable
}
index := common.BigToHash(common.Big0)
result := state.GetState(chain.Config().Dccs.Contract, index)
var length int64
if (result == common.Hash{}) {
length = 0
} else {
length = result.Big().Int64()
}
log.Trace("Total number of signer from staking smart contract", "length", length)
signers := make([]common.Address, length)
key := crypto.Keccak256Hash(hexutil.MustDecode(index.String()))
for i := 0; i < len(signers); i++ {
log.Trace("key hash", "key", key)
singer := state.GetState(chain.Config().Dccs.Contract, key)
signers[i] = common.HexToAddress(singer.Hex())
key = key.Plus()
}
return newSnapshot(d.config, d.signatures, number, header.Hash(), signers), nil
}

// getHeaderFromInput returns either:
// + the input header, if number == header.Number
// + the header in parents if available (nessesary for batch headers processing)
// + chain.GetHeaderByNumber(number), if all else fail
func getAvailableHeader(number uint64, header *types.Header, parents []*types.Header, chain consensus.ChainReader) *types.Header {
headerNumber := header.Number.Uint64()
if number == headerNumber {
return header
}
if number > headerNumber {
return chain.GetHeaderByNumber(number)
}
idx := len(parents) - int(headerNumber) + int(number)
if idx >= 0 {
header := parents[idx]
if header.Number.Uint64() == number {
return header
}
log.Error("invalid parrent number", "expected", number, "actual", header.Number)
}
return chain.GetHeaderByNumber(number)
}

func (d *Dccs) getHeaderSnapshotFor(header *types.Header, chain consensus.ChainReader, parents []*types.Header) (*Snapshot, error) {
number := header.Number.Uint64()
cp := d.config.Checkpoint(number)
cpHeader := getAvailableHeader(cp, header, parents, chain)
if cpHeader == nil {
return nil, fmt.Errorf("checkpoint header missing: checkpoint = %v, header = %v", cp, number)
}
ss := d.config.Snapshot(number)
ssHeader := getAvailableHeader(ss, header, parents, chain)
return d.getHeaderSnapshot(cpHeader, ssHeader)
}

// the signer list is retrieved from checkpoint header extra,
// but the snapshot hash is hash of snapshot header
func (d *Dccs) getHeaderSnapshot(cpHeader, ssHeader *types.Header) (*Snapshot, error) {
cp := cpHeader.Number.Uint64()
if !d.config.IsCheckpoint(cp) {
return nil, errNotCheckpoint
}
ss := ssHeader.Number.Uint64()
if d.config.Snapshot(cp) != ss {
return nil, errNotSnapshot
}
if len(cpHeader.Extra) <= extraVanity+extraSeal {
return nil, errMissingCheckpointSigners
}
extraSuffix := len(cpHeader.Extra) - extraSeal
extraSealers := cpHeader.Extra[extraVanity:extraSuffix]
if len(extraSealers) == 0 {
log.Error("empty sealers list", "number", cp, "extra", common.Bytes2Hex(cpHeader.Extra))
return nil, errMissingCheckpointSigners
}
if len(extraSealers)%common.AddressLength != 0 {
log.Error("not divided by common.AddressLength", "number", cp, "actual", common.Bytes2Hex(extraSealers))
return nil, errInvalidCheckpointSigners
}
signersCount := len(extraSealers) / common.AddressLength
signers := make([]common.Address, signersCount)
for i := 0; i < signersCount; i++ {
offset := i * common.AddressLength
signers[i] = common.BytesToAddress(extraSealers[offset : offset+common.AddressLength])
}

log.Trace("Snapshot parsed from checkpoint header", "snapshot number", ss, "hash", ssHeader.Hash(), "for number", cpHeader.Number)
return newSnapshot(d.config, d.signatures, ss, ssHeader.Hash(), signers), nil
}

// verifySeal1 checks whether the signature contained in the header satisfies the
// consensus protocol requirements. The method accepts an optional list of parent
// headers that aren't yet part of the local blockchain to generate the snapshots
Expand All @@ -239,7 +332,7 @@ func (d *Dccs) verifySeal1(chain consensus.ChainReader, header *types.Header, pa
return errUnknownBlock
}
// Retrieve the snapshot needed to verify this header and cache it
snap, err := d.snapshot1(chain, number-1, header.ParentHash, parents)
snap, err := d.snapshot1(chain, header, parents)
if err != nil {
return err
}
Expand Down Expand Up @@ -285,28 +378,14 @@ func (d *Dccs) verifySeal1(chain consensus.ChainReader, header *types.Header, pa
// header for running the transactions on top.
func (d *Dccs) prepare1(chain consensus.ChainReader, header *types.Header) error {
header.Nonce = types.BlockNonce{}
// Get the beneficiary of signer from smart contract and set to header's coinbase to give sealing reward later
number := header.Number.Uint64()
cp := d.config.Snapshot(number)
checkpoint := chain.GetHeaderByNumber(cp)
if checkpoint != nil {
root, _ := chain.StateAt(checkpoint.Root)
index := common.BigToHash(common.Big1).String()[2:]
coinbase := "0x000000000000000000000000" + header.Coinbase.String()[2:]
key := crypto.Keccak256Hash(hexutil.MustDecode(coinbase + index))
result := root.GetState(chain.Config().Dccs.Contract, key)
beneficiary := common.HexToAddress(result.Hex())
header.Coinbase = beneficiary
} else {
log.Error("state is not available at the block number", "number", cp)
return errors.New("state is not available at the block number")
}
d.prepareBeneficiary(header, chain)

// Set the correct difficulty
snap, err := d.snapshot1(chain, number-1, header.ParentHash, nil)
snap, err := d.snapshot1(chain, header, nil)
if err != nil {
return err
}
number := header.Number.Uint64()
parent := chain.GetHeader(header.ParentHash, number-1)
if parent == nil {
return consensus.ErrUnknownAncestor
Expand Down Expand Up @@ -338,6 +417,47 @@ func (d *Dccs) prepare1(chain consensus.ChainReader, header *types.Header) error
return nil
}

// prepareBeneficiary gets the beneficiary of signer from smart contract and
// set to header's coinbase to give sealing reward later.
// if all else fails, the sealer address is kept as reward beneficiary
func (d *Dccs) prepareBeneficiary(header *types.Header, chain consensus.ChainReader) {
index := common.BigToHash(common.Big1).String()[2:]
sealer := "0x000000000000000000000000" + header.Coinbase.String()[2:]
key := crypto.Keccak256Hash(hexutil.MustDecode(sealer + index))

number := header.Number.Uint64()

// try the current active state first
state, err := chain.State()
if err != nil {
log.Error("Chain state not available", "number", number, "err", err)
} else if state != nil {
hash := state.GetState(chain.Config().Dccs.Contract, key)
if (hash != common.Hash{}) {
header.Coinbase = common.HexToAddress(hash.Hex())
return
}
}

// then try the snapshot state
ss := d.config.Snapshot(number)
ssHeader := chain.GetHeaderByNumber(ss)
if ssHeader == nil {
log.Warn("Snapshot header not avaialbe", "for number", number, "snapshot number", ss)
return
}
state, err = chain.StateAt(ssHeader.Root)
if err != nil || state == nil {
log.Warn("Snapshot state not available", "for number", number, "snapshot number", ss, "err", err)
return
}

hash := state.GetState(chain.Config().Dccs.Contract, key)
if (hash != common.Hash{}) {
header.Coinbase = common.HexToAddress(hash.Hex())
}
}

// finalize1 implements consensus.Engine, ensuring no uncles are set, nor block
// rewards given, and returns the final block.
func (d *Dccs) finalize1(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) {
Expand Down Expand Up @@ -379,7 +499,7 @@ func (d *Dccs) seal1(chain consensus.ChainReader, block *types.Block, results ch
d.lock.RUnlock()

// Bail out if we're unauthorized to sign a block
snap, err := d.snapshot1(chain, number-1, header.ParentHash, nil)
snap, err := d.snapshot1(chain, header, nil)
if err != nil {
return err
}
Expand Down
15 changes: 9 additions & 6 deletions consensus/dccs/1_snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,15 @@ func (s *Snapshot) init1() *Snapshot {

// signers1 retrieves the list of authorized signers in hash ascending order.
func (s *Snapshot) signers1() []Signer {
sigs := make([]Signer, 0, len(s.Signers))
for sig := range s.Signers {
sigs = append(sigs, Signer{Hash: s.Hash, Address: sig})
}
sort.Sort(signersAscendingByHash(sigs))
return sigs
s.sortedOnce.Do(func() {
sigs := make([]Signer, 0, len(s.Signers))
for sig := range s.Signers {
sigs = append(sigs, Signer{Hash: s.Hash, Address: sig})
}
sort.Sort(signersAscendingByHash(sigs))
s.sorted = sigs
})
return s.sorted
}

// inturn2 returns if a signer at a given block height is in-turn or not.
Expand Down
8 changes: 4 additions & 4 deletions consensus/dccs/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func (api *API) GetSnapshot(number *rpc.BlockNumber) (*Snapshot, error) {
if header == nil {
return nil, errUnknownBlock
}
return api.dccs.snapshot1(api.chain, header.Number.Uint64(), header.Hash(), nil)
return api.dccs.snapshot1(api.chain, header, nil)
}

// GetSnapshotAtHash retrieves the state snapshot at a given block.
Expand All @@ -52,7 +52,7 @@ func (api *API) GetSnapshotAtHash(hash common.Hash) (*Snapshot, error) {
if header == nil {
return nil, errUnknownBlock
}
return api.dccs.snapshot1(api.chain, header.Number.Uint64(), header.Hash(), nil)
return api.dccs.snapshot1(api.chain, header, nil)
}

// GetSigners retrieves the list of authorized signers at the specified block.
Expand All @@ -68,7 +68,7 @@ func (api *API) GetSigners(number *rpc.BlockNumber) ([]Signer, error) {
if header == nil {
return nil, errUnknownBlock
}
snap, err := api.dccs.snapshot1(api.chain, header.Number.Uint64(), header.Hash(), nil)
snap, err := api.dccs.snapshot1(api.chain, header, nil)
if err != nil {
return nil, err
}
Expand All @@ -81,7 +81,7 @@ func (api *API) GetSignersAtHash(hash common.Hash) ([]Signer, error) {
if header == nil {
return nil, errUnknownBlock
}
snap, err := api.dccs.snapshot1(api.chain, header.Number.Uint64(), header.Hash(), nil)
snap, err := api.dccs.snapshot1(api.chain, header, nil)
if err != nil {
return nil, err
}
Expand Down
Loading

0 comments on commit 67fec1a

Please sign in to comment.