Skip to content

Commit

Permalink
fix: refactoring reward validation
Browse files Browse the repository at this point in the history
  • Loading branch information
egonspace committed May 17, 2024
1 parent 1bddf95 commit 24c9a5a
Show file tree
Hide file tree
Showing 16 changed files with 191 additions and 79 deletions.
10 changes: 7 additions & 3 deletions consensus/beacon/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,10 @@ func (beacon *Beacon) VerifyUncles(chain consensus.ChainReader, block *types.Blo
return nil
}

func (beacon *Beacon) ValidateEngineSpecific(*params.ChainConfig, *types.Header, *big.Int, interface{}) error {
return nil
}

// verifyHeader checks whether a header conforms to the consensus rules of the
// stock Ethereum consensus engine. The difference between the beacon and classic is
// (a) The following fields are expected to be constants:
Expand Down Expand Up @@ -265,17 +269,17 @@ func (beacon *Beacon) Prepare(chain consensus.ChainHeaderReader, header *types.H
}

// Finalize implements consensus.Engine, setting the final state on the header
func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) error {
func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) (interface{}, error) {
// Finalize is different with Prepare, it can be used in both block generation
// and verification. So determine the consensus rules by header type.
if !beacon.IsPoSHeader(header) {
beacon.ethone.Finalize(chain, header, state, txs, uncles)
return nil
return nil, nil
}
// The block reward is no longer handled here. It's done by the
// external consensus engine.
header.Root = state.IntermediateRoot(true)
return nil
return nil, nil
}

// FinalizeAndAssemble implements consensus.Engine, setting the final state and
Expand Down
8 changes: 6 additions & 2 deletions consensus/clique/clique.go
Original file line number Diff line number Diff line change
Expand Up @@ -561,11 +561,11 @@ func (c *Clique) Prepare(chain consensus.ChainHeaderReader, header *types.Header

// Finalize implements consensus.Engine, ensuring no uncles are set, nor block
// rewards given.
func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) error {
func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) (interface{}, error) {
// No block rewards in PoA, so the state remains as is and uncles are dropped
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
header.UncleHash = types.CalcUncleHash(nil)
return nil
return nil, nil
}

// FinalizeAndAssemble implements consensus.Engine, ensuring no uncles are set,
Expand All @@ -578,6 +578,10 @@ func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *
return types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil)), nil
}

func (c *Clique) ValidateEngineSpecific(*params.ChainConfig, *types.Header, *big.Int, interface{}) error {
return nil
}

