github.com/ethereum/go-ethereum@v1.16.1/eth/tracers/live/supply.go (about) 1 // Copyright 2024 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package live 18 19 import ( 20 "encoding/json" 21 "errors" 22 "fmt" 23 "math/big" 24 "path/filepath" 25 26 "github.com/ethereum/go-ethereum/common" 27 "github.com/ethereum/go-ethereum/common/hexutil" 28 "github.com/ethereum/go-ethereum/consensus/misc/eip4844" 29 "github.com/ethereum/go-ethereum/core/tracing" 30 "github.com/ethereum/go-ethereum/core/types" 31 "github.com/ethereum/go-ethereum/core/vm" 32 "github.com/ethereum/go-ethereum/eth/tracers" 33 "github.com/ethereum/go-ethereum/log" 34 "github.com/ethereum/go-ethereum/params" 35 "gopkg.in/natefinch/lumberjack.v2" 36 ) 37 38 func init() { 39 tracers.LiveDirectory.Register("supply", newSupplyTracer) 40 } 41 42 type supplyInfoIssuance struct { 43 GenesisAlloc *big.Int `json:"genesisAlloc,omitempty"` 44 Reward *big.Int `json:"reward,omitempty"` 45 Withdrawals *big.Int `json:"withdrawals,omitempty"` 46 } 47 48 //go:generate go run github.com/fjl/gencodec -type supplyInfoIssuance -field-override supplyInfoIssuanceMarshaling -out gen_supplyinfoissuance.go 49 type supplyInfoIssuanceMarshaling struct { 50 GenesisAlloc *hexutil.Big 51 Reward *hexutil.Big 52 Withdrawals *hexutil.Big 53 } 54 55 type supplyInfoBurn struct { 56 EIP1559 *big.Int `json:"1559,omitempty"` 57 Blob *big.Int `json:"blob,omitempty"` 58 Misc *big.Int `json:"misc,omitempty"` 59 } 60 61 //go:generate go run github.com/fjl/gencodec -type supplyInfoBurn -field-override supplyInfoBurnMarshaling -out gen_supplyinfoburn.go 62 type supplyInfoBurnMarshaling struct { 63 EIP1559 *hexutil.Big 64 Blob *hexutil.Big 65 Misc *hexutil.Big 66 } 67 68 type supplyInfo struct { 69 Issuance *supplyInfoIssuance `json:"issuance,omitempty"` 70 Burn *supplyInfoBurn `json:"burn,omitempty"` 71 72 // Block info 73 Number uint64 `json:"blockNumber"` 74 Hash common.Hash `json:"hash"` 75 ParentHash common.Hash `json:"parentHash"` 76 } 77 78 type supplyTxCallstack struct { 79 calls []supplyTxCallstack 80 burn *big.Int 81 } 82 83 type supplyTracer struct { 84 delta supplyInfo 85 txCallstack []supplyTxCallstack // Callstack for current transaction 86 logger *lumberjack.Logger 87 chainConfig *params.ChainConfig 88 } 89 90 type supplyTracerConfig struct { 91 Path string `json:"path"` // Path to the directory where the tracer logs will be stored 92 MaxSize int `json:"maxSize"` // MaxSize is the maximum size in megabytes of the tracer log file before it gets rotated. It defaults to 100 megabytes. 93 } 94 95 func newSupplyTracer(cfg json.RawMessage) (*tracing.Hooks, error) { 96 var config supplyTracerConfig 97 if err := json.Unmarshal(cfg, &config); err != nil { 98 return nil, fmt.Errorf("failed to parse config: %v", err) 99 } 100 if config.Path == "" { 101 return nil, errors.New("supply tracer output path is required") 102 } 103 104 // Store traces in a rotating file 105 logger := &lumberjack.Logger{ 106 Filename: filepath.Join(config.Path, "supply.jsonl"), 107 } 108 if config.MaxSize > 0 { 109 logger.MaxSize = config.MaxSize 110 } 111 112 t := &supplyTracer{ 113 delta: newSupplyInfo(), 114 logger: logger, 115 } 116 return &tracing.Hooks{ 117 OnBlockchainInit: t.onBlockchainInit, 118 OnBlockStart: t.onBlockStart, 119 OnBlockEnd: t.onBlockEnd, 120 OnGenesisBlock: t.onGenesisBlock, 121 OnTxStart: t.onTxStart, 122 OnBalanceChange: t.onBalanceChange, 123 OnEnter: t.onEnter, 124 OnExit: t.onExit, 125 OnClose: t.onClose, 126 }, nil 127 } 128 129 func newSupplyInfo() supplyInfo { 130 return supplyInfo{ 131 Issuance: &supplyInfoIssuance{ 132 GenesisAlloc: big.NewInt(0), 133 Reward: big.NewInt(0), 134 Withdrawals: big.NewInt(0), 135 }, 136 Burn: &supplyInfoBurn{ 137 EIP1559: big.NewInt(0), 138 Blob: big.NewInt(0), 139 Misc: big.NewInt(0), 140 }, 141 142 Number: 0, 143 Hash: common.Hash{}, 144 ParentHash: common.Hash{}, 145 } 146 } 147 148 func (s *supplyTracer) resetDelta() { 149 s.delta = newSupplyInfo() 150 } 151 152 func (s *supplyTracer) onBlockchainInit(chainConfig *params.ChainConfig) { 153 s.chainConfig = chainConfig 154 } 155 156 func (s *supplyTracer) onBlockStart(ev tracing.BlockEvent) { 157 s.resetDelta() 158 159 s.delta.Number = ev.Block.NumberU64() 160 s.delta.Hash = ev.Block.Hash() 161 s.delta.ParentHash = ev.Block.ParentHash() 162 163 // Calculate Burn for this block 164 if ev.Block.BaseFee() != nil { 165 burn := new(big.Int).Mul(new(big.Int).SetUint64(ev.Block.GasUsed()), ev.Block.BaseFee()) 166 s.delta.Burn.EIP1559 = burn 167 } 168 // Blob burnt gas 169 if blobGas := ev.Block.BlobGasUsed(); blobGas != nil && *blobGas > 0 && ev.Block.ExcessBlobGas() != nil { 170 var ( 171 baseFee = eip4844.CalcBlobFee(s.chainConfig, ev.Block.Header()) 172 burn = new(big.Int).Mul(new(big.Int).SetUint64(*blobGas), baseFee) 173 ) 174 s.delta.Burn.Blob = burn 175 } 176 } 177 178 func (s *supplyTracer) onBlockEnd(err error) { 179 s.write(s.delta) 180 } 181 182 func (s *supplyTracer) onGenesisBlock(b *types.Block, alloc types.GenesisAlloc) { 183 s.resetDelta() 184 185 s.delta.Number = b.NumberU64() 186 s.delta.Hash = b.Hash() 187 s.delta.ParentHash = b.ParentHash() 188 189 // Initialize supply with total allocation in genesis block 190 for _, account := range alloc { 191 s.delta.Issuance.GenesisAlloc.Add(s.delta.Issuance.GenesisAlloc, account.Balance) 192 } 193 194 s.write(s.delta) 195 } 196 197 func (s *supplyTracer) onBalanceChange(a common.Address, prevBalance, newBalance *big.Int, reason tracing.BalanceChangeReason) { 198 diff := new(big.Int).Sub(newBalance, prevBalance) 199 200 // NOTE: don't handle "BalanceIncreaseGenesisBalance" because it is handled in OnGenesisBlock 201 switch reason { 202 case tracing.BalanceIncreaseRewardMineUncle: 203 case tracing.BalanceIncreaseRewardMineBlock: 204 s.delta.Issuance.Reward.Add(s.delta.Issuance.Reward, diff) 205 case tracing.BalanceIncreaseWithdrawal: 206 s.delta.Issuance.Withdrawals.Add(s.delta.Issuance.Withdrawals, diff) 207 case tracing.BalanceDecreaseSelfdestructBurn: 208 // BalanceDecreaseSelfdestructBurn is non-reversible as it happens 209 // at the end of the transaction. 210 s.delta.Burn.Misc.Sub(s.delta.Burn.Misc, diff) 211 default: 212 return 213 } 214 } 215 216 func (s *supplyTracer) onTxStart(vm *tracing.VMContext, tx *types.Transaction, from common.Address) { 217 s.txCallstack = make([]supplyTxCallstack, 0, 1) 218 } 219 220 // internalTxsHandler handles internal transactions burned amount 221 func (s *supplyTracer) internalTxsHandler(call *supplyTxCallstack) { 222 // Handle Burned amount 223 if call.burn != nil { 224 s.delta.Burn.Misc.Add(s.delta.Burn.Misc, call.burn) 225 } 226 227 // Recursively handle internal calls 228 for _, call := range call.calls { 229 callCopy := call 230 s.internalTxsHandler(&callCopy) 231 } 232 } 233 234 func (s *supplyTracer) onEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { 235 call := supplyTxCallstack{ 236 calls: make([]supplyTxCallstack, 0), 237 } 238 239 // This is a special case of burned amount which has to be handled here 240 // which happens when type == selfdestruct and from == to. 241 if vm.OpCode(typ) == vm.SELFDESTRUCT && from == to && value.Cmp(common.Big0) == 1 { 242 call.burn = value 243 } 244 245 // Append call to the callstack, so we can fill the details in CaptureExit 246 s.txCallstack = append(s.txCallstack, call) 247 } 248 249 func (s *supplyTracer) onExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { 250 if depth == 0 { 251 // No need to handle Burned amount if transaction is reverted 252 if !reverted { 253 s.internalTxsHandler(&s.txCallstack[0]) 254 } 255 return 256 } 257 258 size := len(s.txCallstack) 259 if size <= 1 { 260 return 261 } 262 // Pop call 263 call := s.txCallstack[size-1] 264 s.txCallstack = s.txCallstack[:size-1] 265 size -= 1 266 267 // In case of a revert, we can drop the call and all its subcalls. 268 // Caution, that this has to happen after popping the call from the stack. 269 if reverted { 270 return 271 } 272 s.txCallstack[size-1].calls = append(s.txCallstack[size-1].calls, call) 273 } 274 275 func (s *supplyTracer) onClose() { 276 if err := s.logger.Close(); err != nil { 277 log.Warn("failed to close supply tracer log file", "error", err) 278 } 279 } 280 281 func (s *supplyTracer) write(data any) { 282 supply, ok := data.(supplyInfo) 283 if !ok { 284 log.Warn("failed to cast supply tracer data on write to log file") 285 return 286 } 287 288 // Remove empty fields 289 if supply.Issuance.GenesisAlloc.Sign() == 0 { 290 supply.Issuance.GenesisAlloc = nil 291 } 292 293 if supply.Issuance.Reward.Sign() == 0 { 294 supply.Issuance.Reward = nil 295 } 296 297 if supply.Issuance.Withdrawals.Sign() == 0 { 298 supply.Issuance.Withdrawals = nil 299 } 300 301 if supply.Issuance.GenesisAlloc == nil && supply.Issuance.Reward == nil && supply.Issuance.Withdrawals == nil { 302 supply.Issuance = nil 303 } 304 305 if supply.Burn.EIP1559.Sign() == 0 { 306 supply.Burn.EIP1559 = nil 307 } 308 309 if supply.Burn.Blob.Sign() == 0 { 310 supply.Burn.Blob = nil 311 } 312 313 if supply.Burn.Misc.Sign() == 0 { 314 supply.Burn.Misc = nil 315 } 316 317 if supply.Burn.EIP1559 == nil && supply.Burn.Blob == nil && supply.Burn.Misc == nil { 318 supply.Burn = nil 319 } 320 321 out, _ := json.Marshal(supply) 322 if _, err := s.logger.Write(out); err != nil { 323 log.Warn("failed to write to supply tracer log file", "error", err) 324 } 325 if _, err := s.logger.Write([]byte{'\n'}); err != nil { 326 log.Warn("failed to write to supply tracer log file", "error", err) 327 } 328 }