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