// Authorize injects a private key into the consensus engine to mint new blocks
// with.
func (c *Clique) Authorize(signer common.Address, signFn SignerFn) {
Expand Down
6 changes: 5 additions & 1 deletion consensus/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,9 @@ type Engine interface {
//
// Note: The block header and state database might be updated to reflect any
// consensus rules that happen at finalization (e.g. block rewards).
// Finalize returns engine specific output which can be validated by ValidateEngineSpecific
Finalize(chain ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction,
uncles []*types.Header) error
uncles []*types.Header) (interface{}, error)

// FinalizeAndAssemble runs any post-transaction state modifications (e.g. block
// rewards) and assembles the final block.
Expand All @@ -100,6 +101,9 @@ type Engine interface {
FinalizeAndAssemble(chain ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction,
uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error)

// ValidateEngineSpecific validates some header fields with processed output
ValidateEngineSpecific(config *params.ChainConfig, header *types.Header, blockFees *big.Int, output interface{}) error

// Seal generates a new sealing request for the given input block and pushes
// the result into the given channel.
//
Expand Down
40 changes: 22 additions & 18 deletions consensus/ethash/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -603,35 +603,21 @@ func (ethash *Ethash) Prepare(chain consensus.ChainHeaderReader, header *types.H

// Finalize implements consensus.Engine, accumulating the block and uncle rewards,
// setting the final state on the header
func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) error {
var proposedReward []byte
if header.Rewards != nil {
// `accumulateRewards` updates `header.Rewards` field,
// so we save `header.Rewards` of proposed block before calling the function
// But if we're a miner and are making a block, the field should be nil.
proposedReward = header.Rewards
}

func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) (interface{}, error) {
// Accumulate any block and uncle rewards and commit the final state root
if err := accumulateRewards(chain.Config(), state, header, uncles); err != nil {
return err
return nil, err
}

if proposedReward != nil && chain.Config().IsBrioche(header.Number) {
// validate the rewards from the proposed block with calculated value locally
if !bytes.Equal(header.Rewards, proposedReward) {
return fmt.Errorf("invalid rewards (remote: %x local: %x)", proposedReward, header.Rewards)
}
}
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
return nil
return &FinalizedOutput{reward: header.Rewards}, nil
}

// FinalizeAndAssemble implements consensus.Engine, accumulating the block and
// uncle rewards, setting the final state and assembling the block.
func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
// Finalize block
if err := ethash.Finalize(chain, header, state, txs, uncles); err != nil {
if _, err := ethash.Finalize(chain, header, state, txs, uncles); err != nil {
return nil, err
}

Expand All @@ -650,6 +636,24 @@ func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea
return types.NewBlock(header, txs, uncles, receipts, trie.NewStackTrie(nil)), nil
}

func (ethash *Ethash) ValidateEngineSpecific(config *params.ChainConfig, header *types.Header, blockFees *big.Int, output interface{}) error {
if !wemixminer.IsPoW() && header.Fees.Cmp(blockFees) != 0 {
return fmt.Errorf("invalid fees collected (remote: %v local: %v)", header.Fees, blockFees)
}

if !wemixminer.IsPoW() && config.IsBrioche(header.Number) {
if out, ok := output.(*FinalizedOutput); ok {
// validate the rewards from the proposed block with calculated value locally
if !bytes.Equal(header.Rewards, out.reward) {
return fmt.Errorf("invalid rewards (remote: %x local: %x)", header.Rewards, out.reward)
}
} else {
return fmt.Errorf("invalid finalized output (%v)", output)
}
}
return nil
}

// SealHash returns the hash of a block prior to it being sealed.
func (ethash *Ethash) SealHash(header *types.Header) (hash common.Hash) {
hasher := sha3.NewLegacyKeccak256()
Expand Down
4 changes: 4 additions & 0 deletions consensus/ethash/ethash.go
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,10 @@ type Config struct {
Log log.Logger `toml:"-"`
}

type FinalizedOutput struct {
reward []byte
}

// Ethash is a consensus engine based on proof-of-work implementing the ethash
// algorithm.
type Ethash struct {
Expand Down
8 changes: 3 additions & 5 deletions core/block_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,11 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
// transition, such as amount of used gas, the receipt roots and the state root
// itself. ValidateState returns a database batch if the validation was a success
// otherwise nil and an error is returned.
func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, receipts types.Receipts, usedGas uint64, fees *big.Int) error {
func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, receipts types.Receipts, usedGas uint64, fees *big.Int, finalizedOutput interface{}) error {
header := block.Header()
if block.GasUsed() != usedGas {
return fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), usedGas)
}
if !wemixminer.IsPoW() && block.Fees().Cmp(fees) != 0 {
return fmt.Errorf("invalid fees collected (remote: %v local: %v)", block.Fees(), fees)
}

// Validate the received block's bloom with the one derived from the generated receipts.
// For valid blocks this should always validate to true.
Expand All @@ -105,7 +102,8 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD
if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root {
return fmt.Errorf("invalid merkle root (remote: %x local: %x)", header.Root, root)
}
return nil

return v.engine.ValidateEngineSpecific(v.bc.chainConfig, header, fees, finalizedOutput)
}

// CalcGasLimit computes the gas limit of the next block after parent. It aims
Expand Down
4 changes: 2 additions & 2 deletions core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -1636,7 +1636,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool)

// Process block using the parent state as reference point
substart := time.Now()
receipts, logs, usedGas, fees, err := bc.processor.Process(block, statedb, bc.vmConfig)
receipts, logs, usedGas, fees, fo, err := bc.processor.Process(block, statedb, bc.vmConfig)
if err != nil {
bc.reportBlock(block, receipts, err)
atomic.StoreUint32(&followupInterrupt, 1)
Expand All @@ -1657,7 +1657,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool)

