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