Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd, core, eth: add issuance tracking and querying #24723

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ var (
utils.NoUSBFlag,
utils.USBFlag,
utils.SmartCardDaemonPathFlag,
utils.IssuanceFlag,
utils.OverrideArrowGlacierFlag,
utils.OverrideTerminalTotalDifficulty,
utils.EthashCacheDirFlag,
Expand Down
39 changes: 39 additions & 0 deletions cmd/geth/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (

"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/issuance"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/state/pruner"
Expand Down Expand Up @@ -156,6 +157,18 @@ as the backend data source, making this command a lot faster.

The argument is interpreted as block number or hash. If none is provided, the latest
block is used.
`,
},
{
Name: "crawl-supply",
Usage: "Calculate the Ether supply at a specific block",
Action: utils.MigrateFlags(crawlSupply),
Category: "MISCELLANEOUS COMMANDS",
Flags: utils.GroupFlags(utils.NetworkFlags, utils.DatabasePathFlags),
Description: `
geth snapshot crawl-supply
will traverse the whole state from the given root and accumulate all the Ether
balances to calculate the total supply.
`,
},
},
Expand Down Expand Up @@ -576,3 +589,29 @@ func dumpState(ctx *cli.Context) error {
"elapsed", common.PrettyDuration(time.Since(start)))
return nil
}

func crawlSupply(ctx *cli.Context) error {
stack, _ := makeConfigNode(ctx)
defer stack.Close()

chaindb := utils.MakeChainDatabase(ctx, stack, true)
headBlock := rawdb.ReadHeadBlock(chaindb)
if headBlock == nil {
log.Error("Failed to load head block")
return errors.New("no head block")
}
if ctx.NArg() > 1 {
log.Error("Too many arguments given")
return errors.New("too many arguments")
}
snaptree, err := snapshot.New(chaindb, trie.NewDatabase(chaindb), 256, headBlock.Root(), false, false, false)
if err != nil {
log.Error("Failed to open snapshot tree", "err", err)
return err
}
if _, err = issuance.Supply(headBlock.Header(), snaptree); err != nil {
log.Error("Failed to calculate current supply", "err", err)
return err
}
return nil
}
1 change: 1 addition & 0 deletions cmd/geth/usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{
utils.IdentityFlag,
utils.LightKDFFlag,
utils.EthPeerRequiredBlocksFlag,
utils.IssuanceFlag,
}, utils.NetworkFlags, utils.DatabasePathFlags),
},
{
Expand Down
15 changes: 7 additions & 8 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,11 +181,6 @@ var (
Name: "identity",
Usage: "Custom node name",
}
DocRootFlag = DirectoryFlag{
Name: "docroot",
Usage: "Document Root for HTTPClient file scheme",
Value: DirectoryString(HomeDir()),
}
ExitWhenSyncedFlag = cli.BoolFlag{
Name: "exitwhensynced",
Usage: "Exits after block synchronisation completes",
Expand Down Expand Up @@ -821,6 +816,10 @@ var (
Usage: "InfluxDB organization name (v2 only)",
Value: metrics.DefaultConfig.InfluxDBOrganization,
}
IssuanceFlag = cli.BoolFlag{
Name: "issuance",
Usage: "Track Ether issuance (don't use in production)",
}
)

var (
Expand Down Expand Up @@ -1673,9 +1672,6 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
cfg.SnapshotCache = 0 // Disabled
}
}
if ctx.GlobalIsSet(DocRootFlag.Name) {
cfg.DocRoot = ctx.GlobalString(DocRootFlag.Name)
}
if ctx.GlobalIsSet(VMEnableDebugFlag.Name) {
// TODO(fjl): force-enable this in --dev mode
cfg.EnablePreimageRecording = ctx.GlobalBool(VMEnableDebugFlag.Name)
Expand Down Expand Up @@ -1705,6 +1701,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
cfg.EthDiscoveryURLs = SplitAndTrim(urls)
}
}
if ctx.GlobalIsSet(IssuanceFlag.Name) {
cfg.EnableIssuanceRecording = ctx.GlobalBool(IssuanceFlag.Name)
}
// Override any default configs for hard coded networks.
switch {
case ctx.GlobalBool(MainnetFlag.Name):
Expand Down
23 changes: 20 additions & 3 deletions core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/ethereum/go-ethereum/common/mclock"
"github.com/ethereum/go-ethereum/common/prque"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core/issuance"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/state/snapshot"
Expand Down Expand Up @@ -1185,7 +1186,7 @@ func (bc *BlockChain) writeKnownBlock(block *types.Block) error {

// writeBlockWithState writes block, metadata and corresponding state data to the
// database.
func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB) error {
func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, state *state.StateDB) error {
// Calculate the total difficulty of the block
ptd := bc.GetTd(block.ParentHash(), block.NumberU64()-1)
if ptd == nil {
Expand Down Expand Up @@ -1263,6 +1264,22 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
}
}
}
// If Ether issuance tracking is enabled, do it before emitting events
if bc.vmConfig.EnableIssuanceRecording {
// Note, this code path is opt-in for data analysis nodes, so speed
// is not really relevant, simplicity and containment much more so.
parent := rawdb.ReadHeader(bc.db, block.ParentHash(), block.NumberU64()-1)
if parent == nil {
log.Error("Failed to retrieve parent for issuance", "err", err)
} else {
issuance, err := issuance.Issuance(block, parent, bc.stateCache.TrieDB(), bc.chainConfig)
if err != nil {
log.Error("Failed to record Ether issuance", "err", err)
} else {
rawdb.WriteIssuance(bc.db, block.NumberU64(), block.Hash(), issuance)
}
}
}
return nil
}

Expand All @@ -1280,7 +1297,7 @@ func (bc *BlockChain) WriteBlockAndSetHead(block *types.Block, receipts []*types
// and also it applies the given block as the new chain head. This function expects
// the chain mutex to be held.
func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) {
if err := bc.writeBlockWithState(block, receipts, logs, state); err != nil {
if err := bc.writeBlockWithState(block, receipts, state); err != nil {
return NonStatTy, err
}
currentBlock := bc.CurrentBlock()
Expand Down Expand Up @@ -1643,7 +1660,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool)
var status WriteStatus
if !setHead {
// Don't set the head, only insert the block
err = bc.writeBlockWithState(block, receipts, logs, statedb)
err = bc.writeBlockWithState(block, receipts, statedb)
} else {
status, err = bc.writeBlockAndSetHead(block, receipts, logs, statedb, false)
}
Expand Down
169 changes: 169 additions & 0 deletions core/issuance/issuance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// Copyright 2022 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 issuance

import (
"fmt"
"math/big"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core/state/snapshot"
"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"
)

// Issuance calculates the Ether issuance (or burn) across two state tries. In
// normal mode of operation, the expectation is to calculate the issuance between
// two consecutive blocks.
func Issuance(block *types.Block, parent *types.Header, db *trie.Database, config *params.ChainConfig) (*big.Int, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO this is not issuance, but "EtherDelta". Issuance is "how much is issued", which IMO should be mining-rewards.

This is how I think of the terms:

  • rewards: mining rewards
  • burn: 1559 burn
  • issuance: rewards - burn
  • delta: issuance - destroyed ether

And the supply is the delta between empty-state (before genesis) and chain head.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO this is not issuance, but "EtherDelta". Issuance is "how much is issued", which IMO should be mining-rewards.

Similar comment here :) #24723 (comment)

the delta

"delta" is a great word, probably better than "diff" or "change". I'll edit my comment above to use "delta".

var (
issuance = 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(parent.Root, db)
if err != nil {
return nil, fmt.Errorf("failed to open source trie: %v", err)
}
dst, err := trie.New(block.Root(), db)
if err != nil {
return nil, fmt.Errorf("failed to open destination trie: %v", err)
}
// Gather all the changes across from source to destination
fwdDiffIt, _ := trie.NewDifferenceIterator(src.NodeIterator(nil), dst.NodeIterator(nil))
fwdIt := trie.NewIterator(fwdDiffIt)

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

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

// Calculate the difference between the "calculated" and "crawled" issuance
diff := new(big.Int).Set(issuance)
diff.Sub(diff, subsidy)
diff.Sub(diff, uncles)
diff.Add(diff, burn)

log.Info("Calculated issuance for block", "number", block.Number(), "hash", block.Hash(), "state", issuance, "subsidy", subsidy, "uncles", uncles, "burn", burn, "diff", diff, "elapsed", time.Since(start))
return issuance, nil
}

// Subsidy calculates the block mining and uncle subsidy as well as the 1559 burn
// solely based on header fields. This method is a very accurate approximation of
// the true issuance, 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) (subsidy *big.Int, uncles *big.Int, burn *big.Int) {
// Calculate the block subsidy based on chain rules and progression
subsidy = new(big.Int)
uncles = new(big.Int)

// Select the correct block reward based on chain progression
if config.Ethash != nil {
if block.Difficulty().BitLen() != 0 {
subsidy = ethash.FrontierBlockReward
if config.IsByzantium(block.Number()) {
subsidy = ethash.ByzantiumBlockReward
}
if config.IsConstantinople(block.Number()) {
subsidy = ethash.ConstantinopleBlockReward
}
}
// Accumulate the rewards for inclded uncles
Copy link

@alextes alextes Jun 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*included

(:

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, subsidy)
r.Div(r, big8)
uncles.Add(uncles, r)

// Add the reward for accumulating the side blocks
r.Div(subsidy, big32)
uncles.Add(uncles, r)
}
}
// Calculate the burn based on chain rules and progression
burn = new(big.Int)
if block.BaseFee() != nil {
burn = new(big.Int).Mul(new(big.Int).SetUint64(block.GasUsed()), block.BaseFee())
}
return subsidy, uncles, burn
}

// Supply crawls the state snapshot at a given header and gatheres all the account
// balances to sum into the total Ether supply.
func Supply(header *types.Header, snaptree *snapshot.Tree) (*big.Int, error) {
accIt, err := snaptree.AccountIterator(header.Root, common.Hash{})
if err != nil {
return nil, err
}
defer accIt.Release()

log.Info("Ether supply counting started", "block", header.Number, "hash", header.Hash(), "root", header.Root)
var (
start = time.Now()
logged = time.Now()
accounts uint64
)
supply := big.NewInt(0)
for accIt.Next() {
account, err := snapshot.FullAccount(accIt.Account())
if err != nil {
return nil, err
}
supply.Add(supply, account.Balance)
accounts++
if time.Since(logged) > 8*time.Second {
log.Info("Ether supply counting in progress", "at", accIt.Hash(),
"accounts", accounts, "supply", supply, "elapsed", common.PrettyDuration(time.Since(start)))
logged = time.Now()
}
}
log.Info("Ether supply counting complete", "block", header.Number, "hash", header.Hash(), "root", header.Root,
"accounts", accounts, "supply", supply, "elapsed", common.PrettyDuration(time.Since(start)))

return supply, nil
}
36 changes: 36 additions & 0 deletions core/rawdb/accessors_metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package rawdb

import (
"encoding/json"
"math/big"
"time"

"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -186,3 +187,38 @@ func WriteTransitionStatus(db ethdb.KeyValueWriter, data []byte) {
log.Crit("Failed to store the eth2 transition status", "err", err)
}
}

// ReadIssuance retrieves the amount of Ether (in Wei) issued (or burnt) in a
// specific block. If unavailable for the specific block (non full synced node),
// nil will be returned.
func ReadIssuance(db ethdb.KeyValueReader, number uint64, hash common.Hash) *big.Int {
blob, _ := db.Get(issuanceKey(number, hash))
if len(blob) < 2 {
return nil
}
// Since negative big ints can't be encoded to bytes directly, use a dirty
// hack to store the negativift flag in the first byte (0 == positive,
// 1 == negative)
issuance := new(big.Int).SetBytes(blob[1:])
if blob[0] == 1 {
issuance.Neg(issuance)
}
return issuance
}

// WriteIssuance stores the amount of Ether (in wei) issued (or burnt) in a
// specific block.
func WriteIssuance(db ethdb.KeyValueWriter, number uint64, hash common.Hash, issuance *big.Int) {
// Since negative big ints can't be encoded to bytes directly, use a dirty
// hack to store the negativift flag in the first byte (0 == positive,
// 1 == negative)
blob := []byte{0}
if issuance.Sign() < 0 {
blob[0] = 1
}
blob = append(blob, issuance.Bytes()...)

if err := db.Put(issuanceKey(number, hash), blob); err != nil {
log.Crit("Failed to store block issuance", "err", err)
}
}
Loading