calltrace_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. // Copyright 2021 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. package tracetest
  17. import (
  18. "encoding/json"
  19. "math/big"
  20. "os"
  21. "path/filepath"
  22. "reflect"
  23. "strings"
  24. "testing"
  25. "unicode"
  26. "github.com/ethereum/go-ethereum/common"
  27. "github.com/ethereum/go-ethereum/common/hexutil"
  28. "github.com/ethereum/go-ethereum/common/math"
  29. "github.com/ethereum/go-ethereum/core"
  30. "github.com/ethereum/go-ethereum/core/rawdb"
  31. "github.com/ethereum/go-ethereum/core/types"
  32. "github.com/ethereum/go-ethereum/core/vm"
  33. "github.com/ethereum/go-ethereum/crypto"
  34. "github.com/ethereum/go-ethereum/eth/tracers"
  35. "github.com/ethereum/go-ethereum/params"
  36. "github.com/ethereum/go-ethereum/rlp"
  37. "github.com/ethereum/go-ethereum/tests"
  38. // Force-load native and js packages, to trigger registration
  39. _ "github.com/ethereum/go-ethereum/eth/tracers/js"
  40. _ "github.com/ethereum/go-ethereum/eth/tracers/native"
  41. )
  42. // To generate a new callTracer test, copy paste the makeTest method below into
  43. // a Geth console and call it with a transaction hash you which to export.
  44. /*
  45. // makeTest generates a callTracer test by running a prestate reassembled and a
  46. // call trace run, assembling all the gathered information into a test case.
  47. var makeTest = function(tx, rewind) {
  48. // Generate the genesis block from the block, transaction and prestate data
  49. var block = eth.getBlock(eth.getTransaction(tx).blockHash);
  50. var genesis = eth.getBlock(block.parentHash);
  51. delete genesis.gasUsed;
  52. delete genesis.logsBloom;
  53. delete genesis.parentHash;
  54. delete genesis.receiptsRoot;
  55. delete genesis.sha3Uncles;
  56. delete genesis.size;
  57. delete genesis.transactions;
  58. delete genesis.transactionsRoot;
  59. delete genesis.uncles;
  60. genesis.gasLimit = genesis.gasLimit.toString();
  61. genesis.number = genesis.number.toString();
  62. genesis.timestamp = genesis.timestamp.toString();
  63. genesis.alloc = debug.traceTransaction(tx, {tracer: "prestateTracer", rewind: rewind});
  64. for (var key in genesis.alloc) {
  65. genesis.alloc[key].nonce = genesis.alloc[key].nonce.toString();
  66. }
  67. genesis.config = admin.nodeInfo.protocols.eth.config;
  68. // Generate the call trace and produce the test input
  69. var result = debug.traceTransaction(tx, {tracer: "callTracer", rewind: rewind});
  70. delete result.time;
  71. console.log(JSON.stringify({
  72. genesis: genesis,
  73. context: {
  74. number: block.number.toString(),
  75. difficulty: block.difficulty,
  76. timestamp: block.timestamp.toString(),
  77. gasLimit: block.gasLimit.toString(),
  78. miner: block.miner,
  79. },
  80. input: eth.getRawTransaction(tx),
  81. result: result,
  82. }, null, 2));
  83. }
  84. */
  85. type callContext struct {
  86. Number math.HexOrDecimal64 `json:"number"`
  87. Difficulty *math.HexOrDecimal256 `json:"difficulty"`
  88. Time math.HexOrDecimal64 `json:"timestamp"`
  89. GasLimit math.HexOrDecimal64 `json:"gasLimit"`
  90. Miner common.Address `json:"miner"`
  91. }
  92. // callTrace is the result of a callTracer run.
  93. type callTrace struct {
  94. Type string `json:"type"`
  95. From common.Address `json:"from"`
  96. To common.Address `json:"to"`
  97. Input hexutil.Bytes `json:"input"`
  98. Output hexutil.Bytes `json:"output"`
  99. Gas *hexutil.Uint64 `json:"gas,omitempty"`
  100. GasUsed *hexutil.Uint64 `json:"gasUsed,omitempty"`
  101. Value *hexutil.Big `json:"value,omitempty"`
  102. Error string `json:"error,omitempty"`
  103. Calls []callTrace `json:"calls,omitempty"`
  104. }
  105. // callTracerTest defines a single test to check the call tracer against.
  106. type callTracerTest struct {
  107. Genesis *core.Genesis `json:"genesis"`
  108. Context *callContext `json:"context"`
  109. Input string `json:"input"`
  110. TracerConfig json.RawMessage `json:"tracerConfig"`
  111. Result *callTrace `json:"result"`
  112. }
  113. // Iterates over all the input-output datasets in the tracer test harness and
  114. // runs the JavaScript tracers against them.
  115. func TestCallTracerLegacy(t *testing.T) {
  116. testCallTracer("callTracerLegacy", "call_tracer_legacy", t)
  117. }
  118. func TestCallTracerNative(t *testing.T) {
  119. testCallTracer("callTracer", "call_tracer", t)
  120. }
  121. func testCallTracer(tracerName string, dirPath string, t *testing.T) {
  122. files, err := os.ReadDir(filepath.Join("testdata", dirPath))
  123. if err != nil {
  124. t.Fatalf("failed to retrieve tracer test suite: %v", err)
  125. }
  126. for _, file := range files {
  127. if !strings.HasSuffix(file.Name(), ".json") {
  128. continue
  129. }
  130. file := file // capture range variable
  131. t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) {
  132. t.Parallel()
  133. var (
  134. test = new(callTracerTest)
  135. tx = new(types.Transaction)
  136. )
  137. // Call tracer test found, read if from disk
  138. if blob, err := os.ReadFile(filepath.Join("testdata", dirPath, file.Name())); err != nil {
  139. t.Fatalf("failed to read testcase: %v", err)
  140. } else if err := json.Unmarshal(blob, test); err != nil {
  141. t.Fatalf("failed to parse testcase: %v", err)
  142. }
  143. if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {
  144. t.Fatalf("failed to parse testcase input: %v", err)
  145. }
  146. // Configure a blockchain with the given prestate
  147. var (
  148. signer = types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)))
  149. origin, _ = signer.Sender(tx)
  150. txContext = vm.TxContext{
  151. Origin: origin,
  152. GasPrice: tx.GasPrice(),
  153. }
  154. context = vm.BlockContext{
  155. CanTransfer: core.CanTransfer,
  156. Transfer: core.Transfer,
  157. Coinbase: test.Context.Miner,
  158. BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)),
  159. Time: new(big.Int).SetUint64(uint64(test.Context.Time)),
  160. Difficulty: (*big.Int)(test.Context.Difficulty),
  161. GasLimit: uint64(test.Context.GasLimit),
  162. }
  163. _, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
  164. )
  165. tracer, err := tracers.New(tracerName, new(tracers.Context), test.TracerConfig)
  166. if err != nil {
  167. t.Fatalf("failed to create call tracer: %v", err)
  168. }
  169. evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer})
  170. msg, err := tx.AsMessage(signer, nil)
  171. if err != nil {
  172. t.Fatalf("failed to prepare transaction for tracing: %v", err)
  173. }
  174. st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
  175. if _, err = st.TransitionDb(); err != nil {
  176. t.Fatalf("failed to execute transaction: %v", err)
  177. }
  178. // Retrieve the trace result and compare against the etalon
  179. res, err := tracer.GetResult()
  180. if err != nil {
  181. t.Fatalf("failed to retrieve trace result: %v", err)
  182. }
  183. ret := new(callTrace)
  184. if err := json.Unmarshal(res, ret); err != nil {
  185. t.Fatalf("failed to unmarshal trace result: %v", err)
  186. }
  187. if !jsonEqual(ret, test.Result) {
  188. // uncomment this for easier debugging
  189. //have, _ := json.MarshalIndent(ret, "", " ")
  190. //want, _ := json.MarshalIndent(test.Result, "", " ")
  191. //t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", string(have), string(want))
  192. t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, test.Result)
  193. }
  194. })
  195. }
  196. }
  197. // jsonEqual is similar to reflect.DeepEqual, but does a 'bounce' via json prior to
  198. // comparison
  199. func jsonEqual(x, y interface{}) bool {
  200. xTrace := new(callTrace)
  201. yTrace := new(callTrace)
  202. if xj, err := json.Marshal(x); err == nil {
  203. json.Unmarshal(xj, xTrace)
  204. } else {
  205. return false
  206. }
  207. if yj, err := json.Marshal(y); err == nil {
  208. json.Unmarshal(yj, yTrace)
  209. } else {
  210. return false
  211. }
  212. return reflect.DeepEqual(xTrace, yTrace)
  213. }
  214. // camel converts a snake cased input string into a camel cased output.
  215. func camel(str string) string {
  216. pieces := strings.Split(str, "_")
  217. for i := 1; i < len(pieces); i++ {
  218. pieces[i] = string(unicode.ToUpper(rune(pieces[i][0]))) + pieces[i][1:]
  219. }
  220. return strings.Join(pieces, "")
  221. }
  222. func BenchmarkTracers(b *testing.B) {
  223. files, err := os.ReadDir(filepath.Join("testdata", "call_tracer"))
  224. if err != nil {
  225. b.Fatalf("failed to retrieve tracer test suite: %v", err)
  226. }
  227. for _, file := range files {
  228. if !strings.HasSuffix(file.Name(), ".json") {
  229. continue
  230. }
  231. file := file // capture range variable
  232. b.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(b *testing.B) {
  233. blob, err := os.ReadFile(filepath.Join("testdata", "call_tracer", file.Name()))
  234. if err != nil {
  235. b.Fatalf("failed to read testcase: %v", err)
  236. }
  237. test := new(callTracerTest)
  238. if err := json.Unmarshal(blob, test); err != nil {
  239. b.Fatalf("failed to parse testcase: %v", err)
  240. }
  241. benchTracer("callTracer", test, b)
  242. })
  243. }
  244. }
  245. func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
  246. // Configure a blockchain with the given prestate
  247. tx := new(types.Transaction)
  248. if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {
  249. b.Fatalf("failed to parse testcase input: %v", err)
  250. }
  251. signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)))
  252. msg, err := tx.AsMessage(signer, nil)
  253. if err != nil {
  254. b.Fatalf("failed to prepare transaction for tracing: %v", err)
  255. }
  256. origin, _ := signer.Sender(tx)
  257. txContext := vm.TxContext{
  258. Origin: origin,
  259. GasPrice: tx.GasPrice(),
  260. }
  261. context := vm.BlockContext{
  262. CanTransfer: core.CanTransfer,
  263. Transfer: core.Transfer,
  264. Coinbase: test.Context.Miner,
  265. BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)),
  266. Time: new(big.Int).SetUint64(uint64(test.Context.Time)),
  267. Difficulty: (*big.Int)(test.Context.Difficulty),
  268. GasLimit: uint64(test.Context.GasLimit),
  269. }
  270. _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
  271. b.ReportAllocs()
  272. b.ResetTimer()
  273. for i := 0; i < b.N; i++ {
  274. tracer, err := tracers.New(tracerName, new(tracers.Context), nil)
  275. if err != nil {
  276. b.Fatalf("failed to create call tracer: %v", err)
  277. }
  278. evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer})
  279. snap := statedb.Snapshot()
  280. st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
  281. if _, err = st.TransitionDb(); err != nil {
  282. b.Fatalf("failed to execute transaction: %v", err)
  283. }
  284. if _, err = tracer.GetResult(); err != nil {
  285. b.Fatal(err)
  286. }
  287. statedb.RevertToSnapshot(snap)
  288. }
  289. }
  290. // TestZeroValueToNotExitCall tests the calltracer(s) on the following:
  291. // Tx to A, A calls B with zero value. B does not already exist.
  292. // Expected: that enter/exit is invoked and the inner call is shown in the result
  293. func TestZeroValueToNotExitCall(t *testing.T) {
  294. var to = common.HexToAddress("0x00000000000000000000000000000000deadbeef")
  295. privkey, err := crypto.HexToECDSA("0000000000000000deadbeef00000000000000000000000000000000deadbeef")
  296. if err != nil {
  297. t.Fatalf("err %v", err)
  298. }
  299. signer := types.NewEIP155Signer(big.NewInt(1))
  300. tx, err := types.SignNewTx(privkey, signer, &types.LegacyTx{
  301. GasPrice: big.NewInt(0),
  302. Gas: 50000,
  303. To: &to,
  304. })
  305. if err != nil {
  306. t.Fatalf("err %v", err)
  307. }
  308. origin, _ := signer.Sender(tx)
  309. txContext := vm.TxContext{
  310. Origin: origin,
  311. GasPrice: big.NewInt(1),
  312. }
  313. context := vm.BlockContext{
  314. CanTransfer: core.CanTransfer,
  315. Transfer: core.Transfer,
  316. Coinbase: common.Address{},
  317. BlockNumber: new(big.Int).SetUint64(8000000),
  318. Time: new(big.Int).SetUint64(5),
  319. Difficulty: big.NewInt(0x30000),
  320. GasLimit: uint64(6000000),
  321. }
  322. var code = []byte{
  323. byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), // in and outs zero
  324. byte(vm.DUP1), byte(vm.PUSH1), 0xff, byte(vm.GAS), // value=0,address=0xff, gas=GAS
  325. byte(vm.CALL),
  326. }
  327. var alloc = core.GenesisAlloc{
  328. to: core.GenesisAccount{
  329. Nonce: 1,
  330. Code: code,
  331. },
  332. origin: core.GenesisAccount{
  333. Nonce: 0,
  334. Balance: big.NewInt(500000000000000),
  335. },
  336. }
  337. _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false)
  338. // Create the tracer, the EVM environment and run it
  339. tracer, err := tracers.New("callTracer", nil, nil)
  340. if err != nil {
  341. t.Fatalf("failed to create call tracer: %v", err)
  342. }
  343. evm := vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer})
  344. msg, err := tx.AsMessage(signer, nil)
  345. if err != nil {
  346. t.Fatalf("failed to prepare transaction for tracing: %v", err)
  347. }
  348. st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
  349. if _, err = st.TransitionDb(); err != nil {
  350. t.Fatalf("failed to execute transaction: %v", err)
  351. }
  352. // Retrieve the trace result and compare against the etalon
  353. res, err := tracer.GetResult()
  354. if err != nil {
  355. t.Fatalf("failed to retrieve trace result: %v", err)
  356. }
  357. have := new(callTrace)
  358. if err := json.Unmarshal(res, have); err != nil {
  359. t.Fatalf("failed to unmarshal trace result: %v", err)
  360. }
  361. wantStr := `{"type":"CALL","from":"0x682a80a6f560eec50d54e63cbeda1c324c5f8d1b","to":"0x00000000000000000000000000000000deadbeef","value":"0x0","gas":"0x7148","gasUsed":"0x2d0","input":"0x","output":"0x","calls":[{"type":"CALL","from":"0x00000000000000000000000000000000deadbeef","to":"0x00000000000000000000000000000000000000ff","value":"0x0","gas":"0x6cbf","gasUsed":"0x0","input":"0x","output":"0x"}]}`
  362. want := new(callTrace)
  363. json.Unmarshal([]byte(wantStr), want)
  364. if !jsonEqual(have, want) {
  365. t.Error("have != want")
  366. }
  367. }