Skip to content

Commit

Permalink
core,consensus: reduce code duplication, add test for supply.Delta
Browse files Browse the repository at this point in the history
  • Loading branch information
lightclient committed Jul 24, 2023
1 parent 0298e1e commit e39372d
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 81 deletions.
23 changes: 16 additions & 7 deletions consensus/ethash/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ func (ethash *Ethash) Prepare(chain consensus.ChainHeaderReader, header *types.H
// Finalize implements consensus.Engine, accumulating the block and uncle rewards.
func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, withdrawals []*types.Withdrawal) {
// Accumulate any block and uncle rewards
accumulateRewards(chain.Config(), state, header, uncles)
applyRewards(chain.Config(), state, header, uncles)
}

// FinalizeAndAssemble implements consensus.Engine, accumulating the block and
Expand Down Expand Up @@ -543,10 +543,19 @@ var (
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.
func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header *types.Header, uncles []*types.Header) {
// applyRewards credits the coinbase of the given block with the mining reward.
func applyRewards(config *params.ChainConfig, state *state.StateDB, header *types.Header, uncles []*types.Header) {
f := func(h *types.Header, amt *big.Int) {
state.AddBalance(h.Coinbase, amt)
}
AccumulateRewards(config, header, uncles, f, f)
}

// AccumulateRewards is a generic function that allows the caller to decide how
// to apply rewards. The total reward consists of the static block reward and
// rewards for included uncles. The coinbase of each uncle block is also
// rewarded.
func AccumulateRewards(config *params.ChainConfig, header *types.Header, uncles []*types.Header, accUncleReward, accTotalReward func(*types.Header, *big.Int)) {
// Select the correct block reward based on chain progression
blockReward := FrontierBlockReward
if config.IsByzantium(header.Number) {
Expand All @@ -563,10 +572,10 @@ func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header
r.Sub(r, header.Number)
r.Mul(r, blockReward)
r.Div(r, big8)
state.AddBalance(uncle.Coinbase, r)
accUncleReward(uncle, r)

r.Div(blockReward, big32)
reward.Add(reward, r)
}
state.AddBalance(header.Coinbase, reward)
accTotalReward(header, reward)
}
16 changes: 15 additions & 1 deletion core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -1401,12 +1401,26 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
if parent == nil {
log.Error("Failed to retrieve parent for supply delta", "err", err)
} else {
supplyDelta, err := supply.Delta(block, parent, bc.stateCache.TrieDB(), bc.chainConfig)
start := time.Now()

supplyDelta, err := supply.Delta(parent, block.Header(), bc.stateCache.TrieDB())
if err != nil {
log.Error("Failed to record Ether supply delta", "err", err)
} else {
rawdb.WriteSupplyDelta(bc.db, block.NumberU64(), block.Hash(), supplyDelta)
}

// Calculate the block coinbaseReward based on chain rules and progression.
coinbaseReward, unclesReward, burn, withdrawals := supply.Subsidy(block, bc.chainConfig)

// Calculate the difference between the "calculated" and "crawled" supply delta.
diff := new(big.Int).Set(supplyDelta)
diff.Sub(diff, coinbaseReward)
diff.Sub(diff, unclesReward)
diff.Sub(diff, withdrawals)
diff.Add(diff, burn)

log.Info("Calculated supply delta for block", "number", block.Number(), "hash", block.Hash(), "supplydelta", supplyDelta, "coinbasereward", coinbaseReward, "unclesreward", unclesReward, "burn", burn, "withdrawals", withdrawals, "diff", diff, "elapsed", time.Since(start))
}
}
return nil
Expand Down
79 changes: 79 additions & 0 deletions core/blockchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/supply"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
Expand Down Expand Up @@ -4340,4 +4341,82 @@ func TestEIP3651(t *testing.T) {
if actual.Cmp(expected) != 0 {
t.Fatalf("sender balance incorrect: expected %d, got %d", expected, actual)
}

}

