github.com/ethereum/go-ethereum@v1.14.4-0.20240516095835-473ee8fc07a3/cmd/era/main.go (about) 1 // Copyright 2023 The go-ethereum Authors 2 // This file is part of go-ethereum. 3 // 4 // go-ethereum is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU 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 // go-ethereum 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 General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. 16 17 package main 18 19 import ( 20 "encoding/json" 21 "errors" 22 "fmt" 23 "math/big" 24 "os" 25 "path/filepath" 26 "strconv" 27 "strings" 28 "time" 29 30 "github.com/ethereum/go-ethereum/common" 31 "github.com/ethereum/go-ethereum/core/types" 32 "github.com/ethereum/go-ethereum/internal/era" 33 "github.com/ethereum/go-ethereum/internal/ethapi" 34 "github.com/ethereum/go-ethereum/internal/flags" 35 "github.com/ethereum/go-ethereum/params" 36 "github.com/ethereum/go-ethereum/trie" 37 "github.com/urfave/cli/v2" 38 ) 39 40 var app = flags.NewApp("go-ethereum era tool") 41 42 var ( 43 dirFlag = &cli.StringFlag{ 44 Name: "dir", 45 Usage: "directory storing all relevant era1 files", 46 Value: "eras", 47 } 48 networkFlag = &cli.StringFlag{ 49 Name: "network", 50 Usage: "network name associated with era1 files", 51 Value: "mainnet", 52 } 53 eraSizeFlag = &cli.IntFlag{ 54 Name: "size", 55 Usage: "number of blocks per era", 56 Value: era.MaxEra1Size, 57 } 58 txsFlag = &cli.BoolFlag{ 59 Name: "txs", 60 Usage: "print full transaction values", 61 } 62 ) 63 64 var ( 65 blockCommand = &cli.Command{ 66 Name: "block", 67 Usage: "get block data", 68 ArgsUsage: "<number>", 69 Action: block, 70 Flags: []cli.Flag{ 71 txsFlag, 72 }, 73 } 74 infoCommand = &cli.Command{ 75 Name: "info", 76 ArgsUsage: "<epoch>", 77 Usage: "get epoch information", 78 Action: info, 79 } 80 verifyCommand = &cli.Command{ 81 Name: "verify", 82 ArgsUsage: "<expected>", 83 Usage: "verifies each era1 against expected accumulator root", 84 Action: verify, 85 } 86 ) 87 88 func init() { 89 app.Commands = []*cli.Command{ 90 blockCommand, 91 infoCommand, 92 verifyCommand, 93 } 94 app.Flags = []cli.Flag{ 95 dirFlag, 96 networkFlag, 97 eraSizeFlag, 98 } 99 } 100 101 func main() { 102 if err := app.Run(os.Args); err != nil { 103 fmt.Fprintf(os.Stderr, "%v\n", err) 104 os.Exit(1) 105 } 106 } 107 108 // block prints the specified block from an era1 store. 109 func block(ctx *cli.Context) error { 110 num, err := strconv.ParseUint(ctx.Args().First(), 10, 64) 111 if err != nil { 112 return fmt.Errorf("invalid block number: %w", err) 113 } 114 e, err := open(ctx, num/uint64(ctx.Int(eraSizeFlag.Name))) 115 if err != nil { 116 return fmt.Errorf("error opening era1: %w", err) 117 } 118 defer e.Close() 119 // Read block with number. 120 block, err := e.GetBlockByNumber(num) 121 if err != nil { 122 return fmt.Errorf("error reading block %d: %w", num, err) 123 } 124 // Convert block to JSON and print. 125 val := ethapi.RPCMarshalBlock(block, ctx.Bool(txsFlag.Name), ctx.Bool(txsFlag.Name), params.MainnetChainConfig) 126 b, err := json.MarshalIndent(val, "", " ") 127 if err != nil { 128 return fmt.Errorf("error marshaling json: %w", err) 129 } 130 fmt.Println(string(b)) 131 return nil 132 } 133 134 // info prints some high-level information about the era1 file. 135 func info(ctx *cli.Context) error { 136 epoch, err := strconv.ParseUint(ctx.Args().First(), 10, 64) 137 if err != nil { 138 return fmt.Errorf("invalid epoch number: %w", err) 139 } 140 e, err := open(ctx, epoch) 141 if err != nil { 142 return err 143 } 144 defer e.Close() 145 acc, err := e.Accumulator() 146 if err != nil { 147 return fmt.Errorf("error reading accumulator: %w", err) 148 } 149 td, err := e.InitialTD() 150 if err != nil { 151 return fmt.Errorf("error reading total difficulty: %w", err) 152 } 153 info := struct { 154 Accumulator common.Hash `json:"accumulator"` 155 TotalDifficulty *big.Int `json:"totalDifficulty"` 156 StartBlock uint64 `json:"startBlock"` 157 Count uint64 `json:"count"` 158 }{ 159 acc, td, e.Start(), e.Count(), 160 } 161 b, _ := json.MarshalIndent(info, "", " ") 162 fmt.Println(string(b)) 163 return nil 164 } 165 166 // open opens an era1 file at a certain epoch. 167 func open(ctx *cli.Context, epoch uint64) (*era.Era, error) { 168 var ( 169 dir = ctx.String(dirFlag.Name) 170 network = ctx.String(networkFlag.Name) 171 ) 172 entries, err := era.ReadDir(dir, network) 173 if err != nil { 174 return nil, fmt.Errorf("error reading era dir: %w", err) 175 } 176 if epoch >= uint64(len(entries)) { 177 return nil, fmt.Errorf("epoch out-of-bounds: last %d, want %d", len(entries)-1, epoch) 178 } 179 return era.Open(filepath.Join(dir, entries[epoch])) 180 } 181 182 // verify checks each era1 file in a directory to ensure it is well-formed and 183 // that the accumulator matches the expected value. 184 func verify(ctx *cli.Context) error { 185 if ctx.Args().Len() != 1 { 186 return errors.New("missing accumulators file") 187 } 188 189 roots, err := readHashes(ctx.Args().First()) 190 if err != nil { 191 return fmt.Errorf("unable to read expected roots file: %w", err) 192 } 193 194 var ( 195 dir = ctx.String(dirFlag.Name) 196 network = ctx.String(networkFlag.Name) 197 start = time.Now() 198 reported = time.Now() 199 ) 200 201 entries, err := era.ReadDir(dir, network) 202 if err != nil { 203 return fmt.Errorf("error reading %s: %w", dir, err) 204 } 205 206 if len(entries) != len(roots) { 207 return errors.New("number of era1 files should match the number of accumulator hashes") 208 } 209 210 // Verify each epoch matches the expected root. 211 for i, want := range roots { 212 // Wrap in function so defers don't stack. 213 err := func() error { 214 name := entries[i] 215 e, err := era.Open(filepath.Join(dir, name)) 216 if err != nil { 217 return fmt.Errorf("error opening era1 file %s: %w", name, err) 218 } 219 defer e.Close() 220 // Read accumulator and check against expected. 221 if got, err := e.Accumulator(); err != nil { 222 return fmt.Errorf("error retrieving accumulator for %s: %w", name, err) 223 } else if got != want { 224 return fmt.Errorf("invalid root %s: got %s, want %s", name, got, want) 225 } 226 // Recompute accumulator. 227 if err := checkAccumulator(e); err != nil { 228 return fmt.Errorf("error verify era1 file %s: %w", name, err) 229 } 230 // Give the user some feedback that something is happening. 231 if time.Since(reported) >= 8*time.Second { 232 fmt.Printf("Verifying Era1 files \t\t verified=%d,\t elapsed=%s\n", i, common.PrettyDuration(time.Since(start))) 233 reported = time.Now() 234 } 235 return nil 236 }() 237 if err != nil { 238 return err 239 } 240 } 241 242 return nil 243 } 244 245 // checkAccumulator verifies the accumulator matches the data in the Era. 246 func checkAccumulator(e *era.Era) error { 247 var ( 248 err error 249 want common.Hash 250 td *big.Int 251 tds = make([]*big.Int, 0) 252 hashes = make([]common.Hash, 0) 253 ) 254 if want, err = e.Accumulator(); err != nil { 255 return fmt.Errorf("error reading accumulator: %w", err) 256 } 257 if td, err = e.InitialTD(); err != nil { 258 return fmt.Errorf("error reading total difficulty: %w", err) 259 } 260 it, err := era.NewIterator(e) 261 if err != nil { 262 return fmt.Errorf("error making era iterator: %w", err) 263 } 264 // To fully verify an era the following attributes must be checked: 265 // 1) the block index is constructed correctly 266 // 2) the tx root matches the value in the block 267 // 3) the receipts root matches the value in the block 268 // 4) the starting total difficulty value is correct 269 // 5) the accumulator is correct by recomputing it locally, which verifies 270 // the blocks are all correct (via hash) 271 // 272 // The attributes 1), 2), and 3) are checked for each block. 4) and 5) require 273 // accumulation across the entire set and are verified at the end. 274 for it.Next() { 275 // 1) next() walks the block index, so we're able to implicitly verify it. 276 if it.Error() != nil { 277 return fmt.Errorf("error reading block %d: %w", it.Number(), err) 278 } 279 block, receipts, err := it.BlockAndReceipts() 280 if it.Error() != nil { 281 return fmt.Errorf("error reading block %d: %w", it.Number(), err) 282 } 283 // 2) recompute tx root and verify against header. 284 tr := types.DeriveSha(block.Transactions(), trie.NewStackTrie(nil)) 285 if tr != block.TxHash() { 286 return fmt.Errorf("tx root in block %d mismatch: want %s, got %s", block.NumberU64(), block.TxHash(), tr) 287 } 288 // 3) recompute receipt root and check value against block. 289 rr := types.DeriveSha(receipts, trie.NewStackTrie(nil)) 290 if rr != block.ReceiptHash() { 291 return fmt.Errorf("receipt root in block %d mismatch: want %s, got %s", block.NumberU64(), block.ReceiptHash(), rr) 292 } 293 hashes = append(hashes, block.Hash()) 294 td.Add(td, block.Difficulty()) 295 tds = append(tds, new(big.Int).Set(td)) 296 } 297 // 4+5) Verify accumulator and total difficulty. 298 got, err := era.ComputeAccumulator(hashes, tds) 299 if err != nil { 300 return fmt.Errorf("error computing accumulator: %w", err) 301 } 302 if got != want { 303 return fmt.Errorf("expected accumulator root does not match calculated: got %s, want %s", got, want) 304 } 305 return nil 306 } 307 308 // readHashes reads a file of newline-delimited hashes. 309 func readHashes(f string) ([]common.Hash, error) { 310 b, err := os.ReadFile(f) 311 if err != nil { 312 return nil, errors.New("unable to open accumulators file") 313 } 314 s := strings.Split(string(b), "\n") 315 // Remove empty last element, if present. 316 if s[len(s)-1] == "" { 317 s = s[:len(s)-1] 318 } 319 // Convert to hashes. 320 r := make([]common.Hash, len(s)) 321 for i := range s { 322 r[i] = common.HexToHash(s[i]) 323 } 324 return r, nil 325 }