// Validate the state using the default validator
substart = time.Now()
if err := bc.validator.ValidateState(block, statedb, receipts, usedGas, fees); err != nil {
if err := bc.validator.ValidateState(block, statedb, receipts, usedGas, fees, fo); err != nil {
bc.reportBlock(block, receipts, err)
atomic.StoreUint32(&followupInterrupt, 1)
return it.index, err
Expand Down
4 changes: 2 additions & 2 deletions core/blockchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,12 +159,12 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
if err != nil {
return err
}
receipts, _, usedGas, fees, err := blockchain.processor.Process(block, statedb, vm.Config{})
receipts, _, usedGas, fees, fo, err := blockchain.processor.Process(block, statedb, vm.Config{})
if err != nil {
blockchain.reportBlock(block, receipts, err)
return err
}
err = blockchain.validator.ValidateState(block, statedb, receipts, usedGas, fees)
err = blockchain.validator.ValidateState(block, statedb, receipts, usedGas, fees, fo)
if err != nil {
blockchain.reportBlock(block, receipts, err)
return err
Expand Down
3 changes: 1 addition & 2 deletions core/chain_makers.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,7 @@ func (b *BlockGen) AddTxWithChain(bc *BlockChain, tx *types.Transaction) {
b.SetCoinbase(common.Address{})
}
b.statedb.Prepare(tx.Hash(), len(b.txs))
fees := new(big.Int)
receipt, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, fees, vm.Config{})
receipt, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, b.header.Fees, vm.Config{})
if err != nil {
panic(err)
}
Expand Down
10 changes: 5 additions & 5 deletions core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func NewStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consen
// Process returns the receipts and logs accumulated during the process and
// returns the amount of gas that was used in the process. If any of the
// transactions failed to execute due to insufficient gas it will return an error.
func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, *big.Int, error) {
func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, *big.Int, interface{}, error) {
var (
receipts types.Receipts
usedGas = new(uint64)
Expand All @@ -77,20 +77,20 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
for i, tx := range block.Transactions() {
msg, err := tx.AsMessage(types.MakeSigner(p.config, header.Number), header.BaseFee)
if err != nil {
return nil, nil, 0, big.NewInt(0), fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
return nil, nil, 0, big.NewInt(0), nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
}
statedb.Prepare(tx.Hash(), i)
receipt, err := applyTransaction(msg, p.config, p.bc, nil, gp, statedb, blockNumber, blockHash, tx, usedGas, fees, vmenv)
if err != nil {
return nil, nil, 0, big.NewInt(0), fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
return nil, nil, 0, big.NewInt(0), nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
}
receipts = append(receipts, receipt)
allLogs = append(allLogs, receipt.Logs...)
}
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles())
fo, err := p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles())

return receipts, allLogs, *usedGas, fees, nil
return receipts, allLogs, *usedGas, fees, fo, err
}

func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, fees *big.Int, evm *vm.EVM) (*types.Receipt, error) {
Expand Down
4 changes: 2 additions & 2 deletions core/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type Validator interface {

// ValidateState validates the given statedb and optionally the receipts and
// gas used.
ValidateState(block *types.Block, state *state.StateDB, receipts types.Receipts, usedGas uint64, fees *big.Int) error
ValidateState(block *types.Block, state *state.StateDB, receipts types.Receipts, usedGas uint64, fees *big.Int, finalizedOutput interface{}) error
}

// Prefetcher is an interface for pre-caching transaction signatures and state.
Expand All @@ -49,5 +49,5 @@ type Processor interface {
// Process processes the state changes according to the Ethereum rules by running
// the transaction messages using the statedb and applying any rewards to both
// the processor (coinbase) and any included uncles.
Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, *big.Int, error)
Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, *big.Int, interface{}, error)
}
2 changes: 1 addition & 1 deletion eth/state_accessor.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ func (eth *Ethereum) StateAtBlock(block *types.Block, reexec uint64, base *state
if current = eth.blockchain.GetBlockByNumber(next); current == nil {
return nil, fmt.Errorf("block #%d not found", next)
}
_, _, _, _, err := eth.blockchain.Processor().Process(current, statedb, vm.Config{})
_, _, _, _, _, err := eth.blockchain.Processor().Process(current, statedb, vm.Config{})
if err != nil {
return nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(), err)
}
Expand Down
21 changes: 9 additions & 12 deletions params/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -459,19 +459,16 @@ func (bc *BriocheConfig) GetBriocheBlockReward(defaultReward *big.Int, num *big.
}

func (bc *BriocheConfig) halveRewards(baseReward *big.Int, num *big.Int) *big.Int {
result := big.NewInt(0).Set(baseReward)
past := big.NewInt(0).Set(num)
past.Sub(past, bc.FirstHalvingBlock)
halvingTimes := bc.HalvingTimes
for ; halvingTimes > 0; halvingTimes-- {
result = result.Mul(result, big.NewInt(int64(bc.HalvingRate))) // `HalvingRate` may be greater than 100 theoretically
result = result.Div(result, big.NewInt(100))
if past.Cmp(bc.HalvingPeriod) < 0 {
break
}
past = past.Sub(past, bc.HalvingPeriod)
elapsed := new(big.Int).Sub(num, bc.FirstHalvingBlock)
times := new(big.Int).Add(common.Big1, new(big.Int).Div(elapsed, bc.HalvingPeriod))
if times.Uint64() > bc.HalvingTimes {
times = big.NewInt(int64(bc.HalvingTimes))
}
return result

reward := new(big.Int).Set(baseReward)
numerator := new(big.Int).Exp(big.NewInt(int64(bc.HalvingRate)), times, nil)
denominator := new(big.Int).Exp(big.NewInt(100), times, nil)
return reward.Div(reward.Mul(reward, numerator), denominator)
}

// String implements the stringer interface, returning the consensus engine details.
Expand Down
Loading

0 comments on commit 24c9a5a

Please sign in to comment.