func TestDelta(t *testing.T) {
var (
aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa")
engine = beacon.NewFaker()

// A sender who makes transactions, has some funds
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
funds = big.NewInt(params.Ether)
gspec = &Genesis{
Config: params.AllEthashProtocolChanges,
Alloc: GenesisAlloc{
addr1: {Balance: funds},
// The address 0xAAAA self-destructs
aa: {
Code: []byte{
byte(vm.ADDRESS),
byte(vm.SELFDESTRUCT),
},
Nonce: 0,
Balance: big.NewInt(41),
},
},
}
)

gspec.Config.TerminalTotalDifficulty = common.Big0
gspec.Config.TerminalTotalDifficultyPassed = true
gspec.Config.ShanghaiTime = u64(0)
signer := types.LatestSigner(gspec.Config)

db, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) {
b.SetCoinbase(common.Address{1})

// One transaction to 0xAAAA
txdata := &types.DynamicFeeTx{
ChainID: gspec.Config.ChainID,
Nonce: 0,
To: &aa,
Value: common.Big1,
Gas: 50000,
GasFeeCap: newGwei(5),
GasTipCap: big.NewInt(2),
}
tx := types.NewTx(txdata)
tx, _ = types.SignTx(tx, signer, key1)

b.AddTx(tx)
b.AddWithdrawal(&types.Withdrawal{Amount: 1337})
})

var (
parent = gspec.ToBlock().Header()
block = blocks[0]
)

got, err := supply.Delta(parent, block.Header(), trie.NewDatabase(db))
if err != nil {
t.Fatalf("failed to calculate delta: %v", err)
}

// Calculate delta, w/o self-destructs
coinbaseReward, _, burn, withdrawals := supply.Subsidy(block, gspec.Config)

want := new(big.Int)
want.Add(want, coinbaseReward)
want.Add(want, withdrawals)
want.Sub(want, burn)

// Now account for self-destructed amount.
want.Sub(want, big.NewInt(42))

if want.Cmp(got) != 0 {
t.Fatalf("incorrect delta calculated: want %d, got %d", want, got)
}
}
113 changes: 40 additions & 73 deletions core/supply/delta.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,120 +19,87 @@ package supply
import (
"fmt"
"math/big"
"time"

"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
)

