calltrace_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  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. "io/ioutil"
  20. "math/big"
  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 pacakges, 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. Result *callTrace `json:"result"`
  111. }
  112. // Iterates over all the input-output datasets in the tracer test harness and
  113. // runs the JavaScript tracers against them.
  114. func TestCallTracerLegacy(t *testing.T) {
  115. testCallTracer("callTracerLegacy", "call_tracer_legacy", t)
  116. }
  117. func TestCallTracerJs(t *testing.T) {
  118. testCallTracer("callTracerJs", "call_tracer", t)
  119. }
  120. func TestCallTracerNative(t *testing.T) {
  121. testCallTracer("callTracer", "call_tracer", t)
  122. }
  123. func testCallTracer(tracerName string, dirPath string, t *testing.T) {
  124. files, err := ioutil.ReadDir(filepath.Join("testdata", dirPath))
  125. if err != nil {
  126. t.Fatalf("failed to retrieve tracer test suite: %v", err)
  127. }
  128. for _, file := range files {
  129. if !strings.HasSuffix(file.Name(), ".json") {
  130. continue
  131. }
  132. file := file // capture range variable
  133. t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) {
  134. t.Parallel()
  135. var (
  136. test = new(callTracerTest)
  137. tx = new(types.Transaction)
  138. )
  139. // Call tracer test found, read if from disk
  140. if blob, err := ioutil.ReadFile(filepath.Join("testdata", dirPath, file.Name())); err != nil {
  141. t.Fatalf("failed to read testcase: %v", err)
  142. } else if err := json.Unmarshal(blob, test); err != nil {
  143. t.Fatalf("failed to parse testcase: %v", err)
  144. }
  145. if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {
  146. t.Fatalf("failed to parse testcase input: %v", err)
  147. }
  148. // Configure a blockchain with the given prestate
  149. var (
  150. signer = types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)))
  151. origin, _ = signer.Sender(tx)
  152. txContext = vm.TxContext{
  153. Origin: origin,
  154. GasPrice: tx.GasPrice(),
  155. }
  156. context = vm.BlockContext{
  157. CanTransfer: core.CanTransfer,
  158. Transfer: core.Transfer,
  159. Coinbase: test.Context.Miner,
  160. BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)),
  161. Time: new(big.Int).SetUint64(uint64(test.Context.Time)),
  162. Difficulty: (*big.Int)(test.Context.Difficulty),
  163. GasLimit: uint64(test.Context.GasLimit),
  164. }
  165. _, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
  166. )
  167. tracer, err := tracers.New(tracerName, new(tracers.Context))
  168. if err != nil {
  169. t.Fatalf("failed to create call tracer: %v", err)
  170. }
  171. evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer})
  172. msg, err := tx.AsMessage(signer)
  173. if err != nil {
  174. t.Fatalf("failed to prepare transaction for tracing: %v", err)
  175. }
  176. st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
  177. if _, err = st.TransitionDb(); err != nil {
  178. t.Fatalf("failed to execute transaction: %v", err)
  179. }
  180. // Retrieve the trace result and compare against the etalon
  181. res, err := tracer.GetResult()
  182. if err != nil {
  183. t.Fatalf("failed to retrieve trace result: %v", err)
  184. }
  185. ret := new(callTrace)
  186. if err := json.Unmarshal(res, ret); err != nil {
  187. t.Fatalf("failed to unmarshal trace result: %v", err)
  188. }
  189. if !jsonEqual(ret, test.Result) {
  190. // uncomment this for easier debugging
  191. //have, _ := json.MarshalIndent(ret, "", " ")
  192. //want, _ := json.MarshalIndent(test.Result, "", " ")
  193. //t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", string(have), string(want))
  194. t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, test.Result)
  195. }
  196. })
  197. }
  198. }
  199. // jsonEqual is similar to reflect.DeepEqual, but does a 'bounce' via json prior to
  200. // comparison
  201. func jsonEqual(x, y interface{}) bool {
  202. xTrace := new(callTrace)
  203. yTrace := new(callTrace)
  204. if xj, err := json.Marshal(x); err == nil {
  205. json.Unmarshal(xj, xTrace)
  206. } else {
  207. return false
  208. }
  209. if yj, err := json.Marshal(y); err == nil {
  210. json.Unmarshal(yj, yTrace)
  211. } else {
  212. return false
  213. }
  214. return reflect.DeepEqual(xTrace, yTrace)
  215. }
  216. // camel converts a snake cased input string into a camel cased output.
  217. func camel(str string) string {
  218. pieces := strings.Split(str, "_")
  219. for i := 1; i < len(pieces); i++ {
  220. pieces[i] = string(unicode.ToUpper(rune(pieces[i][0]))) + pieces[i][1:]
  221. }
  222. return strings.Join(pieces, "")
  223. }
  224. func BenchmarkTracers(b *testing.B) {
  225. files, err := ioutil.ReadDir(filepath.Join("testdata", "call_tracer"))
  226. if err != nil {
  227. b.Fatalf("failed to retrieve tracer test suite: %v", err)
  228. }
  229. for _, file := range files {
  230. if !strings.HasSuffix(file.Name(), ".json") {
  231. continue
  232. }
  233. file := file // capture range variable
  234. b.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(b *testing.B) {
  235. blob, err := ioutil.ReadFile(filepath.Join("testdata", "call_tracer", file.Name()))
  236. if err != nil {
  237. b.Fatalf("failed to read testcase: %v", err)
  238. }
  239. test := new(callTracerTest)
  240. if err := json.Unmarshal(blob, test); err != nil {
  241. b.Fatalf("failed to parse testcase: %v", err)
  242. }
  243. benchTracer("callTracerNative", test, b)
  244. })
  245. }
  246. }
  247. func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
  248. // Configure a blockchain with the given prestate
  249. tx := new(types.Transaction)
  250. if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil {
  251. b.Fatalf("failed to parse testcase input: %v", err)
  252. }
  253. signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)))
  254. msg, err := tx.AsMessage(signer)
  255. if err != nil {
  256. b.Fatalf("failed to prepare transaction for tracing: %v", err)
  257. }
  258. origin, _ := signer.Sender(tx)
  259. txContext := vm.TxContext{
  260. Origin: origin,
  261. GasPrice: tx.GasPrice(),
  262. }
  263. context := vm.BlockContext{
  264. CanTransfer: core.CanTransfer,
  265. Transfer: core.Transfer,
  266. Coinbase: test.Context.Miner,
  267. BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)),
  268. Time: new(big.Int).SetUint64(uint64(test.Context.Time)),
  269. Difficulty: (*big.Int)(test.Context.Difficulty),
  270. GasLimit: uint64(test.Context.GasLimit),
  271. }
  272. _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
  273. b.ReportAllocs()
  274. b.ResetTimer()
  275. for i := 0; i < b.N; i++ {
  276. tracer, err := tracers.New(tracerName, new(tracers.Context))
  277. if err != nil {
  278. b.Fatalf("failed to create call tracer: %v", err)
  279. }
  280. evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer})
  281. snap := statedb.Snapshot()
  282. st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
  283. if _, err = st.TransitionDb(); err != nil {
  284. b.Fatalf("failed to execute transaction: %v", err)
  285. }
  286. if _, err = tracer.GetResult(); err != nil {
  287. b.Fatal(err)
  288. }
  289. statedb.RevertToSnapshot(snap)
  290. }
  291. }
  292. // TestZeroValueToNotExitCall tests the calltracer(s) on the following:
  293. // Tx to A, A calls B with zero value. B does not already exist.
  294. // Expected: that enter/exit is invoked and the inner call is shown in the result
  295. func TestZeroValueToNotExitCall(t *testing.T) {
  296. var to = common.HexToAddress("0x00000000000000000000000000000000deadbeef")
  297. privkey, err := crypto.HexToECDSA("0000000000000000deadbeef00000000000000000000000000000000deadbeef")
  298. if err != nil {
  299. t.Fatalf("err %v", err)
  300. }
  301. signer := types.NewEIP155Signer(big.NewInt(1))
  302. tx, err := types.SignNewTx(privkey, signer, &types.LegacyTx{
  303. GasPrice: big.NewInt(0),
  304. Gas: 50000,
  305. To: &to,
  306. })
  307. if err != nil {
  308. t.Fatalf("err %v", err)
  309. }
  310. origin, _ := signer.Sender(tx)
  311. txContext := vm.TxContext{
  312. Origin: origin,
  313. GasPrice: big.NewInt(1),
  314. }
  315. context := vm.BlockContext{
  316. CanTransfer: core.CanTransfer,
  317. Transfer: core.Transfer,
  318. Coinbase: common.Address{},
  319. BlockNumber: new(big.Int).SetUint64(8000000),
  320. Time: new(big.Int).SetUint64(5),
  321. Difficulty: big.NewInt(0x30000),
  322. GasLimit: uint64(6000000),
  323. }
  324. var code = []byte{
  325. byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), // in and outs zero
  326. byte(vm.DUP1), byte(vm.PUSH1), 0xff, byte(vm.GAS), // value=0,address=0xff, gas=GAS
  327. byte(vm.CALL),
  328. }
  329. var alloc = core.GenesisAlloc{
  330. to: core.GenesisAccount{
  331. Nonce: 1,
  332. Code: code,
  333. },
  334. origin: core.GenesisAccount{
  335. Nonce: 0,
  336. Balance: big.NewInt(500000000000000),
  337. },
  338. }
  339. _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false)
  340. // Create the tracer, the EVM environment and run it
  341. tracer, err := tracers.New("callTracer", nil)
  342. if err != nil {
  343. t.Fatalf("failed to create call tracer: %v", err)
  344. }
  345. evm := vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer})
  346. msg, err := tx.AsMessage(signer)
  347. if err != nil {
  348. t.Fatalf("failed to prepare transaction for tracing: %v", err)
  349. }
  350. st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
  351. if _, err = st.TransitionDb(); err != nil {
  352. t.Fatalf("failed to execute transaction: %v", err)
  353. }
  354. // Retrieve the trace result and compare against the etalon
  355. res, err := tracer.GetResult()
  356. if err != nil {
  357. t.Fatalf("failed to retrieve trace result: %v", err)
  358. }
  359. have := new(callTrace)
  360. if err := json.Unmarshal(res, have); err != nil {
  361. t.Fatalf("failed to unmarshal trace result: %v", err)
  362. }
  363. 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"}]}`
  364. want := new(callTrace)
  365. json.Unmarshal([]byte(wantStr), want)
  366. if !jsonEqual(have, want) {
  367. t.Error("have != want")
  368. }
  369. }