mirror of
https://gitlab.com/pulsechaincom/erigon-pulse.git
synced 2024-12-25 13:07:17 +00:00
605d7a8d59
* handle chain flag in integration * handle chain flag in integration * handle chain flag in integration * save * save * merge devel * save * noopWriter - one is enough * chain spec parser * chain spec parser * embed * embed * embed * embed * embed * embed * embed * clean * clean * correct alloc after reset state * correct alloc after reset state * correct alloc after reset state * integration reset state now does re-apply genesis and chainConfig * eips summary * eips summary * eips summary
466 lines
15 KiB
Go
466 lines
15 KiB
Go
package aura
|
|
|
|
import (
|
|
"container/list"
|
|
"fmt"
|
|
"sort"
|
|
"sync"
|
|
|
|
lru "github.com/hashicorp/golang-lru"
|
|
"github.com/ledgerwatch/erigon/common"
|
|
"github.com/ledgerwatch/erigon/consensus/aura/aurainterfaces"
|
|
"github.com/ledgerwatch/erigon/core/types"
|
|
"github.com/ledgerwatch/erigon/log"
|
|
"go.uber.org/atomic"
|
|
)
|
|
|
|
/// Kind of SystemOrCodeCall, this is either an on-chain address, or code.
|
|
type SystemOrCodeCallKind uint8
|
|
|
|
const (
|
|
SystemCallOnChain SystemOrCodeCallKind = 0
|
|
CallHardCodedCode SystemOrCodeCallKind = 1
|
|
)
|
|
|
|
//nolint
|
|
type CallResults struct {
|
|
data []byte
|
|
proof [][]byte
|
|
execError string
|
|
}
|
|
|
|
// Type alias for a function we can make calls through synchronously.
|
|
// Returns the call result and state proof for each call.
|
|
type Call func(common.Address, []byte) (CallResults, error)
|
|
|
|
// A system-calling closure. Enacts calls on a block's state from the system address.
|
|
type SystemCall func(common.Address, []byte) (CallResults, error)
|
|
|
|
type client interface {
|
|
CallAtBlockHash(common.Hash, common.Address, []byte) (CallResults, error)
|
|
CallAtLatestBlock(common.Address, []byte) (CallResults, error)
|
|
SystemCallAtBlockHash(blockHash common.Hash, contract common.Address, data []byte) (CallResults, error)
|
|
}
|
|
|
|
type ValidatorSet interface {
|
|
|
|
// Get the default "Call" helper, for use in general operation.
|
|
// TODO [keorn]: this is a hack intended to migrate off of
|
|
// a strict dependency on state always being available.
|
|
defaultCaller(blockHash common.Hash) (Call, error)
|
|
|
|
// Called for each new block this node is creating. If this block is
|
|
// the first block of an epoch, this is called *after* `on_epoch_begin()`,
|
|
// but with the same parameters.
|
|
//
|
|
// Returns a list of contract calls to be pushed onto the new block.
|
|
//func generateEngineTransactions(_first bool, _header *types.Header, _call SystemCall) -> Result<Vec<(Address, Bytes)>, EthcoreError>
|
|
|
|
// Called on the close of every block.
|
|
onCloseBlock(_header *types.Header, _address common.Address) error
|
|
|
|
// Draws an validator nonce modulo number of validators.
|
|
getWithCaller(parentHash common.Hash, nonce uint, caller Call) (common.Address, error)
|
|
/*
|
|
// Returns the current number of validators.
|
|
fn count(&self, parent: &H256) -> usize {
|
|
let default = self.default_caller(BlockId::Hash(*parent));
|
|
self.count_with_caller(parent, &*default)
|
|
}
|
|
|
|
// Signalling that a new epoch has begun.
|
|
//
|
|
// All calls here will be from the `SYSTEM_ADDRESS`: 2^160 - 2
|
|
// and will have an effect on the block's state.
|
|
// The caller provided here may not generate proofs.
|
|
//
|
|
// `first` is true if this is the first block in the set.
|
|
fn on_epoch_begin(
|
|
&self,
|
|
_first: bool,
|
|
_header: &Header,
|
|
_call: &mut SystemCall,
|
|
) -> Result<(), ::error::Error> {
|
|
Ok(())
|
|
}
|
|
|
|
// Extract genesis epoch data from the genesis state and header.
|
|
fn genesis_epoch_data(&self, _header: &Header, _call: &Call) -> Result<Vec<u8>, String> {
|
|
Ok(Vec::new())
|
|
}
|
|
|
|
// Whether this block is the last one in its epoch.
|
|
//
|
|
// Indicates that the validator set changed at the given block in a manner
|
|
// that doesn't require finality.
|
|
//
|
|
// `first` is true if this is the first block in the set.
|
|
fn is_epoch_end(&self, first: bool, chain_head: &Header) -> Option<Vec<u8>>;
|
|
|
|
// Whether the given block signals the end of an epoch, but change won't take effect
|
|
// until finality.
|
|
//
|
|
// Engine should set `first` only if the header is genesis. Multiplexing validator
|
|
// sets can set `first` to internal changes.
|
|
fn signals_epoch_end(
|
|
&self,
|
|
first: bool,
|
|
header: &Header,
|
|
aux: AuxiliaryData,
|
|
) -> ::engines::EpochChange<EthereumMachine>;
|
|
|
|
// Recover the validator set from the given proof, the block number, and
|
|
// whether this header is first in its set.
|
|
//
|
|
// May fail if the given header doesn't kick off an epoch or
|
|
// the proof is invalid.
|
|
//
|
|
// Returns the set, along with a flag indicating whether finality of a specific
|
|
// hash should be proven.
|
|
fn epoch_set(
|
|
&self,
|
|
first: bool,
|
|
machine: &EthereumMachine,
|
|
number: BlockNumber,
|
|
proof: &[u8],
|
|
) -> Result<(SimpleList, Option<H256>), ::error::Error>;
|
|
|
|
// Checks if a given address is a validator, with the given function
|
|
// for executing synchronous calls to contracts.
|
|
fn contains_with_caller(
|
|
&self,
|
|
parent_block_hash: &H256,
|
|
address: &Address,
|
|
caller: &Call,
|
|
) -> bool;
|
|
|
|
// Draws an validator nonce modulo number of validators.
|
|
fn get_with_caller(&self, parent_block_hash: &H256, nonce: usize, caller: &Call) -> Address;
|
|
|
|
// Returns the current number of validators.
|
|
fn count_with_caller(&self, parent_block_hash: &H256, caller: &Call) -> usize;
|
|
|
|
// Notifies about malicious behaviour.
|
|
fn report_malicious(
|
|
&self,
|
|
_validator: &Address,
|
|
_set_block: BlockNumber,
|
|
_block: BlockNumber,
|
|
_proof: Bytes,
|
|
) {
|
|
}
|
|
// Notifies about benign misbehaviour.
|
|
fn report_benign(&self, _validator: &Address, _set_block: BlockNumber, _block: BlockNumber) {}
|
|
*/
|
|
}
|
|
|
|
func get(s ValidatorSet, h common.Hash, nonce uint) (common.Address, error) {
|
|
d, err := s.defaultCaller(h)
|
|
if err != nil {
|
|
return common.Address{}, err
|
|
}
|
|
return s.getWithCaller(h, nonce, d)
|
|
}
|
|
|
|
//nolint
|
|
type MultiItem struct {
|
|
num uint64
|
|
hash common.Hash
|
|
set ValidatorSet
|
|
}
|
|
|
|
type Multi struct {
|
|
sorted []MultiItem
|
|
parent func(common.Hash) *types.Header
|
|
}
|
|
|
|
func (s *Multi) Less(i, j int) bool { return s.sorted[i].num < s.sorted[j].num }
|
|
func (s *Multi) Len() int { return len(s.sorted) }
|
|
func (s *Multi) Swap(i, j int) { s.sorted[i], s.sorted[j] = s.sorted[j], s.sorted[i] }
|
|
|
|
func NewMulti(m map[uint64]ValidatorSet) *Multi {
|
|
if _, ok := m[0]; !ok {
|
|
panic("ValidatorSet has to be specified from block 0")
|
|
}
|
|
list := make([]MultiItem, len(m))
|
|
i := 0
|
|
for n, v := range m {
|
|
list[i] = MultiItem{num: n, set: v}
|
|
i++
|
|
}
|
|
multi := &Multi{sorted: list}
|
|
sort.Sort(multi)
|
|
return multi
|
|
}
|
|
|
|
func (s *Multi) defaultCaller(blockHash common.Hash) (Call, error) {
|
|
set, ok := s.correctSet(blockHash)
|
|
if !ok {
|
|
return nil, fmt.Errorf("no validator set for given blockHash: %x", blockHash)
|
|
}
|
|
return set.defaultCaller(blockHash)
|
|
}
|
|
|
|
func (s *Multi) getWithCaller(parentHash common.Hash, nonce uint, caller Call) (common.Address, error) {
|
|
panic("not implemented")
|
|
}
|
|
|
|
func (s *Multi) correctSet(blockHash common.Hash) (ValidatorSet, bool) {
|
|
parent := s.parent(blockHash)
|
|
if parent == nil {
|
|
return nil, false
|
|
}
|
|
_, set := s.correctSetByNumber(parent.Number.Uint64())
|
|
return set, set != nil
|
|
}
|
|
|
|
func (s *Multi) correctSetByNumber(parentNumber uint64) (uint64, ValidatorSet) {
|
|
// get correct set by block number, along with block number at which
|
|
// this set was activated.
|
|
for i := len(s.sorted); i >= 0; i-- {
|
|
if s.sorted[i].num <= parentNumber+1 {
|
|
return s.sorted[i].num, s.sorted[i].set
|
|
}
|
|
}
|
|
panic("constructor validation ensures that there is at least one validator set for block 0; block 0 is less than any uint; qed")
|
|
}
|
|
|
|
func (s *Multi) get(num uint64) (first bool, set ValidatorSet) {
|
|
block, set := s.correctSetByNumber(num)
|
|
first = block == num
|
|
return first, set
|
|
}
|
|
|
|
func (s *Multi) onCloseBlock(header *types.Header, address common.Address) error {
|
|
_, set := s.get(header.Number.Uint64())
|
|
return set.onCloseBlock(header, address)
|
|
}
|
|
|
|
//func (s *Multi) onEpochBegin(first bool, header *types.Header, call SysCall) error {
|
|
// first, set := s.get(header.Number.Uint64())
|
|
// return set.onEpochBegin(first,header, address)
|
|
//}
|
|
|
|
type SimpleList struct {
|
|
validators []common.Address
|
|
}
|
|
|
|
func (s *SimpleList) onCloseBlock(_header *types.Header, _address common.Address) error { return nil }
|
|
func (s *SimpleList) defaultCaller(blockHash common.Hash) (Call, error) {
|
|
return nil, fmt.Errorf("simple list doesn't require calls")
|
|
}
|
|
func (s *SimpleList) getWithCaller(parentHash common.Hash, nonce uint, caller Call) (common.Address, error) {
|
|
if len(s.validators) == 0 {
|
|
return common.Address{}, fmt.Errorf("cannot operate with an empty validator set")
|
|
}
|
|
return s.validators[nonce%uint(len(s.validators))], nil
|
|
}
|
|
|
|
// Draws an validator nonce modulo number of validators.
|
|
|
|
func NewSimpleList(validators []common.Address) *SimpleList {
|
|
return &SimpleList{validators: validators}
|
|
}
|
|
|
|
//nolint
|
|
type ReportQueueItem struct {
|
|
addr common.Address
|
|
blockNum uint64
|
|
data []byte
|
|
}
|
|
type ReportQueue struct {
|
|
sync.RWMutex
|
|
list *list.List
|
|
}
|
|
|
|
//nolint
|
|
func (q *ReportQueue) push(addr common.Address, blockNum uint64, data []byte) {
|
|
q.Lock()
|
|
defer q.Unlock()
|
|
q.list.PushBack(&ReportQueueItem{addr: addr, blockNum: blockNum, data: data})
|
|
}
|
|
|
|
// Filters reports of validators that have already been reported or are banned.
|
|
|
|
func (q *ReportQueue) filter(abi aurainterfaces.ValidatorSetABI, client client, ourAddr, contractAddr common.Address) error {
|
|
q.Lock()
|
|
defer q.Unlock()
|
|
for e := q.list.Front(); e != nil; e = e.Next() {
|
|
el := e.Value.(*ReportQueueItem)
|
|
// Check if the validator should be reported.
|
|
maliciousValidatorAddress := el.addr
|
|
data, decoder := abi.ShouldValidatorReport(ourAddr, maliciousValidatorAddress, el.blockNum)
|
|
res, err := client.CallAtLatestBlock(contractAddr, data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if res.execError != "" {
|
|
log.Warn("Failed to query report status, dropping pending report.", "reason", res.execError)
|
|
continue
|
|
}
|
|
var shouldReport bool
|
|
err = decoder(res.data, &res)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !shouldReport {
|
|
q.list.Remove(e)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Removes reports from the queue if it contains more than `MAX_QUEUED_REPORTS` entries.
|
|
func (q *ReportQueue) truncate() {
|
|
// The maximum number of reports to keep queued.
|
|
const MaxQueuedReports = 10
|
|
|
|
q.RLock()
|
|
defer q.RUnlock()
|
|
// Removes reports from the queue if it contains more than `MAX_QUEUED_REPORTS` entries.
|
|
if q.list.Len() > MaxQueuedReports {
|
|
log.Warn("Removing reports from report cache, even though it has not been finalized", "amount", q.list.Len()-MaxQueuedReports)
|
|
}
|
|
i := 0
|
|
for e := q.list.Front(); e != nil; e = e.Next() {
|
|
if i > MaxQueuedReports {
|
|
q.list.Remove(e)
|
|
}
|
|
i++
|
|
}
|
|
}
|
|
|
|
// The validator contract should have the following interface:
|
|
//nolint
|
|
type ValidatorSafeContract struct {
|
|
contractAddress common.Address
|
|
validators *lru.Cache // RwLock<MemoryLruCache<H256, SimpleList>>,
|
|
reportQueue ReportQueue //Mutex<ReportQueue>,
|
|
// The block number where we resent the queued reports last time.
|
|
resentReportsInBlock atomic.Uint64
|
|
// If set, this is the block number at which the consensus engine switches from AuRa to AuRa
|
|
// with POSDAO modifications.
|
|
posdaoTransition *uint64
|
|
|
|
abi aurainterfaces.ValidatorSetABI
|
|
client client
|
|
}
|
|
|
|
func NewValidatorSafeContract(contractAddress common.Address, posdaoTransition *uint64, abi aurainterfaces.ValidatorSetABI, client client) *ValidatorSafeContract {
|
|
const MemoizeCapacity = 500
|
|
c, err := lru.New(MemoizeCapacity)
|
|
if err != nil {
|
|
panic("error creating ValidatorSafeContract cache")
|
|
}
|
|
return &ValidatorSafeContract{contractAddress: contractAddress, posdaoTransition: posdaoTransition, validators: c, client: client}
|
|
}
|
|
|
|
// Called for each new block this node is creating. If this block is
|
|
// the first block of an epoch, this is called *after* `on_epoch_begin()`,
|
|
// but with the same parameters.
|
|
//
|
|
// Returns a list of contract calls to be pushed onto the new block.
|
|
//func generateEngineTransactions(_first bool, _header *types.Header, _call SystemCall) -> Result<Vec<(Address, Bytes)>, EthcoreError>
|
|
|
|
func (s *ValidatorSafeContract) defaultCaller(blockHash common.Hash) (Call, error) {
|
|
return func(addr common.Address, data []byte) (CallResults, error) {
|
|
return s.client.CallAtBlockHash(blockHash, addr, data)
|
|
}, nil
|
|
}
|
|
func (s *ValidatorSafeContract) getWithCaller(blockHash common.Hash, nonce uint, caller Call) (common.Address, error) {
|
|
set, ok := s.validators.Get(blockHash)
|
|
if ok {
|
|
return get(set.(ValidatorSet), blockHash, nonce)
|
|
}
|
|
|
|
list, ok := s.getList(caller)
|
|
if !ok {
|
|
return common.Address{}, nil
|
|
}
|
|
return get(list, blockHash, nonce)
|
|
}
|
|
|
|
func (s *ValidatorSafeContract) getList(caller Call) (*SimpleList, bool) {
|
|
code, decoder := s.abi.GetValidators()
|
|
callResult, err := caller(s.contractAddress, code)
|
|
if err != nil {
|
|
log.Debug("Set of validators could not be updated: ", "err", err)
|
|
return nil, false
|
|
}
|
|
if callResult.execError != "" {
|
|
log.Debug("Set of validators could not be updated: ", "err", callResult.execError)
|
|
return nil, false
|
|
}
|
|
var res []common.Address
|
|
err = decoder(callResult.data, &res)
|
|
if err != nil {
|
|
log.Debug("Set of validators could not be updated: ", "err", err)
|
|
return nil, false
|
|
}
|
|
return NewSimpleList(res), true
|
|
}
|
|
|
|
func (s *ValidatorSafeContract) onCloseBlock(header *types.Header, ourAddress common.Address) error {
|
|
// Skip the rest of the function unless there has been a transition to POSDAO AuRa.
|
|
if s.posdaoTransition != nil && header.Number.Uint64() < *s.posdaoTransition {
|
|
log.Trace("Skipping resending of queued malicious behavior reports")
|
|
return nil
|
|
}
|
|
err := s.reportQueue.filter(s.abi, s.client, ourAddress, s.contractAddress)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.reportQueue.truncate()
|
|
/*
|
|
let mut resent_reports_in_block = self.resent_reports_in_block.lock();
|
|
|
|
// Skip at least one block after sending malicious reports last time.
|
|
if header.number() > *resent_reports_in_block + REPORTS_SKIP_BLOCKS {
|
|
*resent_reports_in_block = header.number();
|
|
let mut nonce = client.latest_nonce(our_address);
|
|
for (address, block, data) in report_queue.iter() {
|
|
debug!(target: "engine", "Retrying to report validator {} for misbehavior on block {} with nonce {}.",
|
|
address, block, nonce);
|
|
while match self.transact(data.clone(), nonce) {
|
|
Ok(()) => false,
|
|
Err(EthcoreError(
|
|
EthcoreErrorKind::Transaction(transaction::Error::Old),
|
|
_,
|
|
)) => true,
|
|
Err(err) => {
|
|
warn!(target: "engine", "Cannot report validator {} for misbehavior on block {}: {}",
|
|
address, block, err);
|
|
false
|
|
}
|
|
} {
|
|
warn!(target: "engine", "Nonce {} already used. Incrementing.", nonce);
|
|
nonce += U256::from(1);
|
|
}
|
|
nonce += U256::from(1);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
|
|
*/
|
|
return nil
|
|
}
|
|
|
|
// A validator contract with reporting.
|
|
type ValidatorContract struct {
|
|
contractAddress common.Address
|
|
validators ValidatorSafeContract
|
|
posdaoTransition *uint64
|
|
}
|
|
|
|
func (s *ValidatorContract) defaultCaller(blockHash common.Hash) (Call, error) {
|
|
return s.validators.defaultCaller(blockHash)
|
|
}
|
|
|
|
func (s *ValidatorContract) getWithCaller(parentHash common.Hash, nonce uint, caller Call) (common.Address, error) {
|
|
return s.validators.getWithCaller(parentHash, nonce, caller)
|
|
}
|
|
func (s *ValidatorContract) onCloseBlock(header *types.Header, address common.Address) error {
|
|
return s.validators.onCloseBlock(header, address)
|
|
}
|