// Delta calculates the Ether delta across two state tries. That is, the
// Delta calculates the ether delta across two state tries. That is, the
// issuance minus the ether destroyed.
func Delta(block *types.Block, parent *types.Header, db *trie.Database, config *params.ChainConfig) (*big.Int, error) {
var (
supplyDelta = new(big.Int)
start = time.Now()
)
// Open the two tries.
if block.ParentHash() != parent.Hash() {
return nil, fmt.Errorf("parent hash mismatch: have %s, want %s", block.ParentHash().Hex(), parent.Hash().Hex())
}
src, err := trie.New(trie.StateTrieID(parent.Root), db)
func Delta(src, dst *types.Header, db *trie.Database) (*big.Int, error) {
// Open src and dst tries.
srcTrie, err := trie.New(trie.StateTrieID(src.Root), db)
if err != nil {
return nil, fmt.Errorf("failed to open source trie: %v", err)
}
dst, err := trie.New(trie.StateTrieID(block.Root()), db)
dstTrie, err := trie.New(trie.StateTrieID(dst.Root), db)
if err != nil {
return nil, fmt.Errorf("failed to open destination trie: %v", err)
}

delta := new(big.Int)

// Gather all the changes across from source to destination.
fwdDiffIt, _ := trie.NewDifferenceIterator(src.MustNodeIterator(nil), dst.MustNodeIterator(nil))
fwdDiffIt, _ := trie.NewDifferenceIterator(srcTrie.MustNodeIterator(nil), dstTrie.MustNodeIterator(nil))
fwdIt := trie.NewIterator(fwdDiffIt)

for fwdIt.Next() {
acc := new(types.StateAccount)
if err := rlp.DecodeBytes(fwdIt.Value, acc); err != nil {
panic(err)
}
supplyDelta.Add(supplyDelta, acc.Balance)
delta.Add(delta, acc.Balance)
}
// Gather all the changes across from destination to source.
rewDiffIt, _ := trie.NewDifferenceIterator(dst.MustNodeIterator(nil), src.MustNodeIterator(nil))
rewIt := trie.NewIterator(rewDiffIt)
revDiffIt, _ := trie.NewDifferenceIterator(dstTrie.MustNodeIterator(nil), srcTrie.MustNodeIterator(nil))
revIt := trie.NewIterator(revDiffIt)

for rewIt.Next() {
for revIt.Next() {
acc := new(types.StateAccount)
if err := rlp.DecodeBytes(rewIt.Value, acc); err != nil {
if err := rlp.DecodeBytes(revIt.Value, acc); err != nil {
panic(err)
}
supplyDelta.Sub(supplyDelta, acc.Balance)
delta.Sub(delta, acc.Balance)
}
// Calculate the block fixedReward based on chain rules and progression.
fixedReward, unclesReward, burn, withdrawals := Subsidy(block, config)

// Calculate the difference between the "calculated" and "crawled" supply
// delta.
diff := new(big.Int).Set(supplyDelta)
diff.Sub(diff, fixedReward)
diff.Sub(diff, unclesReward)
diff.Add(diff, burn)

log.Info("Calculated supply delta for block", "number", block.Number(), "hash", block.Hash(), "supplydelta", supplyDelta, "fixedreward", fixedReward, "unclesreward", unclesReward, "burn", burn, "withdrawals", withdrawals, "diff", diff, "elapsed", time.Since(start))
return supplyDelta, nil
return delta, nil
}

// Subsidy calculates the block mining (fixed) and uncle subsidy as well as the
// 1559 burn solely based on header fields. This method is a very accurate
// approximation of the true supply delta, but cannot take into account Ether
// burns via selfdestructs, so it will always be ever so slightly off.
func Subsidy(block *types.Block, config *params.ChainConfig) (fixedReward *big.Int, unclesReward *big.Int, burn *big.Int, withdrawals *big.Int) {
// Calculate the block rewards based on chain rules and progression.
fixedReward = new(big.Int)
unclesReward = new(big.Int)
withdrawals = new(big.Int)

// Select the correct block reward based on chain progression.
if config.Ethash != nil {
if block.Difficulty().BitLen() != 0 {
fixedReward = ethash.FrontierBlockReward
if config.IsByzantium(block.Number()) {
fixedReward = ethash.ByzantiumBlockReward
}
if config.IsConstantinople(block.Number()) {
fixedReward = ethash.ConstantinopleBlockReward
}
// Subsidy calculates the coinbase subsidy and uncle subsidy as well as the
// EIP-1559 burn. This method is a very accurate approximation of the true
// supply delta, but cannot take into account ether burns via selfdestructs, so
// it will always be slightly off.
func Subsidy(block *types.Block, config *params.ChainConfig) (*big.Int, *big.Int, *big.Int, *big.Int) {
var (
coinbaseReward = new(big.Int)
unclesReward = new(big.Int)
withdrawals = new(big.Int)
)
// If block is ethash, calculate the coinbase and uncle rewards.
if config.Ethash != nil && block.Difficulty().BitLen() != 0 {
accCoinbase := func(h *types.Header, amt *big.Int) {
coinbaseReward.Add(coinbaseReward, amt)
}
// Accumulate the rewards for included uncles.
var (
big8 = big.NewInt(8)
big32 = big.NewInt(32)
r = new(big.Int)
)
for _, uncle := range block.Uncles() {
// Add the reward for the side blocks.
r.Add(uncle.Number, big8)
r.Sub(r, block.Number())
r.Mul(r, fixedReward)
r.Div(r, big8)
unclesReward.Add(unclesReward, r)

// Add the reward for accumulating the side blocks.
r.Div(fixedReward, big32)
unclesReward.Add(unclesReward, r)
accUncles := func(h *types.Header, amt *big.Int) {
unclesReward.Add(unclesReward, amt)
}
ethash.AccumulateRewards(config, block.Header(), block.Uncles(), accCoinbase, accUncles)
}
// Calculate the burn based on chain rules and progression.
burn = new(big.Int)
burn := new(big.Int)
if block.BaseFee() != nil {
burn = new(big.Int).Mul(new(big.Int).SetUint64(block.GasUsed()), block.BaseFee())
}

// Sum up withdrawals.
for _, w := range block.Withdrawals() {
withdrawals.Add(withdrawals, big.NewInt(int64(w.Amount)))
withdrawals.Add(withdrawals, newGwei(w.Amount))
}
return coinbaseReward, unclesReward, burn, withdrawals
}

return fixedReward, unclesReward, burn, withdrawals
func newGwei(n uint64) *big.Int {
return new(big.Int).Mul(big.NewInt(int64(n)), big.NewInt(params.GWei))
}

0 comments on commit e39372d

Please sign in to comment.