mirror of https://github.com/ethereum/go-ethereum
core, consensus: pluggable consensus engines (#3817)
This commit adds pluggable consensus engines to go-ethereum. In short, it introduces a generic consensus interface, and refactors the entire codebase to use this interface.pull/13866/head
parent
e50a5b7771
commit
09777952ee
@ -0,0 +1,94 @@ |
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package consensus implements different Ethereum consensus engines.
|
||||
package consensus |
||||
|
||||
import ( |
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/state" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/params" |
||||
"github.com/ethereum/go-ethereum/rpc" |
||||
) |
||||
|
||||
// ChainReader defines a small collection of methods needed to access the local
|
||||
// blockchain during header and/or uncle verification.
|
||||
type ChainReader interface { |
||||
// Config retrieves the blockchain's chain configuration.
|
||||
Config() *params.ChainConfig |
||||
|
||||
// CurrentHeader retrieves the current header from the local chain.
|
||||
CurrentHeader() *types.Header |
||||
|
||||
// GetHeader retrieves a block header from the database by hash and number.
|
||||
GetHeader(hash common.Hash, number uint64) *types.Header |
||||
|
||||
// GetHeaderByNumber retrieves a block header from the database by number.
|
||||
GetHeaderByNumber(number uint64) *types.Header |
||||
|
||||
// GetBlock retrieves a block from the database by hash and number.
|
||||
GetBlock(hash common.Hash, number uint64) *types.Block |
||||
} |
||||
|
||||
// Engine is an algorithm agnostic consensus engine.
|
||||
type Engine interface { |
||||
// VerifyHeader checks whether a header conforms to the consensus rules of a
|
||||
// given engine. Verifying the seal may be done optionally here, or explicitly
|
||||
// via the VerifySeal method.
|
||||
VerifyHeader(chain ChainReader, header *types.Header, seal bool) error |
||||
|
||||
// VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers
|
||||
// concurrently. The method returns a quit channel to abort the operations and
|
||||
// a results channel to retrieve the async verifications (the order is that of
|
||||
// the input slice).
|
||||
VerifyHeaders(chain ChainReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) |
||||
|
||||
// VerifyUncles verifies that the given block's uncles conform to the consensus
|
||||
// rules of a given engine.
|
||||
VerifyUncles(chain ChainReader, block *types.Block) error |
||||
|
||||
// VerifySeal checks whether the crypto seal on a header is valid according to
|
||||
// the consensus rules of the given engine.
|
||||
VerifySeal(chain ChainReader, header *types.Header) error |
||||
|
||||
// Prepare initializes the consensus fields of a block header according to the
|
||||
// rules of a particular engine. The changes are executed inline.
|
||||
Prepare(chain ChainReader, header *types.Header) error |
||||
|
||||
// Finalize runs any post-transaction state modifications (e.g. block rewards)
|
||||
// and assembles the final block.
|
||||
//
|
||||
// Note, the block header and state database might be updated to reflect any
|
||||
// consensus rules that happen at finalization (e.g. block rewards).
|
||||
Finalize(chain ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, |
||||
uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) |
||||
|
||||
// Seal generates a new block for the given input block with the local miner's
|
||||
// seal place on top.
|
||||
Seal(chain ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) |
||||
|
||||
// APIs returns the RPC APIs this consensus engine provides.
|
||||
APIs(chain ChainReader) []rpc.API |
||||
} |
||||
|
||||
// PoW is a consensus engine based on proof-of-work.
|
||||
type PoW interface { |
||||
Engine |
||||
|
||||
// Hashrate returns the current mining hashrate of a PoW consensus engine.
|
||||
Hashrate() float64 |
||||
} |
@ -0,0 +1,496 @@ |
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethash |
||||
|
||||
import ( |
||||
"bytes" |
||||
"errors" |
||||
"fmt" |
||||
"math/big" |
||||
"runtime" |
||||
"sync/atomic" |
||||
"time" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/common/math" |
||||
"github.com/ethereum/go-ethereum/consensus" |
||||
"github.com/ethereum/go-ethereum/consensus/misc" |
||||
"github.com/ethereum/go-ethereum/core/state" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/params" |
||||
set "gopkg.in/fatih/set.v0" |
||||
) |
||||
|
||||
// Ethash proof-of-work protocol constants.
|
||||
var ( |
||||
blockReward *big.Int = big.NewInt(5e+18) // Block reward in wei for successfully mining a block
|
||||
maxUncles = 2 // Maximum number of uncles allowed in a single block
|
||||
) |
||||
|
||||
var ( |
||||
ErrInvalidChain = errors.New("invalid header chain") |
||||
ErrParentUnknown = errors.New("parent not known locally") |
||||
ErrFutureBlock = errors.New("block in the future") |
||||
ErrLargeBlockTimestamp = errors.New("timestamp too big") |
||||
ErrZeroBlockTime = errors.New("timestamp equals parent's") |
||||
ErrInvalidNumber = errors.New("invalid block number") |
||||
ErrTooManyUncles = errors.New("too many uncles") |
||||
ErrDuplicateUncle = errors.New("duplicate uncle") |
||||
ErrUncleIsAncestor = errors.New("uncle is ancestor") |
||||
ErrDanglingUncle = errors.New("uncle's parent is not ancestor") |
||||
ErrNonceOutOfRange = errors.New("nonce out of range") |
||||
ErrInvalidDifficulty = errors.New("non-positive difficulty") |
||||
ErrInvalidMixDigest = errors.New("invalid mix digest") |
||||
ErrInvalidPoW = errors.New("invalid proof-of-work") |
||||
) |
||||
|
||||
// VerifyHeader checks whether a header conforms to the consensus rules of the
|
||||
// stock Ethereum ethash engine.
|
||||
func (ethash *Ethash) VerifyHeader(chain consensus.ChainReader, header *types.Header, seal bool) error { |
||||
// If we're running a full engine faking, accept any input as valid
|
||||
if ethash.fakeFull { |
||||
return nil |
||||
} |
||||
// Short circuit if the header is known, or it's parent not
|
||||
number := header.Number.Uint64() |
||||
if chain.GetHeader(header.Hash(), number) != nil { |
||||
return nil |
||||
} |
||||
parent := chain.GetHeader(header.ParentHash, number-1) |
||||
if parent == nil { |
||||
return ErrParentUnknown |
||||
} |
||||
// Sanity checks passed, do a proper verification
|
||||
return ethash.verifyHeader(chain, header, parent, false, seal) |
||||
} |
||||
|
||||
// VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers
|
||||
// concurrently. The method returns a quit channel to abort the operations and
|
||||
// a results channel to retrieve the async verifications.
|
||||
func (ethash *Ethash) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) { |
||||
// If we're running a full engine faking, accept any input as valid
|
||||
if ethash.fakeFull { |
||||
abort, results := make(chan struct{}), make(chan error, len(headers)) |
||||
for i := 0; i < len(headers); i++ { |
||||
results <- nil |
||||
} |
||||
return abort, results |
||||
} |
||||
// Spawn as many workers as allowed threads
|
||||
workers := runtime.GOMAXPROCS(0) |
||||
if len(headers) < workers { |
||||
workers = len(headers) |
||||
} |
||||
// Create a task channel and spawn the verifiers
|
||||
type result struct { |
||||
index int |
||||
err error |
||||
} |
||||
inputs := make(chan int, workers) |
||||
outputs := make(chan result, len(headers)) |
||||
|
||||
var badblock uint64 |
||||
for i := 0; i < workers; i++ { |
||||
go func() { |
||||
for index := range inputs { |
||||
// If we've found a bad block already before this, stop validating
|
||||
if bad := atomic.LoadUint64(&badblock); bad != 0 && bad <= headers[index].Number.Uint64() { |
||||
outputs <- result{index: index, err: ErrInvalidChain} |
||||
continue |
||||
} |
||||
// We need to look up the first parent
|
||||
var parent *types.Header |
||||
if index == 0 { |
||||
parent = chain.GetHeader(headers[0].ParentHash, headers[0].Number.Uint64()-1) |
||||
} else if headers[index-1].Hash() == headers[index].ParentHash { |
||||
parent = headers[index-1] |
||||
} |
||||
// Ensure the validation is useful and execute it
|
||||
var failure error |
||||
switch { |
||||
case chain.GetHeader(headers[index].Hash(), headers[index].Number.Uint64()-1) != nil: |
||||
outputs <- result{index: index, err: nil} |
||||
case parent == nil: |
||||
failure = ErrParentUnknown |
||||
outputs <- result{index: index, err: failure} |
||||
default: |
||||
failure = ethash.verifyHeader(chain, headers[index], parent, false, seals[index]) |
||||
outputs <- result{index: index, err: failure} |
||||
} |
||||
// If a validation failure occurred, mark subsequent blocks invalid
|
||||
if failure != nil { |
||||
number := headers[index].Number.Uint64() |
||||
if prev := atomic.LoadUint64(&badblock); prev == 0 || prev > number { |
||||
// This two step atomic op isn't thread-safe in that `badblock` might end
|
||||
// up slightly higher than the block number of the first failure (if many
|
||||
// workers try to write at the same time), but it's fine as we're mostly
|
||||
// interested to avoid large useless work, we don't care about 1-2 extra
|
||||
// runs. Doing "full thread safety" would involve mutexes, which would be
|
||||
// a noticeable sync overhead on the fast spinning worker routines.
|
||||
atomic.StoreUint64(&badblock, number) |
||||
} |
||||
} |
||||
} |
||||
}() |
||||
} |
||||
// Feed item indices to the workers until done, sorting and feeding the results to the caller
|
||||
dones := make([]bool, len(headers)) |
||||
errors := make([]error, len(headers)) |
||||
|
||||
abort := make(chan struct{}) |
||||
returns := make(chan error, len(headers)) |
||||
|
||||
go func() { |
||||
defer close(inputs) |
||||
|
||||
input, output := 0, 0 |
||||
for i := 0; i < len(headers)*2; i++ { |
||||
var res result |
||||
|
||||
// If there are tasks left, push to workers
|
||||
if input < len(headers) { |
||||
select { |
||||
case inputs <- input: |
||||
input++ |
||||
continue |
||||
case <-abort: |
||||
return |
||||
case res = <-outputs: |
||||
} |
||||
} else { |
||||
// Otherwise keep waiting for results
|
||||
select { |
||||
case <-abort: |
||||
return |
||||
case res = <-outputs: |
||||
} |
||||
} |
||||
// A result arrived, save and propagate if next
|
||||
dones[res.index], errors[res.index] = true, res.err |
||||
for output < len(headers) && dones[output] { |
||||
returns <- errors[output] |
||||
output++ |
||||
} |
||||
} |
||||
}() |
||||
return abort, returns |
||||
} |
||||
|
||||
// VerifyUncles verifies that the given block's uncles conform to the consensus
|
||||
// rules of the stock Ethereum ethash engine.
|
||||
func (ethash *Ethash) VerifyUncles(chain consensus.ChainReader, block *types.Block) error { |
||||
// If we're running a full engine faking, accept any input as valid
|
||||
if ethash.fakeFull { |
||||
return nil |
||||
} |
||||
// Verify that there are at most 2 uncles included in this block
|
||||
if len(block.Uncles()) > maxUncles { |
||||
return ErrTooManyUncles |
||||
} |
||||
// Gather the set of past uncles and ancestors
|
||||
uncles, ancestors := set.New(), make(map[common.Hash]*types.Header) |
||||
|
||||
number, parent := block.NumberU64()-1, block.ParentHash() |
||||
for i := 0; i < 7; i++ { |
||||
ancestor := chain.GetBlock(parent, number) |
||||
if ancestor == nil { |
||||
break |
||||
} |
||||
ancestors[ancestor.Hash()] = ancestor.Header() |
||||
for _, uncle := range ancestor.Uncles() { |
||||
uncles.Add(uncle.Hash()) |
||||
} |
||||
parent, number = ancestor.ParentHash(), number-1 |
||||
} |
||||
ancestors[block.Hash()] = block.Header() |
||||
uncles.Add(block.Hash()) |
||||
|
||||
// Verify each of the uncles that it's recent, but not an ancestor
|
||||
for _, uncle := range block.Uncles() { |
||||
// Make sure every uncle is rewarded only once
|
||||
hash := uncle.Hash() |
||||
if uncles.Has(hash) { |
||||
return ErrDuplicateUncle |
||||
} |
||||
uncles.Add(hash) |
||||
|
||||
// Make sure the uncle has a valid ancestry
|
||||
if ancestors[hash] != nil { |
||||
return ErrUncleIsAncestor |
||||
} |
||||
if ancestors[uncle.ParentHash] == nil || uncle.ParentHash == block.ParentHash() { |
||||
return ErrDanglingUncle |
||||
} |
||||
if err := ethash.verifyHeader(chain, uncle, ancestors[uncle.ParentHash], true, true); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// verifyHeader checks whether a header conforms to the consensus rules of the
|
||||
// stock Ethereum ethash engine.
|
||||
//
|
||||
// See YP section 4.3.4. "Block Header Validity"
|
||||
func (ethash *Ethash) verifyHeader(chain consensus.ChainReader, header, parent *types.Header, uncle bool, seal bool) error { |
||||
// Ensure that the header's extra-data section is of a reasonable size
|
||||
if uint64(len(header.Extra)) > params.MaximumExtraDataSize { |
||||
return fmt.Errorf("extra-data too long: %d > %d", len(header.Extra), params.MaximumExtraDataSize) |
||||
} |
||||
// Verify the header's timestamp
|
||||
if uncle { |
||||
if header.Time.Cmp(math.MaxBig256) > 0 { |
||||
return ErrLargeBlockTimestamp |
||||
} |
||||
} else { |
||||
if header.Time.Cmp(big.NewInt(time.Now().Unix())) > 0 { |
||||
return ErrFutureBlock |
||||
} |
||||
} |
||||
if header.Time.Cmp(parent.Time) <= 0 { |
||||
return ErrZeroBlockTime |
||||
} |
||||
// Verify the block's difficulty based in it's timestamp and parent's difficulty
|
||||
expected := CalcDifficulty(chain.Config(), header.Time.Uint64(), parent.Time.Uint64(), parent.Number, parent.Difficulty) |
||||
if expected.Cmp(header.Difficulty) != 0 { |
||||
return fmt.Errorf("invalid difficulty: have %v, want %v", header.Difficulty, expected) |
||||
} |
||||
// Verify that the gas limit remains within allowed bounds
|
||||
diff := new(big.Int).Set(parent.GasLimit) |
||||
diff = diff.Sub(diff, header.GasLimit) |
||||
diff.Abs(diff) |
||||
|
||||
limit := new(big.Int).Set(parent.GasLimit) |
||||
limit = limit.Div(limit, params.GasLimitBoundDivisor) |
||||
|
||||
if diff.Cmp(limit) >= 0 || header.GasLimit.Cmp(params.MinGasLimit) < 0 { |
||||
return fmt.Errorf("invalid gas limit: have %v, want %v += %v", header.GasLimit, parent.GasLimit, limit) |
||||
} |
||||
// Verify that the block number is parent's +1
|
||||
if diff := new(big.Int).Sub(header.Number, parent.Number); diff.Cmp(big.NewInt(1)) != 0 { |
||||
return ErrInvalidNumber |
||||
} |
||||
// Verify the engine specific seal securing the block
|
||||
if seal { |
||||
if err := ethash.VerifySeal(chain, header); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
// If all checks passed, validate any special fields for hard forks
|
||||
if err := misc.VerifyDAOHeaderExtraData(chain.Config(), header); err != nil { |
||||
return err |
||||
} |
||||
if err := misc.VerifyForkHashes(chain.Config(), header, uncle); err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty
|
||||
// that a new block should have when created at time given the parent block's time
|
||||
// and difficulty.
|
||||
//
|
||||
// TODO (karalabe): Move the chain maker into this package and make this private!
|
||||
func CalcDifficulty(config *params.ChainConfig, time, parentTime uint64, parentNumber, parentDiff *big.Int) *big.Int { |
||||
if config.IsHomestead(new(big.Int).Add(parentNumber, common.Big1)) { |
||||
return calcDifficultyHomestead(time, parentTime, parentNumber, parentDiff) |
||||
} |
||||
return calcDifficultyFrontier(time, parentTime, parentNumber, parentDiff) |
||||
} |
||||
|
||||
// Some weird constants to avoid constant memory allocs for them.
|
||||
var ( |
||||
expDiffPeriod = big.NewInt(100000) |
||||
big10 = big.NewInt(10) |
||||
bigMinus99 = big.NewInt(-99) |
||||
) |
||||
|
||||
// calcDifficultyHomestead is the difficulty adjustment algorithm. It returns
|
||||
// the difficulty that a new block should have when created at time given the
|
||||
// parent block's time and difficulty. The calculation uses the Homestead rules.
|
||||
func calcDifficultyHomestead(time, parentTime uint64, parentNumber, parentDiff *big.Int) *big.Int { |
||||
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2.mediawiki
|
||||
// algorithm:
|
||||
// diff = (parent_diff +
|
||||
// (parent_diff / 2048 * max(1 - (block_timestamp - parent_timestamp) // 10, -99))
|
||||
// ) + 2^(periodCount - 2)
|
||||
|
||||
bigTime := new(big.Int).SetUint64(time) |
||||
bigParentTime := new(big.Int).SetUint64(parentTime) |
||||
|
||||
// holds intermediate values to make the algo easier to read & audit
|
||||
x := new(big.Int) |
||||
y := new(big.Int) |
||||
|
||||
// 1 - (block_timestamp -parent_timestamp) // 10
|
||||
x.Sub(bigTime, bigParentTime) |
||||
x.Div(x, big10) |
||||
x.Sub(common.Big1, x) |
||||
|
||||
// max(1 - (block_timestamp - parent_timestamp) // 10, -99)))
|
||||
if x.Cmp(bigMinus99) < 0 { |
||||
x.Set(bigMinus99) |
||||
} |
||||
// (parent_diff + parent_diff // 2048 * max(1 - (block_timestamp - parent_timestamp) // 10, -99))
|
||||
y.Div(parentDiff, params.DifficultyBoundDivisor) |
||||
x.Mul(y, x) |
||||
x.Add(parentDiff, x) |
||||
|
||||
// minimum difficulty can ever be (before exponential factor)
|
||||
if x.Cmp(params.MinimumDifficulty) < 0 { |
||||
x.Set(params.MinimumDifficulty) |
||||
} |
||||
// for the exponential factor
|
||||
periodCount := new(big.Int).Add(parentNumber, common.Big1) |
||||
periodCount.Div(periodCount, expDiffPeriod) |
||||
|
||||
// the exponential factor, commonly referred to as "the bomb"
|
||||
// diff = diff + 2^(periodCount - 2)
|
||||
if periodCount.Cmp(common.Big1) > 0 { |
||||
y.Sub(periodCount, common.Big2) |
||||
y.Exp(common.Big2, y, nil) |
||||
x.Add(x, y) |
||||
} |
||||
return x |
||||
} |
||||
|
||||
// calcDifficultyFrontier is the difficulty adjustment algorithm. It returns the
|
||||
// difficulty that a new block should have when created at time given the parent
|
||||
// block's time and difficulty. The calculation uses the Frontier rules.
|
||||
func calcDifficultyFrontier(time, parentTime uint64, parentNumber, parentDiff *big.Int) *big.Int { |
||||
diff := new(big.Int) |
||||
adjust := new(big.Int).Div(parentDiff, params.DifficultyBoundDivisor) |
||||
bigTime := new(big.Int) |
||||
bigParentTime := new(big.Int) |
||||
|
||||
bigTime.SetUint64(time) |
||||
bigParentTime.SetUint64(parentTime) |
||||
|
||||
if bigTime.Sub(bigTime, bigParentTime).Cmp(params.DurationLimit) < 0 { |
||||
diff.Add(parentDiff, adjust) |
||||
} else { |
||||
diff.Sub(parentDiff, adjust) |
||||
} |
||||
if diff.Cmp(params.MinimumDifficulty) < 0 { |
||||
diff.Set(params.MinimumDifficulty) |
||||
} |
||||
|
||||
periodCount := new(big.Int).Add(parentNumber, common.Big1) |
||||
periodCount.Div(periodCount, expDiffPeriod) |
||||
if periodCount.Cmp(common.Big1) > 0 { |
||||
// diff = diff + 2^(periodCount - 2)
|
||||
expDiff := periodCount.Sub(periodCount, common.Big2) |
||||
expDiff.Exp(common.Big2, expDiff, nil) |
||||
diff.Add(diff, expDiff) |
||||
diff = math.BigMax(diff, params.MinimumDifficulty) |
||||
} |
||||
return diff |
||||
} |
||||
|
||||
// VerifySeal implements consensus.Engine, checking whether the given block satisfies
|
||||
// the PoW difficulty requirements.
|
||||
func (ethash *Ethash) VerifySeal(chain consensus.ChainReader, header *types.Header) error { |
||||
// If we're running a fake PoW, accept any seal as valid
|
||||
if ethash.fakeMode { |
||||
time.Sleep(ethash.fakeDelay) |
||||
if ethash.fakeFail == header.Number.Uint64() { |
||||
return ErrInvalidPoW |
||||
} |
||||
return nil |
||||
} |
||||
// If we're running a shared PoW, delegate verification to it
|
||||
if ethash.shared != nil { |
||||
return ethash.shared.VerifySeal(chain, header) |
||||
} |
||||
// Sanity check that the block number is below the lookup table size (60M blocks)
|
||||
number := header.Number.Uint64() |
||||
if number/epochLength >= uint64(len(cacheSizes)) { |
||||
// Go < 1.7 cannot calculate new cache/dataset sizes (no fast prime check)
|
||||
return ErrNonceOutOfRange |
||||
} |
||||
// Ensure that we have a valid difficulty for the block
|
||||
if header.Difficulty.Sign() <= 0 { |
||||
return ErrInvalidDifficulty |
||||
} |
||||
// Recompute the digest and PoW value and verify against the header
|
||||
cache := ethash.cache(number) |
||||
|
||||
size := datasetSize(number) |
||||
if ethash.tester { |
||||
size = 32 * 1024 |
||||
} |
||||
digest, result := hashimotoLight(size, cache, header.HashNoNonce().Bytes(), header.Nonce.Uint64()) |
||||
if !bytes.Equal(header.MixDigest[:], digest) { |
||||
return ErrInvalidMixDigest |
||||
} |
||||
target := new(big.Int).Div(maxUint256, header.Difficulty) |
||||
if new(big.Int).SetBytes(result).Cmp(target) > 0 { |
||||
return ErrInvalidPoW |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// Prepare implements consensus.Engine, initializing the difficulty field of a
|
||||
// header to conform to the ethash protocol. The changes are done inline.
|
||||
func (ethash *Ethash) Prepare(chain consensus.ChainReader, header *types.Header) error { |
||||
parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) |
||||
if parent == nil { |
||||
return ErrParentUnknown |
||||
} |
||||
header.Difficulty = CalcDifficulty(chain.Config(), header.Time.Uint64(), |
||||
parent.Time.Uint64(), parent.Number, parent.Difficulty) |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// Finalize implements consensus.Engine, accumulating the block and uncle rewards,
|
||||
// setting the final state and assembling the block.
|
||||
func (ethash *Ethash) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { |
||||
// Accumulate any block and uncle rewards and commit the final state root
|
||||
AccumulateRewards(state, header, uncles) |
||||
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) |
||||
|
||||
// Header seems complete, assemble into a block and return
|
||||
return types.NewBlock(header, txs, uncles, receipts), nil |
||||
} |
||||
|
||||
// Some weird constants to avoid constant memory allocs for them.
|
||||
var ( |
||||
big8 = big.NewInt(8) |
||||
big32 = big.NewInt(32) |
||||
) |
||||
|
||||
// AccumulateRewards credits the coinbase of the given block with the mining
|
||||
// reward. The total reward consists of the static block reward and rewards for
|
||||
// included uncles. The coinbase of each uncle block is also rewarded.
|
||||
//
|
||||
// TODO (karalabe): Move the chain maker into this package and make this private!
|
||||
func AccumulateRewards(state *state.StateDB, header *types.Header, uncles []*types.Header) { |
||||
reward := new(big.Int).Set(blockReward) |
||||
r := new(big.Int) |
||||
for _, uncle := range uncles { |
||||
r.Add(uncle.Number, big8) |
||||
r.Sub(r, header.Number) |
||||
r.Mul(r, blockReward) |
||||
r.Div(r, big8) |
||||
state.AddBalance(uncle.Coinbase, r) |
||||
|
||||
r.Div(blockReward, big32) |
||||
reward.Add(reward, r) |
||||
} |
||||
state.AddBalance(header.Coinbase, reward) |
||||
} |
@ -0,0 +1,79 @@ |
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethash |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"math/big" |
||||
"os" |
||||
"testing" |
||||
|
||||
"github.com/ethereum/go-ethereum/common/math" |
||||
"github.com/ethereum/go-ethereum/params" |
||||
) |
||||
|
||||
type diffTest struct { |
||||
ParentTimestamp uint64 |
||||
ParentDifficulty *big.Int |
||||
CurrentTimestamp uint64 |
||||
CurrentBlocknumber *big.Int |
||||
CurrentDifficulty *big.Int |
||||
} |
||||
|
||||
func (d *diffTest) UnmarshalJSON(b []byte) (err error) { |
||||
var ext struct { |
||||
ParentTimestamp string |
||||
ParentDifficulty string |
||||
CurrentTimestamp string |
||||
CurrentBlocknumber string |
||||
CurrentDifficulty string |
||||
} |
||||
if err := json.Unmarshal(b, &ext); err != nil { |
||||
return err |
||||
} |
||||
|
||||
d.ParentTimestamp = math.MustParseUint64(ext.ParentTimestamp) |
||||
d.ParentDifficulty = math.MustParseBig256(ext.ParentDifficulty) |
||||
d.CurrentTimestamp = math.MustParseUint64(ext.CurrentTimestamp) |
||||
d.CurrentBlocknumber = math.MustParseBig256(ext.CurrentBlocknumber) |
||||
d.CurrentDifficulty = math.MustParseBig256(ext.CurrentDifficulty) |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func TestCalcDifficulty(t *testing.T) { |
||||
file, err := os.Open("../../tests/files/BasicTests/difficulty.json") |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
defer file.Close() |
||||
|
||||
tests := make(map[string]diffTest) |
||||
err = json.NewDecoder(file).Decode(&tests) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
config := ¶ms.ChainConfig{HomesteadBlock: big.NewInt(1150000)} |
||||
for name, test := range tests { |
||||
number := new(big.Int).Sub(test.CurrentBlocknumber, big.NewInt(1)) |
||||
diff := CalcDifficulty(config, test.CurrentTimestamp, test.ParentTimestamp, number, test.ParentDifficulty) |
||||
if diff.Cmp(test.CurrentDifficulty) != 0 { |
||||
t.Error(name, "failed. Expected", test.CurrentDifficulty, "and calculated", diff) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,40 @@ |
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethash |
||||
|
||||
import ( |
||||
"math/big" |
||||
"testing" |
||||
|
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
) |
||||
|
||||
// Tests that ethash works correctly in test mode.
|
||||
func TestTestMode(t *testing.T) { |
||||
head := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(100)} |
||||
|
||||
ethash := NewTester() |
||||
block, err := ethash.Seal(nil, types.NewBlockWithHeader(head), nil) |
||||
if err != nil { |
||||
t.Fatalf("failed to seal block: %v", err) |
||||
} |
||||
head.Nonce = types.EncodeNonce(block.Nonce()) |
||||
head.MixDigest = block.MixDigest() |
||||
if err := ethash.VerifySeal(nil, head); err != nil { |
||||
t.Fatalf("unexpected verification error: %v", err) |
||||
} |
||||
} |
@ -0,0 +1,146 @@ |
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ethash |
||||
|
||||
import ( |
||||
crand "crypto/rand" |
||||
"math" |
||||
"math/big" |
||||
"math/rand" |
||||
"runtime" |
||||
"sync" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/consensus" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/log" |
||||
) |
||||
|
||||
// Seal implements consensus.Engine, attempting to find a nonce that satisfies
|
||||
// the block's difficulty requirements.
|
||||
func (ethash *Ethash) Seal(chain consensus.ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) { |
||||
// If we're running a fake PoW, simply return a 0 nonce immediately
|
||||
if ethash.fakeMode { |
||||
header := block.Header() |
||||
header.Nonce, header.MixDigest = types.BlockNonce{}, common.Hash{} |
||||
return block.WithSeal(header), nil |
||||
} |
||||
// If we're running a shared PoW, delegate sealing to it
|
||||
if ethash.shared != nil { |
||||
return ethash.shared.Seal(chain, block, stop) |
||||
} |
||||
// Create a runner and the multiple search threads it directs
|
||||
abort := make(chan struct{}) |
||||
found := make(chan *types.Block) |
||||
|
||||
ethash.lock.Lock() |
||||
threads := ethash.threads |
||||
if ethash.rand == nil { |
||||
seed, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64)) |
||||
if err != nil { |
||||
ethash.lock.Unlock() |
||||
return nil, err |
||||
} |
||||
ethash.rand = rand.New(rand.NewSource(seed.Int64())) |
||||
} |
||||
ethash.lock.Unlock() |
||||
if threads == 0 { |
||||
threads = runtime.NumCPU() |
||||
} |
||||
var pend sync.WaitGroup |
||||
for i := 0; i < threads; i++ { |
||||
pend.Add(1) |
||||
go func(id int, nonce uint64) { |
||||
defer pend.Done() |
||||
ethash.mine(block, id, nonce, abort, found) |
||||
}(i, uint64(ethash.rand.Int63())) |
||||
} |
||||
// Wait until sealing is terminated or a nonce is found
|
||||
var result *types.Block |
||||
select { |
||||
case <-stop: |
||||
// Outside abort, stop all miner threads
|
||||
close(abort) |
||||
case result = <-found: |
||||
// One of the threads found a block, abort all others
|
||||
close(abort) |
||||
case <-ethash.update: |
||||
// Thread count was changed on user request, restart
|
||||
close(abort) |
||||
pend.Wait() |
||||
return ethash.Seal(chain, block, stop) |
||||
} |
||||
// Wait for all miners to terminate and return the block
|
||||
pend.Wait() |
||||
return result, nil |
||||
} |
||||
|
||||
// mine is the actual proof-of-work miner that searches for a nonce starting from
|
||||
// seed that results in correct final block difficulty.
|
||||
func (ethash *Ethash) mine(block *types.Block, id int, seed uint64, abort chan struct{}, found chan *types.Block) { |
||||
// Extract some data from the header
|
||||
var ( |
||||
header = block.Header() |
||||
hash = header.HashNoNonce().Bytes() |
||||
target = new(big.Int).Div(maxUint256, header.Difficulty) |
||||
|
||||
number = header.Number.Uint64() |
||||
dataset = ethash.dataset(number) |
||||
) |
||||
// Start generating random nonces until we abort or find a good one
|
||||
var ( |
||||
attempts = int64(0) |
||||
nonce = seed |
||||
) |
||||
logger := log.New("miner", id) |
||||
logger.Trace("Started ethash search for new nonces", "seed", seed) |
||||
for { |
||||
select { |
||||
case <-abort: |
||||
// Mining terminated, update stats and abort
|
||||
logger.Trace("Ethash nonce search aborted", "attempts", nonce-seed) |
||||
ethash.hashrate.Mark(attempts) |
||||
return |
||||
|
||||
default: |
||||
// We don't have to update hash rate on every nonce, so update after after 2^X nonces
|
||||
attempts++ |
||||
if (attempts % (1 << 15)) == 0 { |
||||
ethash.hashrate.Mark(attempts) |
||||
attempts = 0 |
||||
} |
||||
// Compute the PoW value of this nonce
|
||||
digest, result := hashimotoFull(dataset, hash, nonce) |
||||
if new(big.Int).SetBytes(result).Cmp(target) <= 0 { |
||||
// Correct nonce found, create a new header with it
|
||||
header = types.CopyHeader(header) |
||||
header.Nonce = types.EncodeNonce(nonce) |
||||
header.MixDigest = common.BytesToHash(digest) |
||||
|
||||
// Seal and return a block (if still needed)
|
||||
select { |
||||
case found <- block.WithSeal(header): |
||||
logger.Trace("Ethash nonce found and reported", "attempts", nonce-seed, "nonce", nonce) |
||||
case <-abort: |
||||
logger.Trace("Ethash nonce found but discarded", "attempts", nonce-seed, "nonce", nonce) |
||||
} |
||||
return |
||||
} |
||||
nonce++ |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,85 @@ |
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package misc |
||||
|
||||
import ( |
||||
"bytes" |
||||
"errors" |
||||
"math/big" |
||||
|
||||
"github.com/ethereum/go-ethereum/core/state" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/params" |
||||
) |
||||
|
||||
var ( |
||||
// ErrBadProDAOExtra is returned if a header doens't support the DAO fork on a
|
||||
// pro-fork client.
|
||||
ErrBadProDAOExtra = errors.New("bad DAO pro-fork extra-data") |
||||
|
||||
// ErrBadNoDAOExtra is returned if a header does support the DAO fork on a no-
|
||||
// fork client.
|
||||
ErrBadNoDAOExtra = errors.New("bad DAO no-fork extra-data") |
||||
) |
||||
|
||||
// VerifyDAOHeaderExtraData validates the extra-data field of a block header to
|
||||
// ensure it conforms to DAO hard-fork rules.
|
||||
//
|
||||
// DAO hard-fork extension to the header validity:
|
||||
// a) if the node is no-fork, do not accept blocks in the [fork, fork+10) range
|
||||
// with the fork specific extra-data set
|
||||
// b) if the node is pro-fork, require blocks in the specific range to have the
|
||||
// unique extra-data set.
|
||||
func VerifyDAOHeaderExtraData(config *params.ChainConfig, header *types.Header) error { |
||||
// Short circuit validation if the node doesn't care about the DAO fork
|
||||
if config.DAOForkBlock == nil { |
||||
return nil |
||||
} |
||||
// Make sure the block is within the fork's modified extra-data range
|
||||
limit := new(big.Int).Add(config.DAOForkBlock, params.DAOForkExtraRange) |
||||
if header.Number.Cmp(config.DAOForkBlock) < 0 || header.Number.Cmp(limit) >= 0 { |
||||
return nil |
||||
} |
||||
// Depending whether we support or oppose the fork, validate the extra-data contents
|
||||
if config.DAOForkSupport { |
||||
if !bytes.Equal(header.Extra, params.DAOForkBlockExtra) { |
||||
return ErrBadProDAOExtra |
||||
} |
||||
} else { |
||||
if bytes.Equal(header.Extra, params.DAOForkBlockExtra) { |
||||
return ErrBadNoDAOExtra |
||||
} |
||||
} |
||||
// All ok, header has the same extra-data we expect
|
||||
return nil |
||||
} |
||||
|
||||
// ApplyDAOHardFork modifies the state database according to the DAO hard-fork
|
||||
// rules, transferring all balances of a set of DAO accounts to a single refund
|
||||
// contract.
|
||||
func ApplyDAOHardFork(statedb *state.StateDB) { |
||||
// Retrieve the contract to refund balances into
|
||||
if !statedb.Exist(params.DAORefundContract) { |
||||
statedb.CreateAccount(params.DAORefundContract) |
||||
} |
||||
|
||||
// Move every DAO account and extra-balance account funds into the refund contract
|
||||
for _, addr := range params.DAODrainList() { |
||||
statedb.AddBalance(params.DAORefundContract, statedb.GetBalance(addr)) |
||||
statedb.SetBalance(addr, new(big.Int)) |
||||
} |
||||
} |
@ -0,0 +1,43 @@ |
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package misc |
||||
|
||||
import ( |
||||
"fmt" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/params" |
||||
) |
||||
|
||||
// VerifyForkHashes verifies that blocks conforming to network hard-forks do have
|
||||
// the correct hashes, to avoid clients going off on different chains. This is an
|
||||
// optional feature.
|
||||
func VerifyForkHashes(config *params.ChainConfig, header *types.Header, uncle bool) error { |
||||
// We don't care about uncles
|
||||
if uncle { |
||||
return nil |
||||
} |
||||
// If the homestead reprice hash is set, validate it
|
||||
if config.EIP150Block != nil && config.EIP150Block.Cmp(header.Number) == 0 { |
||||
if config.EIP150Hash != (common.Hash{}) && config.EIP150Hash != header.Hash() { |
||||
return fmt.Errorf("homestead gas reprice fork: have 0x%x, want 0x%x", header.Hash(), config.EIP150Hash) |
||||
} |
||||
} |
||||
// All ok, return
|
||||
return nil |
||||
} |
@ -1,87 +0,0 @@ |
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package core |
||||
|
||||
import ( |
||||
"runtime" |
||||
|
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/pow" |
||||
) |
||||
|
||||
// nonceCheckResult contains the result of a nonce verification.
|
||||
type nonceCheckResult struct { |
||||
index int // Index of the item verified from an input array
|
||||
valid bool // Result of the nonce verification
|
||||
} |
||||
|
||||
// verifyNoncesFromHeaders starts a concurrent header nonce verification,
|
||||
// returning a quit channel to abort the operations and a results channel
|
||||
// to retrieve the async verifications.
|
||||
func verifyNoncesFromHeaders(checker pow.PoW, headers []*types.Header) (chan<- struct{}, <-chan nonceCheckResult) { |
||||
items := make([]pow.Block, len(headers)) |
||||
for i, header := range headers { |
||||
items[i] = types.NewBlockWithHeader(header) |
||||
} |
||||
return verifyNonces(checker, items) |
||||
} |
||||
|
||||
// verifyNoncesFromBlocks starts a concurrent block nonce verification,
|
||||
// returning a quit channel to abort the operations and a results channel
|
||||
// to retrieve the async verifications.
|
||||
func verifyNoncesFromBlocks(checker pow.PoW, blocks []*types.Block) (chan<- struct{}, <-chan nonceCheckResult) { |
||||
items := make([]pow.Block, len(blocks)) |
||||
for i, block := range blocks { |
||||
items[i] = block |
||||
} |
||||
return verifyNonces(checker, items) |
||||
} |
||||
|
||||
// verifyNonces starts a concurrent nonce verification, returning a quit channel
|
||||
// to abort the operations and a results channel to retrieve the async checks.
|
||||
func verifyNonces(checker pow.PoW, items []pow.Block) (chan<- struct{}, <-chan nonceCheckResult) { |
||||
// Spawn as many workers as allowed threads
|
||||
workers := runtime.GOMAXPROCS(0) |
||||
if len(items) < workers { |
||||
workers = len(items) |
||||
} |
||||
// Create a task channel and spawn the verifiers
|
||||
tasks := make(chan int, workers) |
||||
results := make(chan nonceCheckResult, len(items)) // Buffered to make sure all workers stop
|
||||
for i := 0; i < workers; i++ { |
||||
go func() { |
||||
for index := range tasks { |
||||
results <- nonceCheckResult{index: index, valid: checker.Verify(items[index]) == nil} |
||||
} |
||||
}() |
||||
} |
||||
// Feed item indices to the workers until done or aborted
|
||||
abort := make(chan struct{}) |
||||
go func() { |
||||
defer close(tasks) |
||||
|
||||
for i := range items { |
||||
select { |
||||
case tasks <- i: |
||||
continue |
||||
case <-abort: |
||||
return |
||||
} |
||||
} |
||||
}() |
||||
return abort, results |
||||
} |
@ -1,238 +0,0 @@ |
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package core |
||||
|
||||
import ( |
||||
"errors" |
||||
"math/big" |
||||
"runtime" |
||||
"testing" |
||||
"time" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/ethdb" |
||||
"github.com/ethereum/go-ethereum/params" |
||||
"github.com/ethereum/go-ethereum/pow" |
||||
) |
||||
|
||||
// failPow is a non-validating proof of work implementation, that returns true
|
||||
// from Verify for all but one block.
|
||||
type failPow struct { |
||||
failing uint64 |
||||
} |
||||
|
||||
func (pow failPow) Search(pow.Block, <-chan struct{}) (uint64, []byte) { |
||||
return 0, nil |
||||
} |
||||
func (pow failPow) Verify(block pow.Block) error { |
||||
if block.NumberU64() == pow.failing { |
||||
return errors.New("failed") |
||||
} |
||||
return nil |
||||
} |
||||
func (pow failPow) Hashrate() float64 { return 0 } |
||||
|
||||
// delayedPow is a non-validating proof of work implementation, that returns true
|
||||
// from Verify for all blocks, but delays them the configured amount of time.
|
||||
type delayedPow struct { |
||||
delay time.Duration |
||||
} |
||||
|
||||
func (pow delayedPow) Search(pow.Block, <-chan struct{}) (uint64, []byte) { |
||||
return 0, nil |
||||
} |
||||
func (pow delayedPow) Verify(block pow.Block) error { time.Sleep(pow.delay); return nil } |
||||
func (pow delayedPow) Hashrate() float64 { return 0 } |
||||
|
||||
// Tests that simple POW verification works, for both good and bad blocks.
|
||||
func TestPowVerification(t *testing.T) { |
||||
// Create a simple chain to verify
|
||||
var ( |
||||
testdb, _ = ethdb.NewMemDatabase() |
||||
genesis = GenesisBlockForTesting(testdb, common.Address{}, new(big.Int)) |
||||
blocks, _ = GenerateChain(params.TestChainConfig, genesis, testdb, 8, nil) |
||||
) |
||||
headers := make([]*types.Header, len(blocks)) |
||||
for i, block := range blocks { |
||||
headers[i] = block.Header() |
||||
} |
||||
// Run the POW checker for blocks one-by-one, checking for both valid and invalid nonces
|
||||
for i := 0; i < len(blocks); i++ { |
||||
for j, full := range []bool{true, false} { |
||||
for k, valid := range []bool{true, false} { |
||||
var results <-chan nonceCheckResult |
||||
|
||||
switch { |
||||
case full && valid: |
||||
_, results = verifyNoncesFromBlocks(pow.FakePow{}, []*types.Block{blocks[i]}) |
||||
case full && !valid: |
||||
_, results = verifyNoncesFromBlocks(failPow{blocks[i].NumberU64()}, []*types.Block{blocks[i]}) |
||||
case !full && valid: |
||||
_, results = verifyNoncesFromHeaders(pow.FakePow{}, []*types.Header{headers[i]}) |
||||
case !full && !valid: |
||||
_, results = verifyNoncesFromHeaders(failPow{headers[i].Number.Uint64()}, []*types.Header{headers[i]}) |
||||
} |
||||
// Wait for the verification result
|
||||
select { |
||||
case result := <-results: |
||||
if result.index != 0 { |
||||
t.Errorf("test %d.%d.%d: invalid index: have %d, want 0", i, j, k, result.index) |
||||
} |
||||
if result.valid != valid { |
||||
t.Errorf("test %d.%d.%d: validity mismatch: have %v, want %v", i, j, k, result.valid, valid) |
||||
} |
||||
case <-time.After(time.Second): |
||||
t.Fatalf("test %d.%d.%d: verification timeout", i, j, k) |
||||
} |
||||
// Make sure no more data is returned
|
||||
select { |
||||
case result := <-results: |
||||
t.Fatalf("test %d.%d.%d: unexpected result returned: %v", i, j, k, result) |
||||
case <-time.After(25 * time.Millisecond): |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Tests that concurrent POW verification works, for both good and bad blocks.
|
||||
func TestPowConcurrentVerification2(t *testing.T) { testPowConcurrentVerification(t, 2) } |
||||
func TestPowConcurrentVerification8(t *testing.T) { testPowConcurrentVerification(t, 8) } |
||||
func TestPowConcurrentVerification32(t *testing.T) { testPowConcurrentVerification(t, 32) } |
||||
|
||||
func testPowConcurrentVerification(t *testing.T, threads int) { |
||||
// Create a simple chain to verify
|
||||
var ( |
||||
testdb, _ = ethdb.NewMemDatabase() |
||||
genesis = GenesisBlockForTesting(testdb, common.Address{}, new(big.Int)) |
||||
blocks, _ = GenerateChain(params.TestChainConfig, genesis, testdb, 8, nil) |
||||
) |
||||
headers := make([]*types.Header, len(blocks)) |
||||
for i, block := range blocks { |
||||
headers[i] = block.Header() |
||||
} |
||||
// Set the number of threads to verify on
|
||||
old := runtime.GOMAXPROCS(threads) |
||||
defer runtime.GOMAXPROCS(old) |
||||
|
||||
// Run the POW checker for the entire block chain at once both for a valid and
|
||||
// also an invalid chain (enough if one is invalid, last but one (arbitrary)).
|
||||
for i, full := range []bool{true, false} { |
||||
for j, valid := range []bool{true, false} { |
||||
var results <-chan nonceCheckResult |
||||
|
||||
switch { |
||||
case full && valid: |
||||
_, results = verifyNoncesFromBlocks(pow.FakePow{}, blocks) |
||||
case full && !valid: |
||||
_, results = verifyNoncesFromBlocks(failPow{uint64(len(blocks) - 1)}, blocks) |
||||
case !full && valid: |
||||
_, results = verifyNoncesFromHeaders(pow.FakePow{}, headers) |
||||
case !full && !valid: |
||||
_, results = verifyNoncesFromHeaders(failPow{uint64(len(headers) - 1)}, headers) |
||||
} |
||||
// Wait for all the verification results
|
||||
checks := make(map[int]bool) |
||||
for k := 0; k < len(blocks); k++ { |
||||
select { |
||||
case result := <-results: |
||||
if _, ok := checks[result.index]; ok { |
||||
t.Fatalf("test %d.%d.%d: duplicate results for %d", i, j, k, result.index) |
||||
} |
||||
if result.index < 0 || result.index >= len(blocks) { |
||||
t.Fatalf("test %d.%d.%d: result %d out of bounds [%d, %d]", i, j, k, result.index, 0, len(blocks)-1) |
||||
} |
||||
checks[result.index] = result.valid |
||||
|
||||
case <-time.After(time.Second): |
||||
t.Fatalf("test %d.%d.%d: verification timeout", i, j, k) |
||||
} |
||||
} |
||||
// Check nonce check validity
|
||||
for k := 0; k < len(blocks); k++ { |
||||
want := valid || (k != len(blocks)-2) // We chose the last but one nonce in the chain to fail
|
||||
if checks[k] != want { |
||||
t.Errorf("test %d.%d.%d: validity mismatch: have %v, want %v", i, j, k, checks[k], want) |
||||
} |
||||
} |
||||
// Make sure no more data is returned
|
||||
select { |
||||
case result := <-results: |
||||
t.Fatalf("test %d.%d: unexpected result returned: %v", i, j, result) |
||||
case <-time.After(25 * time.Millisecond): |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Tests that aborting a POW validation indeed prevents further checks from being
|
||||
// run, as well as checks that no left-over goroutines are leaked.
|
||||
func TestPowConcurrentAbortion2(t *testing.T) { testPowConcurrentAbortion(t, 2) } |
||||
func TestPowConcurrentAbortion8(t *testing.T) { testPowConcurrentAbortion(t, 8) } |
||||
func TestPowConcurrentAbortion32(t *testing.T) { testPowConcurrentAbortion(t, 32) } |
||||
|
||||
func testPowConcurrentAbortion(t *testing.T, threads int) { |
||||
// Create a simple chain to verify
|
||||
var ( |
||||
testdb, _ = ethdb.NewMemDatabase() |
||||
genesis = GenesisBlockForTesting(testdb, common.Address{}, new(big.Int)) |
||||
blocks, _ = GenerateChain(params.TestChainConfig, genesis, testdb, 1024, nil) |
||||
) |
||||
headers := make([]*types.Header, len(blocks)) |
||||
for i, block := range blocks { |
||||
headers[i] = block.Header() |
||||
} |
||||
// Set the number of threads to verify on
|
||||
old := runtime.GOMAXPROCS(threads) |
||||
defer runtime.GOMAXPROCS(old) |
||||
|
||||
// Run the POW checker for the entire block chain at once
|
||||
for i, full := range []bool{true, false} { |
||||
var abort chan<- struct{} |
||||
var results <-chan nonceCheckResult |
||||
|
||||
// Start the verifications and immediately abort
|
||||
if full { |
||||
abort, results = verifyNoncesFromBlocks(delayedPow{time.Millisecond}, blocks) |
||||
} else { |
||||
abort, results = verifyNoncesFromHeaders(delayedPow{time.Millisecond}, headers) |
||||
} |
||||
close(abort) |
||||
|
||||
// Deplete the results channel
|
||||
verified := make(map[int]struct{}) |
||||
for depleted := false; !depleted; { |
||||
select { |
||||
case result := <-results: |
||||
verified[result.index] = struct{}{} |
||||
case <-time.After(50 * time.Millisecond): |
||||
depleted = true |
||||
} |
||||
} |
||||
// Check that abortion was honored by not processing too many POWs
|
||||
if len(verified) > 2*threads { |
||||
t.Errorf("test %d: verification count too large: have %d, want below %d", i, len(verified), 2*threads) |
||||
} |
||||
// Check that there are no gaps in the results
|
||||
for j := 0; j < len(verified); j++ { |
||||
if _, ok := verified[j]; !ok { |
||||
t.Errorf("test %d.%d: gap found in verification results", i, j) |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,58 +0,0 @@ |
||||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package pow |
||||
|
||||
import ( |
||||
"math/big" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
) |
||||
|
||||
type Block interface { |
||||
Difficulty() *big.Int |
||||
HashNoNonce() common.Hash |
||||
Nonce() uint64 |
||||
MixDigest() common.Hash |
||||
NumberU64() uint64 |
||||
} |
||||
|
||||
type ChainManager interface { |
||||
GetBlockByNumber(uint64) *types.Block |
||||
CurrentBlock() *types.Block |
||||
} |
||||
|
||||
type PoW interface { |
||||
Verify(block Block) error |
||||
Search(block Block, stop <-chan struct{}) (uint64, []byte) |
||||
Hashrate() float64 |
||||
} |
||||
|
||||
// FakePow is a non-validating proof of work implementation.
|
||||
// It returns true from Verify for any block.
|
||||
type FakePow struct{} |
||||
|
||||
// Verify implements PoW, returning a success for an input.
|
||||
func (pow FakePow) Verify(block Block) error { return nil } |
||||
|
||||
// Search implements PoW, returning the nonce 0 for any call.
|
||||
func (pow FakePow) Search(block Block, stop <-chan struct{}) (uint64, []byte) { |
||||
return 0, nil |
||||
} |
||||
|
||||
// Hashrate implements PoW, returning 0.
|
||||
func (pow FakePow) Hashrate() float64 { return 0 } |
Loading…
Reference in new issue