package dummy
import (
"errors"
"fmt"
"golang.org/x/crypto/sha3"
"math/big"
"runtime"
"time"
"github.com/ava-labs/coreth/consensus"
"github.com/ava-labs/coreth/core/state"
"github.com/ava-labs/coreth/core/types"
"github.com/ava-labs/coreth/params"
"github.com/ava-labs/coreth/rpc"
"github.com/ava-labs/go-ethereum/common"
"github.com/ava-labs/go-ethereum/rlp"
mapset "github.com/deckarep/golang-set"
)
type OnFinalizeCallbackType = func(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header)
type OnFinalizeAndAssembleCallbackType = func(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt)
type OnAPIsCallbackType = func(consensus.ChainReader) []rpc.API
type OnExtraStateChangeType = func(block *types.Block, statedb *state.StateDB) error
type ConsensusCallbacks struct {
OnSeal func(*types.Block) error
OnSealHash func(*types.Header)
OnAPIs OnAPIsCallbackType
OnFinalize OnFinalizeCallbackType
OnFinalizeAndAssemble OnFinalizeAndAssembleCallbackType
OnExtraStateChange OnExtraStateChangeType
}
type DummyEngine struct {
cb *ConsensusCallbacks
}
func NewDummyEngine(cb *ConsensusCallbacks) *DummyEngine {
return &DummyEngine{cb: cb}
}
var (
allowedFutureBlockTime = 15 * time.Second // Max time from current time allowed for blocks, before they're considered future blocks
)
var (
maxUncles = 2 // Maximum number of uncles allowed in a single block
errTooManyUncles = errors.New("too many uncles")
errZeroBlockTime = errors.New("timestamp equals parent's")
errDuplicateUncle = errors.New("duplicate uncle")
errUncleIsAncestor = errors.New("uncle is ancestor")
errDanglingUncle = errors.New("uncle's parent is not ancestor")
)
// modified from consensus.go
func (self *DummyEngine) 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 > uint64(time.Now().Add(allowedFutureBlockTime).Unix()) {
return consensus.ErrFutureBlock
}
}
//if header.Time <= parent.Time {
if header.Time < parent.Time {
return errZeroBlockTime
}
// Verify that the gas limit is <= 2^63-1
cap := uint64(0x7fffffffffffffff)
if header.GasLimit > cap {
return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, cap)
}
// Verify that the gasUsed is <= gasLimit
if header.GasUsed > header.GasLimit {
return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit)
}
// Verify that the gas limit remains within allowed bounds
diff := int64(parent.GasLimit) - int64(header.GasLimit)
if diff < 0 {
diff *= -1
}
limit := parent.GasLimit / params.GasLimitBoundDivisor
if uint64(diff) >= limit || header.GasLimit < params.MinGasLimit {
return fmt.Errorf("invalid gas limit: have %d, want %d += %d", 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 consensus.ErrInvalidNumber
}
// Verify the engine specific seal securing the block
if seal {
if err := self.VerifySeal(chain, header); err != nil {
return err
}
}
return nil
}
func (self *DummyEngine) verifyHeaderWorker(chain consensus.ChainReader, headers []*types.Header, seals []bool, index int) error {
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]
}
if parent == nil {
return consensus.ErrUnknownAncestor
}
if chain.GetHeader(headers[index].Hash(), headers[index].Number.Uint64()) != nil {
return nil // known block
}
return self.verifyHeader(chain, headers[index], parent, false, seals[index])
}
func (self *DummyEngine) Author(header *types.Header) (common.Address, error) {
return header.Coinbase, nil
}
func (self *DummyEngine) VerifyHeader(chain consensus.ChainReader, header *types.Header, seal bool) error {
// 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 consensus.ErrUnknownAncestor
}
// Sanity checks passed, do a proper verification
return self.verifyHeader(chain, header, parent, false, seal)
}
func (self *DummyEngine) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) {
// 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
var (
inputs = make(chan int)
done = make(chan int, workers)
errors = make([]error, len(headers))
abort = make(chan struct{})
)
for i := 0; i < workers; i++ {
go func() {
for index := range inputs {
errors[index] = self.verifyHeaderWorker(chain, headers, seals, index)
done <- index
}
}()
}
errorsOut := make(chan error, len(headers))
go func() {
defer close(inputs)
var (
in, out = 0, 0
checked = make([]bool, len(headers))
inputs = inputs
)
for {
select {
case inputs <- in:
if in++; in == len(headers) {
// Reached end of headers. Stop sending to workers.
inputs = nil
}
case index := <-done:
for checked[index] = true; checked[out]; out++ {
errorsOut <- errors[out]
if out == len(headers)-1 {
return
}
}
case <-abort:
return
}
}
}()
return abort, errorsOut
}
func (self *DummyEngine) VerifyUncles(chain consensus.ChainReader, block *types.Block) error {
// Verify that there are at most 2 uncles included in this block
if len(block.Uncles()) > maxUncles {
return errTooManyUncles
}
if len(block.Uncles()) == 0 {
return nil
}
// Gather the set of past uncles and ancestors
uncles, ancestors := mapset.NewSet(), 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.Contains(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 := self.verifyHeader(chain, uncle, ancestors[uncle.ParentHash], true, true); err != nil {
return err
}
}
return nil
}
func (self *DummyEngine) VerifySeal(chain consensus.ChainReader, header *types.Header) error {
return nil
}
func (self *DummyEngine) Prepare(chain consensus.ChainReader, header *types.Header) error {
header.Difficulty = big.NewInt(1)
return nil
}
func (self *DummyEngine) Finalize(
chain consensus.ChainReader, header *types.Header,
state *state.StateDB, txs []*types.Transaction,
uncles []*types.Header) {
if self.cb.OnFinalize != nil {
self.cb.OnFinalize(chain, header, state, txs, uncles)
}
// commit the final state root
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
}
func (self *DummyEngine) FinalizeAndAssemble(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction,
uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
if self.cb.OnFinalizeAndAssemble != nil {
self.cb.OnFinalizeAndAssemble(chain, header, state, txs, uncles, receipts)
}
// commit the final state root
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
}
func (self *DummyEngine) Seal(chain consensus.ChainReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) (err error) {
if self.cb.OnSeal != nil {
err = self.cb.OnSeal(block)
} else {
err = nil
}
if err == nil {
results <- block
}
return
}
func (self *DummyEngine) SealHash(header *types.Header) (hash common.Hash) {
if self.cb.OnSealHash != nil {
self.cb.OnSealHash(header)
}
hasher := sha3.NewLegacyKeccak256()
rlp.Encode(hasher, []interface{}{
header.ParentHash,
header.UncleHash,
header.Coinbase,
header.Root,
header.TxHash,
header.ReceiptHash,
header.Bloom,
header.Difficulty,
header.Number,
header.GasLimit,
header.GasUsed,
header.Time,
header.Extra,
})
hasher.Sum(hash[:0])
return hash
}
func (self *DummyEngine) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int {
return big.NewInt(1)
}
func (self *DummyEngine) APIs(chain consensus.ChainReader) (res []rpc.API) {
res = nil
if self.cb.OnAPIs != nil {
res = self.cb.OnAPIs(chain)
}
return
}
func (self *DummyEngine) Close() error {
return nil
}
func (self *DummyEngine) ExtraStateChange(block *types.Block, statedb *state.StateDB) error {
if self.cb.OnExtraStateChange != nil {
return self.cb.OnExtraStateChange(block, statedb)
}
return nil
}