github.com/core-coin/go-core/v2@v2.1.9/xcb/tracers/tracers_test.go (about) 1 // Copyright 2023 by the Authors 2 // This file is part of the go-core library. 3 // 4 // The go-core 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-core 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-core library. If not, see <http://www.gnu.org/licenses/>. 16 17 package tracers 18 19 import ( 20 "crypto/rand" 21 "encoding/json" 22 "io/ioutil" 23 "math/big" 24 "path/filepath" 25 "reflect" 26 "strings" 27 "testing" 28 29 "github.com/core-coin/go-core/v2/common" 30 "github.com/core-coin/go-core/v2/common/hexutil" 31 "github.com/core-coin/go-core/v2/common/math" 32 "github.com/core-coin/go-core/v2/core" 33 "github.com/core-coin/go-core/v2/core/rawdb" 34 "github.com/core-coin/go-core/v2/core/types" 35 "github.com/core-coin/go-core/v2/core/vm" 36 "github.com/core-coin/go-core/v2/crypto" 37 "github.com/core-coin/go-core/v2/params" 38 "github.com/core-coin/go-core/v2/rlp" 39 "github.com/core-coin/go-core/v2/tests" 40 ) 41 42 // To generate a new callTracer test, copy paste the makeTest method below into 43 // a Gocore console and call it with a transaction hash you which to export. 44 45 /* 46 // makeTest generates a callTracer test by running a prestate reassembled and a 47 // call trace run, assembling all the gathered information into a test case. 48 var makeTest = function(tx, rewind) { 49 // Generate the genesis block from the block, transaction and prestate data 50 var block = xcb.getBlock(xcb.getTransaction(tx).blockHash); 51 var genesis = xcb.getBlock(block.parentHash); 52 53 delete genesis.energyUsed; 54 delete genesis.logsBloom; 55 delete genesis.parentHash; 56 delete genesis.receiptsRoot; 57 delete genesis.sha3Uncles; 58 delete genesis.size; 59 delete genesis.transactions; 60 delete genesis.transactionsRoot; 61 delete genesis.uncles; 62 63 genesis.energyLimit = genesis.energyLimit.toString(); 64 genesis.number = genesis.number.toString(); 65 genesis.timestamp = genesis.timestamp.toString(); 66 67 genesis.alloc = debug.traceTransaction(tx, {tracer: "prestateTracer", rewind: rewind}); 68 for (var key in genesis.alloc) { 69 genesis.alloc[key].nonce = genesis.alloc[key].nonce.toString(); 70 } 71 genesis.config = admin.nodeInfo.protocols.xcb.config; 72 73 // Generate the call trace and produce the test input 74 var result = debug.traceTransaction(tx, {tracer: "callTracer", rewind: rewind}); 75 delete result.time; 76 77 console.log(JSON.stringify({ 78 genesis: genesis, 79 context: { 80 number: block.number.toString(), 81 difficulty: block.difficulty, 82 timestamp: block.timestamp.toString(), 83 energyLimit: block.energyLimit.toString(), 84 miner: block.miner, 85 }, 86 input: xcb.getRawTransaction(tx), 87 result: result, 88 }, null, 2)); 89 } 90 */ 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 Energy *hexutil.Uint64 `json:"energy,omitempty"` 100 EnergyUsed *hexutil.Uint64 `json:"energyUsed,omitempty"` 101 Value *hexutil.Big `json:"value,omitempty"` 102 Error string `json:"error,omitempty"` 103 Calls []callTrace `json:"calls,omitempty"` 104 } 105 106 type callContext struct { 107 Number math.HexOrDecimal64 `json:"number"` 108 Difficulty *math.HexOrDecimal256 `json:"difficulty"` 109 Time math.HexOrDecimal64 `json:"timestamp"` 110 EnergyLimit math.HexOrDecimal64 `json:"energyLimit"` 111 Miner common.Address `json:"miner"` 112 } 113 114 // callTracerTest defines a single test to check the call tracer against. 115 type callTracerTest struct { 116 Genesis *core.Genesis `json:"genesis"` 117 Context *callContext `json:"context"` 118 Input string `json:"input"` 119 Result *callTrace `json:"result"` 120 } 121 122 func TestPrestateTracerCreate2(t *testing.T) { 123 addr, _ := common.HexToAddress("cb8000000000000000000000000000000000deadbeef") 124 unsignedTx := types.NewTransaction(1, addr, 125 new(big.Int), 5000000, big.NewInt(1), []byte{}) 126 127 privateKeyEDDSA, err := crypto.GenerateKey(rand.Reader) 128 if err != nil { 129 t.Fatalf("err %v", err) 130 } 131 signer := types.NewNucleusSigner(big.NewInt(1)) 132 tx, err := types.SignTx(unsignedTx, signer, privateKeyEDDSA) 133 if err != nil { 134 t.Fatalf("err %v", err) 135 } 136 /** 137 This comes from one of the test-vectors on the Skinny Create2 - CIP 138 139 address 0x00000000000000000000000000000000deadbeef 140 salt 0x00000000000000000000000000000000000000000000000000000000cafebabe 141 init_code 0xdeadbeef 142 energy (assuming no mem expansion): 32006 143 result: 0x60f3f640a8508fC6a86d45DF051962668E1e8AC7 144 */ 145 origin, _ := signer.Sender(tx) 146 txContext := vm.TxContext{ 147 Origin: origin, 148 EnergyPrice: big.NewInt(1), 149 } 150 context := vm.BlockContext{ 151 CanTransfer: core.CanTransfer, 152 Transfer: core.Transfer, 153 Coinbase: common.Address{}, 154 BlockNumber: new(big.Int).SetUint64(8000000), 155 Time: new(big.Int).SetUint64(5), 156 Difficulty: big.NewInt(0x30000), 157 EnergyLimit: uint64(6000000), 158 } 159 alloc := core.GenesisAlloc{} 160 161 // The code pushes 'deadbeef' into memory, then the other params, and calls CREATE2, then returns 162 // the address 163 alloc[addr] = core.GenesisAccount{ 164 Nonce: 1, 165 Code: hexutil.MustDecode("0x63deadbeef60005263cafebabe6004601c6000F560005260206000F3"), 166 Balance: big.NewInt(1), 167 } 168 alloc[origin] = core.GenesisAccount{ 169 Nonce: 1, 170 Code: []byte{}, 171 Balance: big.NewInt(500000000000000), 172 } 173 _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false) 174 175 // Create the tracer, the CVM environment and run it 176 tracer, err := New("prestateTracer") 177 if err != nil { 178 t.Fatalf("failed to create call tracer: %v", err) 179 } 180 cvm := vm.NewCVM(context, txContext, statedb, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer}) 181 182 msg, err := tx.AsMessage(signer) 183 if err != nil { 184 t.Fatalf("failed to prepare transaction for tracing: %v", err) 185 } 186 st := core.NewStateTransition(cvm, msg, new(core.EnergyPool).AddEnergy(tx.Energy())) 187 if _, err = st.TransitionDb(); err != nil { 188 t.Fatalf("failed to execute transaction: %v", err) 189 } 190 // Retrieve the trace result and compare against the etalon 191 res, err := tracer.GetResult() 192 if err != nil { 193 t.Fatalf("failed to retrieve trace result: %v", err) 194 } 195 ret := make(map[string]interface{}) 196 if err := json.Unmarshal(res, &ret); err != nil { 197 t.Fatalf("failed to unmarshal trace result: %v", err) 198 } 199 if _, has := ret["0xcb16050c2d2855fcee1a2ab8e4db79cc1015faf3b5d4"]; !has { 200 t.Fatalf("Expected 0xcb16050c2d2855fcee1a2ab8e4db79cc1015faf3b5d4 in result") 201 } 202 } 203 204 // Iterates over all the input-output datasets in the tracer test harness and 205 // runs the JavaScript tracers against them. 206 func TestCallTracer(t *testing.T) { 207 t.Skip("temporary skip") 208 files, err := ioutil.ReadDir("testdata") 209 if err != nil { 210 t.Fatalf("failed to retrieve tracer test suite: %v", err) 211 } 212 for _, file := range files { 213 if !strings.HasPrefix(file.Name(), "call_tracer_") { 214 continue 215 } 216 file := file // capture range variable 217 t.Run(camel(strings.TrimSuffix(strings.TrimPrefix(file.Name(), "call_tracer_"), ".json")), func(t *testing.T) { 218 t.Parallel() 219 220 // Call tracer test found, read if from disk 221 blob, err := ioutil.ReadFile(filepath.Join("testdata", file.Name())) 222 if err != nil { 223 t.Fatalf("failed to read testcase: %v", err) 224 } 225 test := new(callTracerTest) 226 if err := json.Unmarshal(blob, test); err != nil { 227 t.Fatalf("failed to parse testcase: %v", err) 228 } 229 // Configure a blockchain with the given prestate 230 tx := new(types.Transaction) 231 if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil { 232 t.Fatalf("failed to parse testcase input: %v", err) 233 } 234 signer := types.MakeSigner(test.Genesis.Config.NetworkID) 235 origin, _ := signer.Sender(tx) 236 txContext := vm.TxContext{ 237 Origin: origin, 238 EnergyPrice: tx.EnergyPrice(), 239 } 240 context := vm.BlockContext{ 241 CanTransfer: core.CanTransfer, 242 Transfer: core.Transfer, 243 Coinbase: test.Context.Miner, 244 BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)), 245 Time: new(big.Int).SetUint64(uint64(test.Context.Time)), 246 Difficulty: (*big.Int)(test.Context.Difficulty), 247 EnergyLimit: uint64(test.Context.EnergyLimit), 248 } 249 _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) 250 251 // Create the tracer, the CVM environment and run it 252 tracer, err := New("callTracer") 253 if err != nil { 254 t.Fatalf("failed to create call tracer: %v", err) 255 } 256 cvm := vm.NewCVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer}) 257 258 msg, err := tx.AsMessage(signer) 259 if err != nil { 260 t.Fatalf("failed to prepare transaction for tracing: %v", err) 261 } 262 st := core.NewStateTransition(cvm, msg, new(core.EnergyPool).AddEnergy(tx.Energy())) 263 if _, err = st.TransitionDb(); err != nil { 264 t.Fatalf("failed to execute transaction: %v", err) 265 } 266 // Retrieve the trace result and compare against the etalon 267 res, err := tracer.GetResult() 268 if err != nil { 269 t.Fatalf("failed to retrieve trace result: %v", err) 270 } 271 ret := new(callTrace) 272 if err := json.Unmarshal(res, ret); err != nil { 273 t.Fatalf("failed to unmarshal trace result: %v", err) 274 } 275 276 if !jsonEqual(ret, test.Result) { 277 // uncomment this for easier debugging 278 //have, _ := json.MarshalIndent(ret, "", " ") 279 //want, _ := json.MarshalIndent(test.Result, "", " ") 280 //t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", string(have), string(want)) 281 t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, test.Result) 282 } 283 }) 284 } 285 } 286 287 // jsonEqual is similar to reflect.DeepEqual, but does a 'bounce' via json prior to 288 // comparison 289 func jsonEqual(x, y interface{}) bool { 290 xTrace := new(callTrace) 291 yTrace := new(callTrace) 292 if xj, err := json.Marshal(x); err == nil { 293 json.Unmarshal(xj, xTrace) 294 } else { 295 return false 296 } 297 if yj, err := json.Marshal(y); err == nil { 298 json.Unmarshal(yj, yTrace) 299 } else { 300 return false 301 } 302 return reflect.DeepEqual(xTrace, yTrace) 303 }