github.1485827954.workers.dev/ethereum/go-ethereum@v1.14.3/cmd/evm/t8n_test.go (about) 1 // Copyright 2021 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 "bufio" 21 "encoding/json" 22 "fmt" 23 "io" 24 "os" 25 "path/filepath" 26 "reflect" 27 "strings" 28 "testing" 29 30 "github.com/ethereum/go-ethereum/cmd/evm/internal/t8ntool" 31 "github.com/ethereum/go-ethereum/internal/cmdtest" 32 "github.com/ethereum/go-ethereum/internal/reexec" 33 ) 34 35 func TestMain(m *testing.M) { 36 // Run the app if we've been exec'd as "ethkey-test" in runEthkey. 37 reexec.Register("evm-test", func() { 38 if err := app.Run(os.Args); err != nil { 39 fmt.Fprintln(os.Stderr, err) 40 os.Exit(1) 41 } 42 os.Exit(0) 43 }) 44 // check if we have been reexec'd 45 if reexec.Init() { 46 return 47 } 48 os.Exit(m.Run()) 49 } 50 51 type testT8n struct { 52 *cmdtest.TestCmd 53 } 54 55 type t8nInput struct { 56 inAlloc string 57 inTxs string 58 inEnv string 59 stFork string 60 stReward string 61 } 62 63 func (args *t8nInput) get(base string) []string { 64 var out []string 65 if opt := args.inAlloc; opt != "" { 66 out = append(out, "--input.alloc") 67 out = append(out, fmt.Sprintf("%v/%v", base, opt)) 68 } 69 if opt := args.inTxs; opt != "" { 70 out = append(out, "--input.txs") 71 out = append(out, fmt.Sprintf("%v/%v", base, opt)) 72 } 73 if opt := args.inEnv; opt != "" { 74 out = append(out, "--input.env") 75 out = append(out, fmt.Sprintf("%v/%v", base, opt)) 76 } 77 if opt := args.stFork; opt != "" { 78 out = append(out, "--state.fork", opt) 79 } 80 if opt := args.stReward; opt != "" { 81 out = append(out, "--state.reward", opt) 82 } 83 return out 84 } 85 86 type t8nOutput struct { 87 alloc bool 88 result bool 89 body bool 90 } 91 92 func (args *t8nOutput) get() (out []string) { 93 if args.body { 94 out = append(out, "--output.body", "stdout") 95 } else { 96 out = append(out, "--output.body", "") // empty means ignore 97 } 98 if args.result { 99 out = append(out, "--output.result", "stdout") 100 } else { 101 out = append(out, "--output.result", "") 102 } 103 if args.alloc { 104 out = append(out, "--output.alloc", "stdout") 105 } else { 106 out = append(out, "--output.alloc", "") 107 } 108 return out 109 } 110 111 func TestT8n(t *testing.T) { 112 t.Parallel() 113 tt := new(testT8n) 114 tt.TestCmd = cmdtest.NewTestCmd(t, tt) 115 for i, tc := range []struct { 116 base string 117 input t8nInput 118 output t8nOutput 119 expExitCode int 120 expOut string 121 }{ 122 { // Test exit (3) on bad config 123 base: "./testdata/1", 124 input: t8nInput{ 125 "alloc.json", "txs.json", "env.json", "Frontier+1346", "", 126 }, 127 output: t8nOutput{alloc: true, result: true}, 128 expExitCode: 3, 129 }, 130 { 131 base: "./testdata/1", 132 input: t8nInput{ 133 "alloc.json", "txs.json", "env.json", "Byzantium", "", 134 }, 135 output: t8nOutput{alloc: true, result: true}, 136 expOut: "exp.json", 137 }, 138 { // blockhash test 139 base: "./testdata/3", 140 input: t8nInput{ 141 "alloc.json", "txs.json", "env.json", "Berlin", "", 142 }, 143 output: t8nOutput{alloc: true, result: true}, 144 expOut: "exp.json", 145 }, 146 { // missing blockhash test 147 base: "./testdata/4", 148 input: t8nInput{ 149 "alloc.json", "txs.json", "env.json", "Berlin", "", 150 }, 151 output: t8nOutput{alloc: true, result: true}, 152 expExitCode: 4, 153 }, 154 { // Uncle test 155 base: "./testdata/5", 156 input: t8nInput{ 157 "alloc.json", "txs.json", "env.json", "Byzantium", "0x80", 158 }, 159 output: t8nOutput{alloc: true, result: true}, 160 expOut: "exp.json", 161 }, 162 { // Sign json transactions 163 base: "./testdata/13", 164 input: t8nInput{ 165 "alloc.json", "txs.json", "env.json", "London", "", 166 }, 167 output: t8nOutput{body: true}, 168 expOut: "exp.json", 169 }, 170 { // Already signed transactions 171 base: "./testdata/13", 172 input: t8nInput{ 173 "alloc.json", "signed_txs.rlp", "env.json", "London", "", 174 }, 175 output: t8nOutput{result: true}, 176 expOut: "exp2.json", 177 }, 178 { // Difficulty calculation - no uncles 179 base: "./testdata/14", 180 input: t8nInput{ 181 "alloc.json", "txs.json", "env.json", "London", "", 182 }, 183 output: t8nOutput{result: true}, 184 expOut: "exp.json", 185 }, 186 { // Difficulty calculation - with uncles 187 base: "./testdata/14", 188 input: t8nInput{ 189 "alloc.json", "txs.json", "env.uncles.json", "London", "", 190 }, 191 output: t8nOutput{result: true}, 192 expOut: "exp2.json", 193 }, 194 { // Difficulty calculation - with ommers + Berlin 195 base: "./testdata/14", 196 input: t8nInput{ 197 "alloc.json", "txs.json", "env.uncles.json", "Berlin", "", 198 }, 199 output: t8nOutput{result: true}, 200 expOut: "exp_berlin.json", 201 }, 202 { // Difficulty calculation on arrow glacier 203 base: "./testdata/19", 204 input: t8nInput{ 205 "alloc.json", "txs.json", "env.json", "London", "", 206 }, 207 output: t8nOutput{result: true}, 208 expOut: "exp_london.json", 209 }, 210 { // Difficulty calculation on arrow glacier 211 base: "./testdata/19", 212 input: t8nInput{ 213 "alloc.json", "txs.json", "env.json", "ArrowGlacier", "", 214 }, 215 output: t8nOutput{result: true}, 216 expOut: "exp_arrowglacier.json", 217 }, 218 { // Difficulty calculation on gray glacier 219 base: "./testdata/19", 220 input: t8nInput{ 221 "alloc.json", "txs.json", "env.json", "GrayGlacier", "", 222 }, 223 output: t8nOutput{result: true}, 224 expOut: "exp_grayglacier.json", 225 }, 226 { // Sign unprotected (pre-EIP155) transaction 227 base: "./testdata/23", 228 input: t8nInput{ 229 "alloc.json", "txs.json", "env.json", "Berlin", "", 230 }, 231 output: t8nOutput{result: true}, 232 expOut: "exp.json", 233 }, 234 { // Test post-merge transition 235 base: "./testdata/24", 236 input: t8nInput{ 237 "alloc.json", "txs.json", "env.json", "Merge", "", 238 }, 239 output: t8nOutput{alloc: true, result: true}, 240 expOut: "exp.json", 241 }, 242 { // Test post-merge transition where input is missing random 243 base: "./testdata/24", 244 input: t8nInput{ 245 "alloc.json", "txs.json", "env-missingrandom.json", "Merge", "", 246 }, 247 output: t8nOutput{alloc: false, result: false}, 248 expExitCode: 3, 249 }, 250 { // Test base fee calculation 251 base: "./testdata/25", 252 input: t8nInput{ 253 "alloc.json", "txs.json", "env.json", "Merge", "", 254 }, 255 output: t8nOutput{alloc: true, result: true}, 256 expOut: "exp.json", 257 }, 258 { // Test withdrawals transition 259 base: "./testdata/26", 260 input: t8nInput{ 261 "alloc.json", "txs.json", "env.json", "Shanghai", "", 262 }, 263 output: t8nOutput{alloc: true, result: true}, 264 expOut: "exp.json", 265 }, 266 { // Cancun tests 267 base: "./testdata/28", 268 input: t8nInput{ 269 "alloc.json", "txs.rlp", "env.json", "Cancun", "", 270 }, 271 output: t8nOutput{alloc: true, result: true}, 272 expOut: "exp.json", 273 }, 274 { // More cancun tests 275 base: "./testdata/29", 276 input: t8nInput{ 277 "alloc.json", "txs.json", "env.json", "Cancun", "", 278 }, 279 output: t8nOutput{alloc: true, result: true}, 280 expOut: "exp.json", 281 }, 282 { // More cancun test, plus example of rlp-transaction that cannot be decoded properly 283 base: "./testdata/30", 284 input: t8nInput{ 285 "alloc.json", "txs_more.rlp", "env.json", "Cancun", "", 286 }, 287 output: t8nOutput{alloc: true, result: true}, 288 expOut: "exp.json", 289 }, 290 } { 291 args := []string{"t8n"} 292 args = append(args, tc.output.get()...) 293 args = append(args, tc.input.get(tc.base)...) 294 var qArgs []string // quoted args for debugging purposes 295 for _, arg := range args { 296 if len(arg) == 0 { 297 qArgs = append(qArgs, `""`) 298 } else { 299 qArgs = append(qArgs, arg) 300 } 301 } 302 tt.Logf("args: %v\n", strings.Join(qArgs, " ")) 303 tt.Run("evm-test", args...) 304 // Compare the expected output, if provided 305 if tc.expOut != "" { 306 file := fmt.Sprintf("%v/%v", tc.base, tc.expOut) 307 want, err := os.ReadFile(file) 308 if err != nil { 309 t.Fatalf("test %d: could not read expected output: %v", i, err) 310 } 311 have := tt.Output() 312 ok, err := cmpJson(have, want) 313 switch { 314 case err != nil: 315 t.Fatalf("test %d, file %v: json parsing failed: %v", i, file, err) 316 case !ok: 317 t.Fatalf("test %d, file %v: output wrong, have \n%v\nwant\n%v\n", i, file, string(have), string(want)) 318 } 319 } 320 tt.WaitExit() 321 if have, want := tt.ExitStatus(), tc.expExitCode; have != want { 322 t.Fatalf("test %d: wrong exit code, have %d, want %d", i, have, want) 323 } 324 } 325 } 326 327 func lineIterator(path string) func() (string, error) { 328 data, err := os.ReadFile(path) 329 if err != nil { 330 return func() (string, error) { return err.Error(), err } 331 } 332 scanner := bufio.NewScanner(strings.NewReader(string(data))) 333 return func() (string, error) { 334 if scanner.Scan() { 335 return scanner.Text(), nil 336 } 337 if err := scanner.Err(); err != nil { 338 return "", err 339 } 340 return "", io.EOF // scanner gobbles io.EOF, but we want it 341 } 342 } 343 344 // TestT8nTracing is a test that checks the tracing-output from t8n. 345 func TestT8nTracing(t *testing.T) { 346 t.Parallel() 347 tt := new(testT8n) 348 tt.TestCmd = cmdtest.NewTestCmd(t, tt) 349 for i, tc := range []struct { 350 base string 351 input t8nInput 352 expExitCode int 353 extraArgs []string 354 expectedTraces []string 355 }{ 356 { 357 base: "./testdata/31", 358 input: t8nInput{ 359 "alloc.json", "txs.json", "env.json", "Cancun", "", 360 }, 361 extraArgs: []string{"--trace"}, 362 expectedTraces: []string{"trace-0-0x88f5fbd1524731a81e49f637aa847543268a5aaf2a6b32a69d2c6d978c45dcfb.jsonl"}, 363 }, 364 { 365 base: "./testdata/31", 366 input: t8nInput{ 367 "alloc.json", "txs.json", "env.json", "Cancun", "", 368 }, 369 extraArgs: []string{"--trace.tracer", ` 370 { 371 result: function(){ 372 return "hello world" 373 }, 374 fault: function(){} 375 }`}, 376 expectedTraces: []string{"trace-0-0x88f5fbd1524731a81e49f637aa847543268a5aaf2a6b32a69d2c6d978c45dcfb.json"}, 377 }, 378 { 379 base: "./testdata/32", 380 input: t8nInput{ 381 "alloc.json", "txs.json", "env.json", "Merge", "", 382 }, 383 extraArgs: []string{"--trace", "--trace.callframes"}, 384 expectedTraces: []string{"trace-0-0x47806361c0fa084be3caa18afe8c48156747c01dbdfc1ee11b5aecdbe4fcf23e.jsonl"}, 385 }, 386 } { 387 args := []string{"t8n"} 388 args = append(args, tc.input.get(tc.base)...) 389 // Place the output somewhere we can find it 390 outdir := t.TempDir() 391 args = append(args, "--output.basedir", outdir) 392 args = append(args, tc.extraArgs...) 393 394 var qArgs []string // quoted args for debugging purposes 395 for _, arg := range args { 396 if len(arg) == 0 { 397 qArgs = append(qArgs, `""`) 398 } else { 399 qArgs = append(qArgs, arg) 400 } 401 } 402 tt.Logf("args: %v\n", strings.Join(qArgs, " ")) 403 tt.Run("evm-test", args...) 404 t.Log(string(tt.Output())) 405 406 // Compare the expected traces 407 for _, traceFile := range tc.expectedTraces { 408 haveFn := lineIterator(filepath.Join(outdir, traceFile)) 409 wantFn := lineIterator(filepath.Join(tc.base, traceFile)) 410 411 for line := 0; ; line++ { 412 want, wErr := wantFn() 413 have, hErr := haveFn() 414 if want != have { 415 t.Fatalf("test %d, trace %v, line %d\nwant: %v\nhave: %v\n", 416 i, traceFile, line, want, have) 417 } 418 if wErr != nil && hErr != nil { 419 break 420 } 421 if wErr != nil { 422 t.Fatal(wErr) 423 } 424 if hErr != nil { 425 t.Fatal(hErr) 426 } 427 t.Logf("%v\n", want) 428 } 429 } 430 if have, want := tt.ExitStatus(), tc.expExitCode; have != want { 431 t.Fatalf("test %d: wrong exit code, have %d, want %d", i, have, want) 432 } 433 } 434 } 435 436 type t9nInput struct { 437 inTxs string 438 stFork string 439 } 440 441 func (args *t9nInput) get(base string) []string { 442 var out []string 443 if opt := args.inTxs; opt != "" { 444 out = append(out, "--input.txs") 445 out = append(out, fmt.Sprintf("%v/%v", base, opt)) 446 } 447 if opt := args.stFork; opt != "" { 448 out = append(out, "--state.fork", opt) 449 } 450 return out 451 } 452 453 func TestT9n(t *testing.T) { 454 t.Parallel() 455 tt := new(testT8n) 456 tt.TestCmd = cmdtest.NewTestCmd(t, tt) 457 for i, tc := range []struct { 458 base string 459 input t9nInput 460 expExitCode int 461 expOut string 462 }{ 463 { // London txs on homestead 464 base: "./testdata/15", 465 input: t9nInput{ 466 inTxs: "signed_txs.rlp", 467 stFork: "Homestead", 468 }, 469 expOut: "exp.json", 470 }, 471 { // London txs on London 472 base: "./testdata/15", 473 input: t9nInput{ 474 inTxs: "signed_txs.rlp", 475 stFork: "London", 476 }, 477 expOut: "exp2.json", 478 }, 479 { // An RLP list (a blockheader really) 480 base: "./testdata/15", 481 input: t9nInput{ 482 inTxs: "blockheader.rlp", 483 stFork: "London", 484 }, 485 expOut: "exp3.json", 486 }, 487 { // Transactions with too low gas 488 base: "./testdata/16", 489 input: t9nInput{ 490 inTxs: "signed_txs.rlp", 491 stFork: "London", 492 }, 493 expOut: "exp.json", 494 }, 495 { // Transactions with value exceeding 256 bits 496 base: "./testdata/17", 497 input: t9nInput{ 498 inTxs: "signed_txs.rlp", 499 stFork: "London", 500 }, 501 expOut: "exp.json", 502 }, 503 { // Invalid RLP 504 base: "./testdata/18", 505 input: t9nInput{ 506 inTxs: "invalid.rlp", 507 stFork: "London", 508 }, 509 expExitCode: t8ntool.ErrorIO, 510 }, 511 } { 512 args := []string{"t9n"} 513 args = append(args, tc.input.get(tc.base)...) 514 515 tt.Run("evm-test", args...) 516 tt.Logf("args:\n go run . %v\n", strings.Join(args, " ")) 517 // Compare the expected output, if provided 518 if tc.expOut != "" { 519 want, err := os.ReadFile(fmt.Sprintf("%v/%v", tc.base, tc.expOut)) 520 if err != nil { 521 t.Fatalf("test %d: could not read expected output: %v", i, err) 522 } 523 have := tt.Output() 524 ok, err := cmpJson(have, want) 525 switch { 526 case err != nil: 527 t.Logf(string(have)) 528 t.Fatalf("test %d, json parsing failed: %v", i, err) 529 case !ok: 530 t.Fatalf("test %d: output wrong, have \n%v\nwant\n%v\n", i, string(have), string(want)) 531 } 532 } 533 tt.WaitExit() 534 if have, want := tt.ExitStatus(), tc.expExitCode; have != want { 535 t.Fatalf("test %d: wrong exit code, have %d, want %d", i, have, want) 536 } 537 } 538 } 539 540 type b11rInput struct { 541 inEnv string 542 inOmmersRlp string 543 inWithdrawals string 544 inTxsRlp string 545 inClique string 546 ethash bool 547 ethashMode string 548 ethashDir string 549 } 550 551 func (args *b11rInput) get(base string) []string { 552 var out []string 553 if opt := args.inEnv; opt != "" { 554 out = append(out, "--input.header") 555 out = append(out, fmt.Sprintf("%v/%v", base, opt)) 556 } 557 if opt := args.inOmmersRlp; opt != "" { 558 out = append(out, "--input.ommers") 559 out = append(out, fmt.Sprintf("%v/%v", base, opt)) 560 } 561 if opt := args.inWithdrawals; opt != "" { 562 out = append(out, "--input.withdrawals") 563 out = append(out, fmt.Sprintf("%v/%v", base, opt)) 564 } 565 if opt := args.inTxsRlp; opt != "" { 566 out = append(out, "--input.txs") 567 out = append(out, fmt.Sprintf("%v/%v", base, opt)) 568 } 569 if opt := args.inClique; opt != "" { 570 out = append(out, "--seal.clique") 571 out = append(out, fmt.Sprintf("%v/%v", base, opt)) 572 } 573 if args.ethash { 574 out = append(out, "--seal.ethash") 575 } 576 if opt := args.ethashMode; opt != "" { 577 out = append(out, "--seal.ethash.mode") 578 out = append(out, fmt.Sprintf("%v/%v", base, opt)) 579 } 580 if opt := args.ethashDir; opt != "" { 581 out = append(out, "--seal.ethash.dir") 582 out = append(out, fmt.Sprintf("%v/%v", base, opt)) 583 } 584 out = append(out, "--output.block") 585 out = append(out, "stdout") 586 return out 587 } 588 589 func TestB11r(t *testing.T) { 590 t.Parallel() 591 tt := new(testT8n) 592 tt.TestCmd = cmdtest.NewTestCmd(t, tt) 593 for i, tc := range []struct { 594 base string 595 input b11rInput 596 expExitCode int 597 expOut string 598 }{ 599 { // unsealed block 600 base: "./testdata/20", 601 input: b11rInput{ 602 inEnv: "header.json", 603 inOmmersRlp: "ommers.json", 604 inTxsRlp: "txs.rlp", 605 }, 606 expOut: "exp.json", 607 }, 608 { // ethash test seal 609 base: "./testdata/21", 610 input: b11rInput{ 611 inEnv: "header.json", 612 inOmmersRlp: "ommers.json", 613 inTxsRlp: "txs.rlp", 614 }, 615 expOut: "exp.json", 616 }, 617 { // clique test seal 618 base: "./testdata/21", 619 input: b11rInput{ 620 inEnv: "header.json", 621 inOmmersRlp: "ommers.json", 622 inTxsRlp: "txs.rlp", 623 inClique: "clique.json", 624 }, 625 expOut: "exp-clique.json", 626 }, 627 { // block with ommers 628 base: "./testdata/22", 629 input: b11rInput{ 630 inEnv: "header.json", 631 inOmmersRlp: "ommers.json", 632 inTxsRlp: "txs.rlp", 633 }, 634 expOut: "exp.json", 635 }, 636 { // block with withdrawals 637 base: "./testdata/27", 638 input: b11rInput{ 639 inEnv: "header.json", 640 inOmmersRlp: "ommers.json", 641 inWithdrawals: "withdrawals.json", 642 inTxsRlp: "txs.rlp", 643 }, 644 expOut: "exp.json", 645 }, 646 } { 647 args := []string{"b11r"} 648 args = append(args, tc.input.get(tc.base)...) 649 650 tt.Run("evm-test", args...) 651 tt.Logf("args:\n go run . %v\n", strings.Join(args, " ")) 652 // Compare the expected output, if provided 653 if tc.expOut != "" { 654 want, err := os.ReadFile(fmt.Sprintf("%v/%v", tc.base, tc.expOut)) 655 if err != nil { 656 t.Fatalf("test %d: could not read expected output: %v", i, err) 657 } 658 have := tt.Output() 659 ok, err := cmpJson(have, want) 660 switch { 661 case err != nil: 662 t.Logf(string(have)) 663 t.Fatalf("test %d, json parsing failed: %v", i, err) 664 case !ok: 665 t.Fatalf("test %d: output wrong, have \n%v\nwant\n%v\n", i, string(have), string(want)) 666 } 667 } 668 tt.WaitExit() 669 if have, want := tt.ExitStatus(), tc.expExitCode; have != want { 670 t.Fatalf("test %d: wrong exit code, have %d, want %d", i, have, want) 671 } 672 } 673 } 674 675 // cmpJson compares the JSON in two byte slices. 676 func cmpJson(a, b []byte) (bool, error) { 677 var j, j2 interface{} 678 if err := json.Unmarshal(a, &j); err != nil { 679 return false, err 680 } 681 if err := json.Unmarshal(b, &j2); err != nil { 682 return false, err 683 } 684 return reflect.DeepEqual(j2, j), nil 685 }