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