mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 10:22:23 +00:00
Merge f1c8bdc760 into 7bb95a9a64
This commit is contained in:
commit
e6225a41a3
3 changed files with 196 additions and 27 deletions
|
|
@ -20,6 +20,7 @@ package clique
|
|||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"sync"
|
||||
|
|
@ -166,19 +167,12 @@ func sigHash(header *types.Header) (hash common.Hash) {
|
|||
return hash
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
func RecoverSealer(header *types.Header) (common.Address, error) {
|
||||
// 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 {
|
||||
|
|
@ -186,7 +180,20 @@ func ecrecover(header *types.Header, sigcache *lru.ARCCache) (common.Address, er
|
|||
}
|
||||
var signer common.Address
|
||||
copy(signer[:], crypto.Keccak256(pubkey[1:])[12:])
|
||||
return signer, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
signer, err := RecoverSealer(header)
|
||||
if err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
sigcache.Add(hash, signer)
|
||||
return signer, nil
|
||||
}
|
||||
|
|
@ -341,7 +348,7 @@ func (c *Clique) verifyCascadingFields(chain consensus.ChainReader, header *type
|
|||
parent = chain.GetHeader(header.ParentHash, number-1)
|
||||
}
|
||||
if parent == nil || parent.Number.Uint64() != number-1 || parent.Hash() != header.ParentHash {
|
||||
return consensus.ErrUnknownAncestor
|
||||
return fmt.Errorf("unknown ancestor [0x%x] for block %d", header.ParentHash, number)
|
||||
}
|
||||
if parent.Time.Uint64()+c.config.Period > header.Time.Uint64() {
|
||||
return ErrInvalidTimestamp
|
||||
|
|
@ -411,14 +418,14 @@ func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash commo
|
|||
// 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
|
||||
return nil, fmt.Errorf("unknown ancestor [0x%x] for block %d", header.ParentHash, number)
|
||||
}
|
||||
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 {
|
||||
return nil, consensus.ErrUnknownAncestor
|
||||
return nil, fmt.Errorf("unknown ancestor [0x%x] for block %d", hash, number)
|
||||
}
|
||||
}
|
||||
headers = append(headers, header)
|
||||
|
|
@ -481,23 +488,23 @@ func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, p
|
|||
return err
|
||||
}
|
||||
if _, ok := snap.Signers[signer]; !ok {
|
||||
return errUnauthorized
|
||||
return fmt.Errorf("unauthorised[2]: signer 0x%x is not one of the signers", signer)
|
||||
}
|
||||
for seen, recent := range snap.Recents {
|
||||
if recent == signer {
|
||||
// Signer is among recents, only fail if the current block doesn't shift it out
|
||||
if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit {
|
||||
return errUnauthorized
|
||||
return fmt.Errorf("unauthorised: signer 0x%x recently signed", signer)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Ensure that the difficulty corresponds to the turn-ness of the signer
|
||||
inturn := snap.inturn(header.Number.Uint64(), signer)
|
||||
if inturn && header.Difficulty.Cmp(diffInTurn) != 0 {
|
||||
return errInvalidDifficulty
|
||||
return fmt.Errorf("Invalid difficulty [block %d], was %d expected %d (in-turn)", header.Number, header.Difficulty, diffInTurn)
|
||||
}
|
||||
if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 {
|
||||
return errInvalidDifficulty
|
||||
return fmt.Errorf("Invalid difficulty [block %d], was %d expected %d (out-of-turn)", header.Number, header.Difficulty, diffNoTurn)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -558,7 +565,7 @@ func (c *Clique) Prepare(chain consensus.ChainReader, header *types.Header) erro
|
|||
// Ensure the timestamp has the correct delay
|
||||
parent := chain.GetHeader(header.ParentHash, number-1)
|
||||
if parent == nil {
|
||||
return consensus.ErrUnknownAncestor
|
||||
return fmt.Errorf("unknown ancestor [0x%x] for block %d", header.ParentHash, number)
|
||||
}
|
||||
header.Time = new(big.Int).Add(parent.Time, new(big.Int).SetUint64(c.config.Period))
|
||||
if header.Time.Int64() < time.Now().Unix() {
|
||||
|
|
@ -613,7 +620,7 @@ func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, results c
|
|||
return err
|
||||
}
|
||||
if _, authorized := snap.Signers[signer]; !authorized {
|
||||
return errUnauthorized
|
||||
return fmt.Errorf("unauthorised[1]: signer 0x%x is not one of the signers", signer)
|
||||
}
|
||||
// If we're amongst the recent signers, wait for the next block
|
||||
for seen, recent := range snap.Recents {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ package clique
|
|||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"io"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
|
|
@ -42,6 +45,8 @@ type testerVote struct {
|
|||
// keys capable of signing transactions.
|
||||
type testerAccountPool struct {
|
||||
accounts map[string]*ecdsa.PrivateKey
|
||||
// can be used to always yield the same accounts
|
||||
fakePRNG io.Reader
|
||||
}
|
||||
|
||||
func newTesterAccountPool() *testerAccountPool {
|
||||
|
|
@ -49,22 +54,53 @@ func newTesterAccountPool() *testerAccountPool {
|
|||
accounts: make(map[string]*ecdsa.PrivateKey),
|
||||
}
|
||||
}
|
||||
func newDeterministicTesterAccountPool() *testerAccountPool {
|
||||
return &testerAccountPool{
|
||||
accounts: make(map[string]*ecdsa.PrivateKey),
|
||||
fakePRNG: &fakeRand{},
|
||||
}
|
||||
}
|
||||
|
||||
func (ap *testerAccountPool) sign(header *types.Header, signer string) {
|
||||
type fakeRand struct {
|
||||
counter uint64
|
||||
}
|
||||
|
||||
func (f *fakeRand) Read(p []byte) (n int, err error) {
|
||||
for i := 0; i < len(p); i++ {
|
||||
p[i] = byte(f.counter)
|
||||
}
|
||||
f.counter++
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (ap *testerAccountPool) getSignature(header *types.Header, signer string) ([]byte, error) {
|
||||
// Ensure we have a persistent key for the signer
|
||||
if ap.accounts[signer] == nil {
|
||||
if ap.fakePRNG != nil {
|
||||
ap.accounts[signer], _ = ecdsa.GenerateKey(crypto.S256(), ap.fakePRNG)
|
||||
} else {
|
||||
ap.accounts[signer], _ = crypto.GenerateKey()
|
||||
}
|
||||
}
|
||||
// Sign the header and embed the signature in extra data
|
||||
sig, _ := crypto.Sign(sigHash(header).Bytes(), ap.accounts[signer])
|
||||
return crypto.Sign(sigHash(header).Bytes(), ap.accounts[signer])
|
||||
}
|
||||
|
||||
func (ap *testerAccountPool) sign(header *types.Header, signer string) {
|
||||
// Sign the header and embed the signature in extra data
|
||||
sig, _ := ap.getSignature(header, signer)
|
||||
copy(header.Extra[len(header.Extra)-65:], sig)
|
||||
}
|
||||
|
||||
func (ap *testerAccountPool) address(account string) common.Address {
|
||||
// Ensure we have a persistent key for the account
|
||||
if ap.accounts[account] == nil {
|
||||
if ap.fakePRNG != nil {
|
||||
ap.accounts[account], _ = ecdsa.GenerateKey(crypto.S256(), ap.fakePRNG)
|
||||
} else {
|
||||
ap.accounts[account], _ = crypto.GenerateKey()
|
||||
}
|
||||
}
|
||||
// Resolve and return the Ethereum address
|
||||
return crypto.PubkeyToAddress(ap.accounts[account].PublicKey)
|
||||
}
|
||||
|
|
@ -77,14 +113,127 @@ type testerChainReader struct {
|
|||
|
||||
func (r *testerChainReader) Config() *params.ChainConfig { return params.AllCliqueProtocolChanges }
|
||||
func (r *testerChainReader) CurrentHeader() *types.Header { panic("not supported") }
|
||||
func (r *testerChainReader) GetHeader(common.Hash, uint64) *types.Header { panic("not supported") }
|
||||
func (r *testerChainReader) GetHeader(hash common.Hash, number uint64) *types.Header {
|
||||
return rawdb.ReadHeader(r.db, hash, number)
|
||||
}
|
||||
func (r *testerChainReader) GetBlock(common.Hash, uint64) *types.Block { panic("not supported") }
|
||||
func (r *testerChainReader) GetHeaderByHash(common.Hash) *types.Header { panic("not supported") }
|
||||
func (r *testerChainReader) GetHeaderByNumber(number uint64) *types.Header {
|
||||
if number == 0 {
|
||||
return rawdb.ReadHeader(r.db, rawdb.ReadCanonicalHash(r.db, 0), 0)
|
||||
}
|
||||
return nil
|
||||
return rawdb.ReadHeader(r.db, rawdb.ReadCanonicalHash(r.db, number), number)
|
||||
}
|
||||
|
||||
func TestCliqueReorgAtEpoch(t *testing.T) {
|
||||
|
||||
// Create the account pool and generate the initial set of signers
|
||||
accounts := newDeterministicTesterAccountPool()
|
||||
|
||||
// 7 signers
|
||||
signerlist := []string{"A", "B", "C", "D", "E", "F", "G"}
|
||||
|
||||
signers := make([]common.Address, len(signerlist))
|
||||
for j, signer := range signerlist {
|
||||
signers[j] = accounts.address(signer)
|
||||
}
|
||||
// Sort the signers
|
||||
for j := 0; j < len(signers); j++ {
|
||||
for k := j + 1; k < len(signers); k++ {
|
||||
if bytes.Compare(signers[j][:], signers[k][:]) > 0 {
|
||||
signers[j], signers[k] = signers[k], signers[j]
|
||||
signerlist[j], signerlist[k] = signerlist[k], signerlist[j]
|
||||
}
|
||||
}
|
||||
}
|
||||
// Create the genesis block with the initial set of signers
|
||||
genesis := &core.Genesis{
|
||||
ExtraData: make([]byte, extraVanity+common.AddressLength*len(signers)+extraSeal),
|
||||
}
|
||||
for j, signer := range signers {
|
||||
copy(genesis.ExtraData[extraVanity+j*common.AddressLength:], signer[:])
|
||||
fmt.Printf("adding signer 0x%x\n", signer)
|
||||
}
|
||||
sealerListBytes := genesis.ExtraData
|
||||
// Create a pristine blockchain with the genesis injected
|
||||
db := ethdb.NewMemDatabase()
|
||||
genesis.Commit(db)
|
||||
|
||||
engine := New(¶ms.CliqueConfig{Epoch: 5}, db)
|
||||
|
||||
// Initialize a fresh chain with only a genesis block
|
||||
blockchain, _ := core.NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{})
|
||||
snap, err := engine.snapshot(&testerChainReader{db: db}, blockchain.CurrentBlock().NumberU64(), blockchain.CurrentBlock().Hash(), nil)
|
||||
for addy, _ := range snap.Signers {
|
||||
fmt.Printf("signer: 0x%x\n", addy)
|
||||
}
|
||||
|
||||
// Generate blocks, up to the checkpoint block
|
||||
// This segment should with the last block being of higher difficulty: in-turn
|
||||
blocks2, _ := core.GenerateChainSeal(params.TestChainConfig, blockchain.CurrentBlock(), engine, db, 6,
|
||||
func(i int, b *core.BlockGen) {
|
||||
b.SetDifficulty(diffNoTurn)
|
||||
extraData := make([]byte, extraVanity+extraSeal)
|
||||
b.SetExtra(extraData)
|
||||
if i+1 == 5 {
|
||||
// Checkpoint block
|
||||
b.SetExtra(sealerListBytes)
|
||||
b.SetDifficulty(diffInTurn)
|
||||
}
|
||||
},
|
||||
func(b *types.Block) *types.Header {
|
||||
// Seal it
|
||||
hdr := b.Header()
|
||||
if hdr.Number.Uint64() >= 5 {
|
||||
k := 5 % len(signerlist)
|
||||
sig, _ := accounts.getSignature(hdr, signerlist[k])
|
||||
extradata := b.Extra()
|
||||
copy(extradata[len(extradata)-65:], sig)
|
||||
hdr.Extra = extradata
|
||||
} else {
|
||||
k := int(b.NumberU64()+5) % len(signerlist)
|
||||
sig, _ := accounts.getSignature(hdr, signerlist[k])
|
||||
extradata := b.Extra()
|
||||
copy(extradata[len(extradata)-65:], sig)
|
||||
hdr.Extra = extradata
|
||||
|
||||
}
|
||||
return hdr
|
||||
})
|
||||
|
||||
addBlocks := func(blocks []*types.Block) {
|
||||
fmt.Printf("Importing %d blocks\n", len(blocks))
|
||||
for _, block := range blocks {
|
||||
author, _ := engine.Author(block.Header())
|
||||
fmt.Printf(" Adding block %d: 0x%x, sealer: 0x%x, len(extra): %v, difficulty %d\n", block.Number(), block.Header().Hash(), author, len(block.Extra()), block.Difficulty())
|
||||
}
|
||||
if _, err := blockchain.InsertChain(blocks); err != nil {
|
||||
t.Fatalf("failed to import blocks: %v", err)
|
||||
}
|
||||
}
|
||||
showRecents := func() {
|
||||
snap, err = engine.snapshot(&testerChainReader{db: db}, blockchain.CurrentBlock().NumberU64(), blockchain.CurrentBlock().Hash(), nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate snapshot: %v", err)
|
||||
}
|
||||
fmt.Printf("Recents:\n")
|
||||
for block, addy := range snap.Recents {
|
||||
fmt.Printf("%v 0x%x\n", block, addy)
|
||||
}
|
||||
}
|
||||
addBlocks(blocks2[0:4])
|
||||
showRecents()
|
||||
addBlocks(blocks2[4:5])
|
||||
showRecents()
|
||||
addBlocks(blocks2[5:6])
|
||||
showRecents()
|
||||
fmt.Printf("Current head: 0x%x\n", blockchain.CurrentBlock().Header().Hash())
|
||||
for block, addy := range snap.Recents {
|
||||
fmt.Printf("%v 0x%x\n", block, addy)
|
||||
}
|
||||
if blockchain.CurrentBlock().Number().Uint64() == 6 {
|
||||
t.Error("Block should have been rejected")
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that voting is evaluated correctly for various simple and complex scenarios.
|
||||
|
|
|
|||
|
|
@ -128,6 +128,11 @@ func (b *BlockGen) AddUncle(h *types.Header) {
|
|||
b.uncles = append(b.uncles, h)
|
||||
}
|
||||
|
||||
// SetDifficulty sets difficulty of the block
|
||||
func (b *BlockGen) SetDifficulty(d *big.Int) {
|
||||
b.header.Difficulty = d
|
||||
}
|
||||
|
||||
// PrevBlock returns a previously generated block by number. It panics if
|
||||
// num is greater or equal to the number of the block being generated.
|
||||
// For index -1, PrevBlock returns the parent block given to GenerateChain.
|
||||
|
|
@ -165,6 +170,9 @@ func (b *BlockGen) OffsetTime(seconds int64) {
|
|||
// values. Inserting them into BlockChain requires use of FakePow or
|
||||
// a similar non-validating proof of work implementation.
|
||||
func GenerateChain(config *params.ChainConfig, parent *types.Block, engine consensus.Engine, db ethdb.Database, n int, gen func(int, *BlockGen)) ([]*types.Block, []types.Receipts) {
|
||||
return GenerateChainSeal(config, parent, engine, db, n, gen, nil)
|
||||
}
|
||||
func GenerateChainSeal(config *params.ChainConfig, parent *types.Block, engine consensus.Engine, db ethdb.Database, n int, gen func(int, *BlockGen), sealer func(block *types.Block) *types.Header) ([]*types.Block, []types.Receipts) {
|
||||
if config == nil {
|
||||
config = params.TestChainConfig
|
||||
}
|
||||
|
|
@ -205,6 +213,11 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
|
|||
if err := statedb.Database().TrieDB().Commit(root, false); err != nil {
|
||||
panic(fmt.Sprintf("trie write error: %v", err))
|
||||
}
|
||||
if sealer != nil {
|
||||
header := sealer(block)
|
||||
return types.NewBlock(header, b.txs, b.uncles, b.receipts), nil
|
||||
}
|
||||
|
||||
return block, b.receipts
|
||||
}
|
||||
return nil, nil
|
||||
|
|
|
|||
Loading…
Reference in a new issue