github.com/tacshi/go-ethereum@v0.0.0-20230616113857-84a434e20921/les/odr_test.go (about) 1 // Copyright 2016 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 17 package les 18 19 import ( 20 "bytes" 21 "context" 22 "crypto/rand" 23 "fmt" 24 "math/big" 25 "reflect" 26 "testing" 27 "time" 28 29 "github.com/tacshi/go-ethereum/common" 30 "github.com/tacshi/go-ethereum/common/math" 31 "github.com/tacshi/go-ethereum/core" 32 "github.com/tacshi/go-ethereum/core/rawdb" 33 "github.com/tacshi/go-ethereum/core/state" 34 "github.com/tacshi/go-ethereum/core/txpool" 35 "github.com/tacshi/go-ethereum/core/types" 36 "github.com/tacshi/go-ethereum/core/vm" 37 "github.com/tacshi/go-ethereum/ethdb" 38 "github.com/tacshi/go-ethereum/light" 39 "github.com/tacshi/go-ethereum/params" 40 "github.com/tacshi/go-ethereum/rlp" 41 ) 42 43 type odrTestFn func(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte 44 45 func TestOdrGetBlockLes2(t *testing.T) { testOdr(t, 2, 1, true, odrGetBlock) } 46 func TestOdrGetBlockLes3(t *testing.T) { testOdr(t, 3, 1, true, odrGetBlock) } 47 func TestOdrGetBlockLes4(t *testing.T) { testOdr(t, 4, 1, true, odrGetBlock) } 48 49 func odrGetBlock(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte { 50 var block *types.Block 51 if bc != nil { 52 block = bc.GetBlockByHash(bhash) 53 } else { 54 block, _ = lc.GetBlockByHash(ctx, bhash) 55 } 56 if block == nil { 57 return nil 58 } 59 rlp, _ := rlp.EncodeToBytes(block) 60 return rlp 61 } 62 63 func TestOdrGetReceiptsLes2(t *testing.T) { testOdr(t, 2, 1, true, odrGetReceipts) } 64 func TestOdrGetReceiptsLes3(t *testing.T) { testOdr(t, 3, 1, true, odrGetReceipts) } 65 func TestOdrGetReceiptsLes4(t *testing.T) { testOdr(t, 4, 1, true, odrGetReceipts) } 66 67 func odrGetReceipts(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte { 68 var receipts types.Receipts 69 if bc != nil { 70 if number := rawdb.ReadHeaderNumber(db, bhash); number != nil { 71 receipts = rawdb.ReadReceipts(db, bhash, *number, config) 72 } 73 } else { 74 if number := rawdb.ReadHeaderNumber(db, bhash); number != nil { 75 receipts, _ = light.GetBlockReceipts(ctx, lc.Odr(), bhash, *number) 76 } 77 } 78 if receipts == nil { 79 return nil 80 } 81 rlp, _ := rlp.EncodeToBytes(receipts) 82 return rlp 83 } 84 85 func TestOdrAccountsLes2(t *testing.T) { testOdr(t, 2, 1, true, odrAccounts) } 86 func TestOdrAccountsLes3(t *testing.T) { testOdr(t, 3, 1, true, odrAccounts) } 87 func TestOdrAccountsLes4(t *testing.T) { testOdr(t, 4, 1, true, odrAccounts) } 88 89 func odrAccounts(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte { 90 dummyAddr := common.HexToAddress("1234567812345678123456781234567812345678") 91 acc := []common.Address{bankAddr, userAddr1, userAddr2, dummyAddr} 92 93 var ( 94 res []byte 95 st *state.StateDB 96 err error 97 ) 98 for _, addr := range acc { 99 if bc != nil { 100 header := bc.GetHeaderByHash(bhash) 101 st, err = state.New(header.Root, state.NewDatabase(db), nil) 102 } else { 103 header := lc.GetHeaderByHash(bhash) 104 st = light.NewState(ctx, header, lc.Odr()) 105 } 106 if err == nil { 107 bal := st.GetBalance(addr) 108 rlp, _ := rlp.EncodeToBytes(bal) 109 res = append(res, rlp...) 110 } 111 } 112 return res 113 } 114 115 func TestOdrContractCallLes2(t *testing.T) { testOdr(t, 2, 2, true, odrContractCall) } 116 func TestOdrContractCallLes3(t *testing.T) { testOdr(t, 3, 2, true, odrContractCall) } 117 func TestOdrContractCallLes4(t *testing.T) { testOdr(t, 4, 2, true, odrContractCall) } 118 119 func odrContractCall(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte { 120 data := common.Hex2Bytes("60CD26850000000000000000000000000000000000000000000000000000000000000000") 121 122 var res []byte 123 for i := 0; i < 3; i++ { 124 data[35] = byte(i) 125 if bc != nil { 126 header := bc.GetHeaderByHash(bhash) 127 statedb, err := state.New(header.Root, bc.StateCache(), nil) 128 129 if err == nil { 130 from := statedb.GetOrNewStateObject(bankAddr) 131 from.SetBalance(math.MaxBig256) 132 133 msg := &core.Message{ 134 From: from.Address(), 135 To: &testContractAddr, 136 Value: new(big.Int), 137 GasLimit: 100000, 138 GasPrice: big.NewInt(params.InitialBaseFee), 139 GasFeeCap: big.NewInt(params.InitialBaseFee), 140 GasTipCap: new(big.Int), 141 Data: data, 142 SkipAccountChecks: true, 143 } 144 145 context := core.NewEVMBlockContext(header, bc, nil) 146 txContext := core.NewEVMTxContext(msg) 147 vmenv := vm.NewEVM(context, txContext, statedb, config, vm.Config{NoBaseFee: true}) 148 149 //vmenv := core.NewEnv(statedb, config, bc, msg, header, vm.Config{}) 150 gp := new(core.GasPool).AddGas(math.MaxUint64) 151 result, _ := core.ApplyMessage(vmenv, msg, gp) 152 res = append(res, result.Return()...) 153 } 154 } else { 155 header := lc.GetHeaderByHash(bhash) 156 state := light.NewState(ctx, header, lc.Odr()) 157 state.SetBalance(bankAddr, math.MaxBig256) 158 msg := &core.Message{ 159 From: bankAddr, 160 To: &testContractAddr, 161 Value: new(big.Int), 162 GasLimit: 100000, 163 GasPrice: big.NewInt(params.InitialBaseFee), 164 GasFeeCap: big.NewInt(params.InitialBaseFee), 165 GasTipCap: new(big.Int), 166 Data: data, 167 SkipAccountChecks: true, 168 } 169 context := core.NewEVMBlockContext(header, lc, nil) 170 txContext := core.NewEVMTxContext(msg) 171 vmenv := vm.NewEVM(context, txContext, state, config, vm.Config{NoBaseFee: true}) 172 gp := new(core.GasPool).AddGas(math.MaxUint64) 173 result, _ := core.ApplyMessage(vmenv, msg, gp) 174 if state.Error() == nil { 175 res = append(res, result.Return()...) 176 } 177 } 178 } 179 return res 180 } 181 182 func TestOdrTxStatusLes2(t *testing.T) { testOdr(t, 2, 1, false, odrTxStatus) } 183 func TestOdrTxStatusLes3(t *testing.T) { testOdr(t, 3, 1, false, odrTxStatus) } 184 func TestOdrTxStatusLes4(t *testing.T) { testOdr(t, 4, 1, false, odrTxStatus) } 185 186 func odrTxStatus(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte { 187 var txs types.Transactions 188 if bc != nil { 189 block := bc.GetBlockByHash(bhash) 190 txs = block.Transactions() 191 } else { 192 if block, _ := lc.GetBlockByHash(ctx, bhash); block != nil { 193 btxs := block.Transactions() 194 txs = make(types.Transactions, len(btxs)) 195 for i, tx := range btxs { 196 var err error 197 txs[i], _, _, _, err = light.GetTransaction(ctx, lc.Odr(), tx.Hash()) 198 if err != nil { 199 return nil 200 } 201 } 202 } 203 } 204 rlp, _ := rlp.EncodeToBytes(txs) 205 return rlp 206 } 207 208 // testOdr tests odr requests whose validation guaranteed by block headers. 209 func testOdr(t *testing.T, protocol int, expFail uint64, checkCached bool, fn odrTestFn) { 210 // Assemble the test environment 211 netconfig := testnetConfig{ 212 blocks: 4, 213 protocol: protocol, 214 connect: true, 215 nopruning: true, 216 } 217 server, client, tearDown := newClientServerEnv(t, netconfig) 218 defer tearDown() 219 220 // Ensure the client has synced all necessary data. 221 clientHead := client.handler.backend.blockchain.CurrentHeader() 222 if clientHead.Number.Uint64() != 4 { 223 t.Fatalf("Failed to sync the chain with server, head: %v", clientHead.Number.Uint64()) 224 } 225 // Disable the mechanism that we will wait a few time for request 226 // even there is no suitable peer to send right now. 227 waitForPeers = 0 228 229 test := func(expFail uint64) { 230 // Mark this as a helper to put the failures at the correct lines 231 t.Helper() 232 233 for i := uint64(0); i <= server.handler.blockchain.CurrentHeader().Number.Uint64(); i++ { 234 bhash := rawdb.ReadCanonicalHash(server.db, i) 235 b1 := fn(light.NoOdr, server.db, server.handler.server.chainConfig, server.handler.blockchain, nil, bhash) 236 237 // Set the timeout as 1 second here, ensure there is enough time 238 // for travis to make the action. 239 ctx, cancel := context.WithTimeout(context.Background(), time.Second) 240 b2 := fn(ctx, client.db, client.handler.backend.chainConfig, nil, client.handler.backend.blockchain, bhash) 241 cancel() 242 243 eq := bytes.Equal(b1, b2) 244 exp := i < expFail 245 if exp && !eq { 246 t.Fatalf("odr mismatch: have %x, want %x", b2, b1) 247 } 248 if !exp && eq { 249 t.Fatalf("unexpected odr match") 250 } 251 } 252 } 253 254 // expect retrievals to fail (except genesis block) without a les peer 255 client.handler.backend.peers.lock.Lock() 256 client.peer.speer.hasBlockHook = func(common.Hash, uint64, bool) bool { return false } 257 client.handler.backend.peers.lock.Unlock() 258 test(expFail) 259 260 // expect all retrievals to pass 261 client.handler.backend.peers.lock.Lock() 262 client.peer.speer.hasBlockHook = func(common.Hash, uint64, bool) bool { return true } 263 client.handler.backend.peers.lock.Unlock() 264 test(5) 265 266 // still expect all retrievals to pass, now data should be cached locally 267 if checkCached { 268 client.handler.backend.peers.unregister(client.peer.speer.id) 269 time.Sleep(time.Millisecond * 10) // ensure that all peerSetNotify callbacks are executed 270 test(5) 271 } 272 } 273 274 func TestGetTxStatusFromUnindexedPeersLES4(t *testing.T) { testGetTxStatusFromUnindexedPeers(t, lpv4) } 275 276 func testGetTxStatusFromUnindexedPeers(t *testing.T, protocol int) { 277 var ( 278 blocks = 8 279 netconfig = testnetConfig{ 280 blocks: blocks, 281 protocol: protocol, 282 nopruning: true, 283 } 284 ) 285 server, client, tearDown := newClientServerEnv(t, netconfig) 286 defer tearDown() 287 288 // Iterate the chain, create the tx indexes locally 289 var ( 290 testHash common.Hash 291 testStatus light.TxStatus 292 293 txs = make(map[common.Hash]*types.Transaction) // Transaction objects set 294 blockNumbers = make(map[common.Hash]uint64) // Transaction hash to block number mappings 295 blockHashes = make(map[common.Hash]common.Hash) // Transaction hash to block hash mappings 296 intraIndex = make(map[common.Hash]uint64) // Transaction intra-index in block 297 ) 298 for number := uint64(1); number < server.backend.Blockchain().CurrentBlock().Number.Uint64(); number++ { 299 block := server.backend.Blockchain().GetBlockByNumber(number) 300 if block == nil { 301 t.Fatalf("Failed to retrieve block %d", number) 302 } 303 for index, tx := range block.Transactions() { 304 txs[tx.Hash()] = tx 305 blockNumbers[tx.Hash()] = number 306 blockHashes[tx.Hash()] = block.Hash() 307 intraIndex[tx.Hash()] = uint64(index) 308 309 if testHash == (common.Hash{}) { 310 testHash = tx.Hash() 311 testStatus = light.TxStatus{ 312 Status: txpool.TxStatusIncluded, 313 Lookup: &rawdb.LegacyTxLookupEntry{ 314 BlockHash: block.Hash(), 315 BlockIndex: block.NumberU64(), 316 Index: uint64(index), 317 }, 318 } 319 } 320 } 321 } 322 // serveMsg processes incoming GetTxStatusMsg and sends the response back. 323 serveMsg := func(peer *testPeer, txLookup uint64) error { 324 msg, err := peer.app.ReadMsg() 325 if err != nil { 326 return err 327 } 328 if msg.Code != GetTxStatusMsg { 329 return fmt.Errorf("message code mismatch: got %d, expected %d", msg.Code, GetTxStatusMsg) 330 } 331 var r GetTxStatusPacket 332 if err := msg.Decode(&r); err != nil { 333 return err 334 } 335 stats := make([]light.TxStatus, len(r.Hashes)) 336 for i, hash := range r.Hashes { 337 number, exist := blockNumbers[hash] 338 if !exist { 339 continue // Filter out unknown transactions 340 } 341 min := uint64(blocks) - txLookup 342 if txLookup != txIndexUnlimited && (txLookup == txIndexDisabled || number < min) { 343 continue // Filter out unindexed transactions 344 } 345 stats[i].Status = txpool.TxStatusIncluded 346 stats[i].Lookup = &rawdb.LegacyTxLookupEntry{ 347 BlockHash: blockHashes[hash], 348 BlockIndex: number, 349 Index: intraIndex[hash], 350 } 351 } 352 data, _ := rlp.EncodeToBytes(stats) 353 reply := &reply{peer.app, TxStatusMsg, r.ReqID, data} 354 reply.send(testBufLimit) 355 return nil 356 } 357 358 var testspecs = []struct { 359 peers int 360 txLookups []uint64 361 txs []common.Hash 362 results []light.TxStatus 363 }{ 364 // Retrieve mined transaction from the empty peerset 365 { 366 peers: 0, 367 txLookups: []uint64{}, 368 txs: []common.Hash{testHash}, 369 results: []light.TxStatus{{}}, 370 }, 371 // Retrieve unknown transaction from the full peers 372 { 373 peers: 3, 374 txLookups: []uint64{txIndexUnlimited, txIndexUnlimited, txIndexUnlimited}, 375 txs: []common.Hash{randomHash()}, 376 results: []light.TxStatus{{}}, 377 }, 378 // Retrieve mined transaction from the full peers 379 { 380 peers: 3, 381 txLookups: []uint64{txIndexUnlimited, txIndexUnlimited, txIndexUnlimited}, 382 txs: []common.Hash{testHash}, 383 results: []light.TxStatus{testStatus}, 384 }, 385 // Retrieve mixed transactions from the full peers 386 { 387 peers: 3, 388 txLookups: []uint64{txIndexUnlimited, txIndexUnlimited, txIndexUnlimited}, 389 txs: []common.Hash{randomHash(), testHash}, 390 results: []light.TxStatus{{}, testStatus}, 391 }, 392 // Retrieve mixed transactions from unindexed peer(but the target is still available) 393 { 394 peers: 3, 395 txLookups: []uint64{uint64(blocks) - testStatus.Lookup.BlockIndex, uint64(blocks) - testStatus.Lookup.BlockIndex - 1, uint64(blocks) - testStatus.Lookup.BlockIndex - 2}, 396 txs: []common.Hash{randomHash(), testHash}, 397 results: []light.TxStatus{{}, testStatus}, 398 }, 399 // Retrieve mixed transactions from unindexed peer(but the target is not available) 400 { 401 peers: 3, 402 txLookups: []uint64{uint64(blocks) - testStatus.Lookup.BlockIndex - 1, uint64(blocks) - testStatus.Lookup.BlockIndex - 1, uint64(blocks) - testStatus.Lookup.BlockIndex - 2}, 403 txs: []common.Hash{randomHash(), testHash}, 404 results: []light.TxStatus{{}, {}}, 405 }, 406 } 407 for _, testspec := range testspecs { 408 // Create a bunch of server peers with different tx history 409 var ( 410 closeFns []func() 411 ) 412 for i := 0; i < testspec.peers; i++ { 413 peer, closePeer, _ := client.newRawPeer(t, fmt.Sprintf("server-%d", i), protocol, testspec.txLookups[i]) 414 closeFns = append(closeFns, closePeer) 415 416 // Create a one-time routine for serving message 417 go func(i int, peer *testPeer, lookup uint64) { 418 serveMsg(peer, lookup) 419 }(i, peer, testspec.txLookups[i]) 420 } 421 422 // Send out the GetTxStatus requests, compare the result with 423 // expected value. 424 r := &light.TxStatusRequest{Hashes: testspec.txs} 425 ctx, cancel := context.WithTimeout(context.Background(), time.Second) 426 defer cancel() 427 428 err := client.handler.backend.odr.RetrieveTxStatus(ctx, r) 429 if err != nil { 430 t.Errorf("Failed to retrieve tx status %v", err) 431 } else { 432 if !reflect.DeepEqual(testspec.results, r.Status) { 433 t.Errorf("Result mismatch, diff") 434 } 435 } 436 437 // Close all connected peers and start the next round 438 for _, closeFn := range closeFns { 439 closeFn() 440 } 441 } 442 } 443 444 // randomHash generates a random blob of data and returns it as a hash. 445 func randomHash() common.Hash { 446 var hash common.Hash 447 if n, err := rand.Read(hash[:]); n != common.HashLength || err != nil { 448 panic(err) 449 } 450 return hash 451 }