Skip to content

Commit 67fec1a

Browse files
authored
Merge pull request ethereum#265 from nextyio/stateless
Stateless header chain verification
2 parents 450a85a + e1802b6 commit 67fec1a

File tree

6 files changed

+228
-106
lines changed

6 files changed

+228
-106
lines changed

consensus/dccs/1_dccs.go

Lines changed: 194 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ package dccs
2020
import (
2121
"bytes"
2222
"errors"
23+
"fmt"
2324
"math"
2425
"math/big"
2526
"time"
@@ -42,6 +43,14 @@ import (
4243
"github.com/ethereum/go-ethereum/params"
4344
)
4445

46+
var (
47+
errSnapshotNotAvailable = errors.New("Snapshot contract not available")
48+
errNotCheckpoint = errors.New("Not a checkpoint block")
49+
errNotSnapshot = errors.New("Not a snapshot block")
50+
errMissingHeaderExtra = errors.New("missing extra data in header")
51+
errMissingCheckpointSigners = errors.New("missing signer list on checkpoint block")
52+
)
53+
4554
var (
4655
rewards = []*big.Int{
4756
big.NewInt(1e+4),
@@ -148,7 +157,7 @@ func (d *Dccs) verifyCascadingFields1(chain consensus.ChainReader, header *types
148157
return ErrInvalidTimestamp
149158
}
150159
// Retrieve the snapshot needed to verify this header and cache it
151-
snap, err := d.snapshot1(chain, number-1, header.ParentHash, parents)
160+
snap, err := d.snapshot1(chain, header, parents)
152161
if err != nil {
153162
return err
154163
}
@@ -168,66 +177,150 @@ func (d *Dccs) verifyCascadingFields1(chain consensus.ChainReader, header *types
168177
}
169178

170179
// snapshot1 retrieves the authorization snapshot at a given point in time.
171-
func (d *Dccs) snapshot1(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) {
172-
// Search for a snapshot in memory or on disk for checkpoints
173-
var (
174-
snap *Snapshot
175-
)
176-
cp := d.config.Snapshot(number + 1)
177-
// looping until data/state are available on local
178-
for snap == nil {
179-
// Get signers from Nexty staking smart contract at the latest epoch checkpoint from block number
180-
checkpoint := chain.GetHeaderByNumber(cp)
181-
if checkpoint == nil {
182-
log.Trace("snapshot header not available", "number", cp)
183-
continue
184-
}
185-
hash := checkpoint.Hash()
186-
log.Trace("Reading signers from epoch checkpoint", "number", cp, "hash", hash)
187-
// If an in-memory snapshot was found, use that
188-
if s, ok := d.recents.Get(hash); ok && number+1 != d.config.ThangLongBlock.Uint64() {
189-
snap = s.(*Snapshot)
190-
log.Trace("Loading snapshot from mem-cache", "hash", snap.Hash, "length", len(snap.signers()))
191-
break
192-
}
193-
state, err := chain.StateAt(checkpoint.Root)
194-
if state == nil || err != nil {
195-
log.Trace("snapshot state not available", "number", cp, "err", err)
196-
continue
197-
}
198-
size := state.GetCodeSize(chain.Config().Dccs.Contract)
199-
if size <= 0 || state.Error() != nil {
200-
log.Trace("snapshot contract state not available", "number", cp, "err", state.Error())
201-
continue
202-
}
203-
index := common.BigToHash(common.Big0)
204-
result := state.GetState(chain.Config().Dccs.Contract, index)
205-
var length int64
206-
if (result == common.Hash{}) {
207-
length = 0
208-
} else {
209-
length = result.Big().Int64()
210-
}
211-
log.Trace("Total number of signer from staking smart contract", "length", length)
212-
signers := make([]common.Address, length)
213-
key := crypto.Keccak256Hash(hexutil.MustDecode(index.String()))
214-
for i := 0; i < len(signers); i++ {
215-
log.Trace("key hash", "key", key)
216-
singer := state.GetState(chain.Config().Dccs.Contract, key)
217-
signers[i] = common.HexToAddress(singer.Hex())
218-
key = key.Plus()
180+
//
181+
// Note: signers list for checkpoint block is gotten from it's snapshot block,
182+
// which is (checkpoint-canonicalDepth). This list is then recorded to the
183+
// checkpoint block extra itself.
184+
// So the signers list for block n can be retrieved from either:
185+
// 1. header extra of block checkpoint(n), or
186+
// 2. state of block snapshot(n) => this is required for miner
187+
func (d *Dccs) snapshot1(chain consensus.ChainReader, header *types.Header, parents []*types.Header) (*Snapshot, error) {
188+
number := header.Number.Uint64()
189+
ssNumber := d.config.Snapshot(number)
190+
ssHeader := getAvailableHeader(ssNumber, header, parents, chain)
191+
if ssHeader == nil {
192+
log.Error("💀 Snapshot header missing", "snapshot number", ssNumber, "for number", number)
193+
return nil, errSnapshotNotAvailable
194+
}
195+
if s, ok := d.recents.Get(ssHeader.Hash()); ok {
196+
// in-memory snapshot found
197+
snap := s.(*Snapshot)
198+
log.Trace("Snapshot loaded from mem-cache", "snapshot number", snap.Number, "hash", snap.Hash, "signers length", len(snap.Signers), "for number", header.Number)
199+
return snap, nil
200+
}
201+
202+
if d.config.IsCheckpoint(number) {
203+
snap, err := d.getStateSnapshot(chain, ssHeader)
204+
if err == nil && snap != nil {
205+
log.Trace("Snapshot retrieved from state and cached", "for number", header.Number, "snapshot number", snap.Number, "hash", snap.Hash)
206+
// Store found snapshot into mem-cache
207+
d.recents.Add(snap.Hash, snap)
208+
return snap, nil
219209
}
220-
snap = newSnapshot(d.config, d.signatures, number, hash, signers)
221-
// Store found snapshot into mem-cache
222-
d.recents.Add(snap.Hash, snap)
223-
break
210+
log.Warn("☢ Snapshot state missing for checkpoint block, continue at your own risk", "snapshot number", ssNumber, "for number", number, "err", err)
224211
}
225212

226-
// Set current block number for snapshot to calculate the inturn & difficulty
227-
snap.Number = number
213+
snap, err := d.getHeaderSnapshotFor(header, chain, parents)
214+
if err != nil || snap == nil {
215+
return nil, err
216+
}
217+
// Store found snapshot into mem-cache
218+
d.recents.Add(snap.Hash, snap)
228219
return snap, nil
229220
}
230221

222+
func (d *Dccs) getStateSnapshot(chain consensus.ChainReader, header *types.Header) (*Snapshot, error) {
223+
number := header.Number.Uint64()
224+
state, err := chain.StateAt(header.Root)
225+
if state == nil || err != nil {
226+
log.Trace("Snapshot state not available", "number", number, "err", err)
227+
return nil, errSnapshotNotAvailable
228+
}
229+
size := state.GetCodeSize(chain.Config().Dccs.Contract)
230+
if size <= 0 || state.Error() != nil {
231+
log.Trace("Snapshot contract state not available", "number", number, "err", state.Error())
232+
return nil, errSnapshotNotAvailable
233+
}
234+
index := common.BigToHash(common.Big0)
235+
result := state.GetState(chain.Config().Dccs.Contract, index)
236+
var length int64
237+
if (result == common.Hash{}) {
238+
length = 0
239+
} else {
240+
length = result.Big().Int64()
241+
}
242+
log.Trace("Total number of signer from staking smart contract", "length", length)
243+
signers := make([]common.Address, length)
244+
key := crypto.Keccak256Hash(hexutil.MustDecode(index.String()))
245+
for i := 0; i < len(signers); i++ {
246+
log.Trace("key hash", "key", key)
247+
singer := state.GetState(chain.Config().Dccs.Contract, key)
248+
signers[i] = common.HexToAddress(singer.Hex())
249+
key = key.Plus()
250+
}
251+
return newSnapshot(d.config, d.signatures, number, header.Hash(), signers), nil
252+
}
253+
254+
// getHeaderFromInput returns either:
255+
// + the input header, if number == header.Number
256+
// + the header in parents if available (nessesary for batch headers processing)
257+
// + chain.GetHeaderByNumber(number), if all else fail
258+
func getAvailableHeader(number uint64, header *types.Header, parents []*types.Header, chain consensus.ChainReader) *types.Header {
259+
headerNumber := header.Number.Uint64()
260+
if number == headerNumber {
261+
return header
262+
}
263+
if number > headerNumber {
264+
return chain.GetHeaderByNumber(number)
265+
}
266+
idx := len(parents) - int(headerNumber) + int(number)
267+
if idx >= 0 {
268+
header := parents[idx]
269+
if header.Number.Uint64() == number {
270+
return header
271+
}
272+
log.Error("invalid parrent number", "expected", number, "actual", header.Number)
273+
}
274+
return chain.GetHeaderByNumber(number)
275+
}
276+
277+
func (d *Dccs) getHeaderSnapshotFor(header *types.Header, chain consensus.ChainReader, parents []*types.Header) (*Snapshot, error) {
278+
number := header.Number.Uint64()
279+
cp := d.config.Checkpoint(number)
280+
cpHeader := getAvailableHeader(cp, header, parents, chain)
281+
if cpHeader == nil {
282+
return nil, fmt.Errorf("checkpoint header missing: checkpoint = %v, header = %v", cp, number)
283+
}
284+
ss := d.config.Snapshot(number)
285+
ssHeader := getAvailableHeader(ss, header, parents, chain)
286+
return d.getHeaderSnapshot(cpHeader, ssHeader)
287+
}
288+
289+
// the signer list is retrieved from checkpoint header extra,
290+
// but the snapshot hash is hash of snapshot header
291+
func (d *Dccs) getHeaderSnapshot(cpHeader, ssHeader *types.Header) (*Snapshot, error) {
292+
cp := cpHeader.Number.Uint64()
293+
if !d.config.IsCheckpoint(cp) {
294+
return nil, errNotCheckpoint
295+
}
296+
ss := ssHeader.Number.Uint64()
297+
if d.config.Snapshot(cp) != ss {
298+
return nil, errNotSnapshot
299+
}
300+
if len(cpHeader.Extra) <= extraVanity+extraSeal {
301+
return nil, errMissingCheckpointSigners
302+
}
303+
extraSuffix := len(cpHeader.Extra) - extraSeal
304+
extraSealers := cpHeader.Extra[extraVanity:extraSuffix]
305+
if len(extraSealers) == 0 {
306+
log.Error("empty sealers list", "number", cp, "extra", common.Bytes2Hex(cpHeader.Extra))
307+
return nil, errMissingCheckpointSigners
308+
}
309+
if len(extraSealers)%common.AddressLength != 0 {
310+
log.Error("not divided by common.AddressLength", "number", cp, "actual", common.Bytes2Hex(extraSealers))
311+
return nil, errInvalidCheckpointSigners
312+
}
313+
signersCount := len(extraSealers) / common.AddressLength
314+
signers := make([]common.Address, signersCount)
315+
for i := 0; i < signersCount; i++ {
316+
offset := i * common.AddressLength
317+
signers[i] = common.BytesToAddress(extraSealers[offset : offset+common.AddressLength])
318+
}
319+
320+
log.Trace("Snapshot parsed from checkpoint header", "snapshot number", ss, "hash", ssHeader.Hash(), "for number", cpHeader.Number)
321+
return newSnapshot(d.config, d.signatures, ss, ssHeader.Hash(), signers), nil
322+
}
323+
231324
// verifySeal1 checks whether the signature contained in the header satisfies the
232325
// consensus protocol requirements. The method accepts an optional list of parent
233326
// headers that aren't yet part of the local blockchain to generate the snapshots
@@ -239,7 +332,7 @@ func (d *Dccs) verifySeal1(chain consensus.ChainReader, header *types.Header, pa
239332
return errUnknownBlock
240333
}
241334
// Retrieve the snapshot needed to verify this header and cache it
242-
snap, err := d.snapshot1(chain, number-1, header.ParentHash, parents)
335+
snap, err := d.snapshot1(chain, header, parents)
243336
if err != nil {
244337
return err
245338
}
@@ -285,28 +378,14 @@ func (d *Dccs) verifySeal1(chain consensus.ChainReader, header *types.Header, pa
285378
// header for running the transactions on top.
286379
func (d *Dccs) prepare1(chain consensus.ChainReader, header *types.Header) error {
287380
header.Nonce = types.BlockNonce{}
288-
// Get the beneficiary of signer from smart contract and set to header's coinbase to give sealing reward later
289-
number := header.Number.Uint64()
290-
cp := d.config.Snapshot(number)
291-
checkpoint := chain.GetHeaderByNumber(cp)
292-
if checkpoint != nil {
293-
root, _ := chain.StateAt(checkpoint.Root)
294-
index := common.BigToHash(common.Big1).String()[2:]
295-
coinbase := "0x000000000000000000000000" + header.Coinbase.String()[2:]
296-
key := crypto.Keccak256Hash(hexutil.MustDecode(coinbase + index))
297-
result := root.GetState(chain.Config().Dccs.Contract, key)
298-
beneficiary := common.HexToAddress(result.Hex())
299-
header.Coinbase = beneficiary
300-
} else {
301-
log.Error("state is not available at the block number", "number", cp)
302-
return errors.New("state is not available at the block number")
303-
}
381+
d.prepareBeneficiary(header, chain)
304382

305383
// Set the correct difficulty
306-
snap, err := d.snapshot1(chain, number-1, header.ParentHash, nil)
384+
snap, err := d.snapshot1(chain, header, nil)
307385
if err != nil {
308386
return err
309387
}
388+
number := header.Number.Uint64()
310389
parent := chain.GetHeader(header.ParentHash, number-1)
311390
if parent == nil {
312391
return consensus.ErrUnknownAncestor
@@ -338,6 +417,47 @@ func (d *Dccs) prepare1(chain consensus.ChainReader, header *types.Header) error
338417
return nil
339418
}
340419

420+
// prepareBeneficiary gets the beneficiary of signer from smart contract and
421+
// set to header's coinbase to give sealing reward later.
422+
// if all else fails, the sealer address is kept as reward beneficiary
423+
func (d *Dccs) prepareBeneficiary(header *types.Header, chain consensus.ChainReader) {
424+
index := common.BigToHash(common.Big1).String()[2:]
425+
sealer := "0x000000000000000000000000" + header.Coinbase.String()[2:]
426+
key := crypto.Keccak256Hash(hexutil.MustDecode(sealer + index))
427+
428+
number := header.Number.Uint64()
429+
430+
// try the current active state first
431+
state, err := chain.State()
432+
if err != nil {
433+
log.Error("Chain state not available", "number", number, "err", err)
434+
} else if state != nil {
435+
hash := state.GetState(chain.Config().Dccs.Contract, key)
436+
if (hash != common.Hash{}) {
437+
header.Coinbase = common.HexToAddress(hash.Hex())
438+
return
439+
}
440+
}
441+
442+
// then try the snapshot state
443+
ss := d.config.Snapshot(number)
444+
ssHeader := chain.GetHeaderByNumber(ss)
445+
if ssHeader == nil {
446+
log.Warn("Snapshot header not avaialbe", "for number", number, "snapshot number", ss)
447+
return
448+
}
449+
state, err = chain.StateAt(ssHeader.Root)
450+
if err != nil || state == nil {
451+
log.Warn("Snapshot state not available", "for number", number, "snapshot number", ss, "err", err)
452+
return
453+
}
454+
455+
hash := state.GetState(chain.Config().Dccs.Contract, key)
456+
if (hash != common.Hash{}) {
457+
header.Coinbase = common.HexToAddress(hash.Hex())
458+
}
459+
}
460+
341461
// finalize1 implements consensus.Engine, ensuring no uncles are set, nor block
342462
// rewards given, and returns the final block.
343463
func (d *Dccs) finalize1(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) {
@@ -379,7 +499,7 @@ func (d *Dccs) seal1(chain consensus.ChainReader, block *types.Block, results ch
379499
d.lock.RUnlock()
380500

381501
// Bail out if we're unauthorized to sign a block
382-
snap, err := d.snapshot1(chain, number-1, header.ParentHash, nil)
502+
snap, err := d.snapshot1(chain, header, nil)
383503
if err != nil {
384504
return err
385505
}

consensus/dccs/1_snapshot.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,15 @@ func (s *Snapshot) init1() *Snapshot {
5151

5252
// signers1 retrieves the list of authorized signers in hash ascending order.
5353
func (s *Snapshot) signers1() []Signer {
54-
sigs := make([]Signer, 0, len(s.Signers))
55-
for sig := range s.Signers {
56-
sigs = append(sigs, Signer{Hash: s.Hash, Address: sig})
57-
}
58-
sort.Sort(signersAscendingByHash(sigs))
59-
return sigs
54+
s.sortedOnce.Do(func() {
55+
sigs := make([]Signer, 0, len(s.Signers))
56+
for sig := range s.Signers {
57+
sigs = append(sigs, Signer{Hash: s.Hash, Address: sig})
58+
}
59+
sort.Sort(signersAscendingByHash(sigs))
60+
s.sorted = sigs
61+
})
62+
return s.sorted
6063
}
6164

6265
// inturn2 returns if a signer at a given block height is in-turn or not.

consensus/dccs/api.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func (api *API) GetSnapshot(number *rpc.BlockNumber) (*Snapshot, error) {
4343
if header == nil {
4444
return nil, errUnknownBlock
4545
}
46-
return api.dccs.snapshot1(api.chain, header.Number.Uint64(), header.Hash(), nil)
46+
return api.dccs.snapshot1(api.chain, header, nil)
4747
}
4848

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

5858
// GetSigners retrieves the list of authorized signers at the specified block.
@@ -68,7 +68,7 @@ func (api *API) GetSigners(number *rpc.BlockNumber) ([]Signer, error) {
6868
if header == nil {
6969
return nil, errUnknownBlock
7070
}
71-
snap, err := api.dccs.snapshot1(api.chain, header.Number.Uint64(), header.Hash(), nil)
71+
snap, err := api.dccs.snapshot1(api.chain, header, nil)
7272
if err != nil {
7373
return nil, err
7474
}
@@ -81,7 +81,7 @@ func (api *API) GetSignersAtHash(hash common.Hash) ([]Signer, error) {
8181
if header == nil {
8282
return nil, errUnknownBlock
8383
}
84-
snap, err := api.dccs.snapshot1(api.chain, header.Number.Uint64(), header.Hash(), nil)
84+
snap, err := api.dccs.snapshot1(api.chain, header, nil)
8585
if err != nil {
8686
return nil, err
8787
}

0 commit comments

Comments
 (0)