From 7a44bab77b25128a6414f22ba7013a66a1030875 Mon Sep 17 00:00:00 2001 From: zsfelfoldi Date: Sat, 24 Oct 2015 04:13:17 +0200 Subject: [PATCH] fixed failing tests, added new ones --- cmd/utils/flags.go | 6 - core/access/chain_access.go | 107 +++++---- core/access/context.go | 16 +- core/blockchain.go | 3 - core/blockchain_test.go | 2 +- core/core_access.go | 10 +- core/state/state_access.go | 1 - les/handler.go | 30 +-- les/handler_test.go | 434 ++++++++++++++++++++++++++++++++++++ les/helper_test.go | 194 ++++++++++++++++ les/odr_test.go | 140 ++++++++++++ les/protocol.go | 20 +- trie/arc.go | 11 + trie/trie.go | 4 + 14 files changed, 879 insertions(+), 99 deletions(-) create mode 100644 les/handler_test.go create mode 100644 les/helper_test.go create mode 100644 les/odr_test.go diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index b8d62c7f91..5128dcc28a 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -135,11 +135,6 @@ var ( Name: "natspec", Usage: "Enable NatSpec confirmation notice", } - DocRootFlag = DirectoryFlag{ - Name: "docroot", - Usage: "Document Root for HTTPClient file scheme", - Value: DirectoryString{common.HomeDir()}, - } CacheFlag = cli.IntFlag{ Name: "cache", Usage: "Megabytes of memory allocated to internal caching (min 16MB / database forced)", @@ -466,7 +461,6 @@ func MakeEthConfig(clientID, version string, ctx *cli.Context) *eth.Config { Olympic: ctx.GlobalBool(OlympicFlag.Name), NAT: MakeNAT(ctx), NatSpec: ctx.GlobalBool(NatspecEnabledFlag.Name), - DocRoot: ctx.GlobalString(DocRootFlag.Name), Discovery: !ctx.GlobalBool(NoDiscoverFlag.Name), NodeKey: MakeNodeKey(ctx), Shh: ctx.GlobalBool(WhisperEnabledFlag.Name), diff --git a/core/access/chain_access.go b/core/access/chain_access.go index 4439e2fbcb..b41dd296f8 100644 --- a/core/access/chain_access.go +++ b/core/access/chain_access.go @@ -30,35 +30,48 @@ import ( ) var ( - ErrCancel = errors.New("ODR cancelled") - errNotInDb = errors.New("object not found in database") + ErrCancel = errors.New("ODR cancelled") + errNotInDb = errors.New("object not found in database") ) -const LogLevel = logger.Info +const LogLevel = logger.Debug var ( requestTimeout = time.Millisecond * 300 - retryPeers = time.Second * 1 + retryPeers = time.Second * 1 ) type ChainAccess struct { - db ethdb.Database - odr bool // light client mode, odr enabled - lock sync.Mutex - valFunc validatorFunc - deliverChan chan *Msg - peers *peerSet + db ethdb.Database + odr bool // light client mode, odr enabled + lock sync.Mutex + sentReqs map[uint64]*sentReq + sentReqCnt uint64 + peers *peerSet // p2p access objects // parameters (light/full/archive) } +type requestFunc func(*Peer) error +type validatorFunc func(*Msg) bool + +type sentReq struct { + valFunc validatorFunc + deliverChan chan *Msg +} + func NewDbChainAccess(db ethdb.Database) *ChainAccess { return NewChainAccess(db, false) } func NewChainAccess(db ethdb.Database, odr bool) *ChainAccess { - return &ChainAccess{db: db, peers: newPeerSet(), odr: odr} + return &ChainAccess{ + db: db, + peers: newPeerSet(), + sentReqs: make(map[uint64]*sentReq), + odr: odr, + } } func (self *ChainAccess) Db() ethdb.Database { @@ -103,63 +116,63 @@ type ObjectAccess interface { Valid(*Msg) bool // if true, keeps the retrieved object } -type requestFunc func(*Peer) error -type validatorFunc func(*Msg) bool - func (self *ChainAccess) Deliver(id string, msg *Msg) (processed bool) { self.lock.Lock() - valFunc := self.valFunc - chn := self.deliverChan - self.lock.Unlock() - if (valFunc != nil) && (chn != nil) && valFunc(msg) { - chn <- msg - return true + defer self.lock.Unlock() + + for i, req := range self.sentReqs { + if req.valFunc(msg) { + req.deliverChan <- msg + delete(self.sentReqs, i) + return true + } } return false } func (self *ChainAccess) networkRequest(rqFunc requestFunc, valFunc validatorFunc, ctx *OdrContext) (*Msg, error) { - + req := &sentReq{ + deliverChan: make(chan *Msg), + valFunc: valFunc, + } self.lock.Lock() - self.deliverChan = make(chan *Msg) - self.valFunc = valFunc + reqCnt := self.sentReqCnt + self.sentReqCnt++ + self.sentReqs[reqCnt] = req self.lock.Unlock() defer func() { self.lock.Lock() - self.deliverChan = nil - self.valFunc = nil + delete(self.sentReqs, reqCnt) self.lock.Unlock() }() - //fmt.Println("networkRequest") - var msg *Msg for { - peers := self.peers.BestPeers() - if len(peers) == 0 { - select { - case <-ctx.cancelOrTimeout: - return nil, ErrCancel - case <-time.After(retryPeers): + peers := self.peers.BestPeers() + if len(peers) == 0 { + select { + case <-ctx.cancelOrTimeout: + return nil, ErrCancel + case <-time.After(retryPeers): + } } - } - for _, peer := range peers { - rqFunc(peer) - select { - case <-ctx.cancelOrTimeout: - return nil, ErrCancel - case msg = <-self.deliverChan: - peer.Promote() - glog.V(LogLevel).Infof("networkRequest success") - return msg, nil - case <-time.After(requestTimeout): - peer.Demote() - glog.V(LogLevel).Infof("networkRequest timeout") + for _, peer := range peers { + rqFunc(peer) + select { + case <-ctx.cancelOrTimeout: + return nil, ErrCancel + case msg = <-req.deliverChan: + peer.Promote() + glog.V(LogLevel).Infof("networkRequest success") + return msg, nil + case <-time.After(requestTimeout): + peer.Demote() + glog.V(LogLevel).Infof("networkRequest timeout") + } } } - } } func (self *ChainAccess) Retrieve(obj ObjectAccess, ctx *OdrContext) (err error) { diff --git a/core/access/context.go b/core/access/context.go index 410d03e3e7..652c9a1cd6 100644 --- a/core/access/context.go +++ b/core/access/context.go @@ -24,19 +24,19 @@ import ( ) type OdrContext struct { - cancel, cancelOrTimeout chan struct{} - id *OdrChannelID - cancelled int32 + cancel, cancelOrTimeout chan struct{} + id *OdrChannelID + cancelled int32 } -var NullCtx = (*OdrContext)(nil) // used when creating states -var NoOdr = (*OdrContext)(nil) // used for individual requests +var NullCtx = (*OdrContext)(nil) // used when creating states +var NoOdr = (*OdrContext)(nil) // used for individual requests func NewContext(id *OdrChannelID) *OdrContext { ctx := &OdrContext{ - cancel: make(chan struct{}), + cancel: make(chan struct{}), cancelOrTimeout: make(chan struct{}), - id: id, + id: id, } go func() { select { @@ -70,4 +70,4 @@ type OdrChannelID struct { func NewChannelID(timeout time.Duration) *OdrChannelID { return &OdrChannelID{timeout: timeout} -} \ No newline at end of file +} diff --git a/core/blockchain.go b/core/blockchain.go index c036d9a472..5bb452869f 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -274,9 +274,6 @@ func (bc *BlockChain) SetHead(head uint64) { if bc.currentBlock == nil { bc.currentBlock = bc.genesisBlock } - bc.insert(bc.currentBlock) - } else { - bc.writeHeader(bc.currentHeader) } if bc.currentHeader == nil { bc.currentHeader = bc.genesisBlock.Header() diff --git a/core/blockchain_test.go b/core/blockchain_test.go index a82379446c..975d0c981b 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -800,7 +800,7 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) { // Import the chain as an archive node and ensure all pointers are updated archiveDb, _ := ethdb.NewMemDatabase() archiveCa := access.NewDbChainAccess(archiveDb) - + WriteGenesisBlockForTesting(archiveDb, GenesisAccount{address, funds}) archive, _ := NewBlockChain(archiveCa, FakePow{}, new(event.TypeMux)) archive.SetProcessor(NewBlockProcessor(archiveCa, FakePow{}, archive, new(event.TypeMux))) diff --git a/core/core_access.go b/core/core_access.go index 4c8d7f81fe..a5a108ceaf 100644 --- a/core/core_access.go +++ b/core/core_access.go @@ -22,7 +22,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/access" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" @@ -110,18 +109,13 @@ func (self *ReceiptsAccess) Valid(msg *access.Msg) bool { glog.V(access.LogLevel).Infof("ODR: invalid number of entries: %d", len(receipts)) return false } - data, err := rlp.EncodeToBytes(receipts[0]) - if err != nil { - glog.V(access.LogLevel).Infof("ODR: RLP encode error: %v", err) - return false - } - hash := crypto.Sha3Hash(data) + hash := types.DeriveSha(receipts[0]) header := GetHeader(self.db, self.blockHash) if header == nil { glog.V(access.LogLevel).Infof("ODR: header not found for block %08x", self.blockHash[:4]) return false } - if bytes.Compare(header.ReceiptHash[:], hash[:]) != 0 { + if !bytes.Equal(header.ReceiptHash[:], hash[:]) { glog.V(access.LogLevel).Infof("ODR: header receipts hash %08x does not match calculated RLP hash %08x", header.ReceiptHash[:4], hash[:4]) return false } diff --git a/core/state/state_access.go b/core/state/state_access.go index d112b28188..c4145b5791 100644 --- a/core/state/state_access.go +++ b/core/state/state_access.go @@ -19,7 +19,6 @@ package state import ( "bytes" - //"fmt" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/access" diff --git a/les/handler.go b/les/handler.go index 8855588e3c..aa3acf28a8 100644 --- a/les/handler.go +++ b/les/handler.go @@ -241,14 +241,14 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { defer msg.Discard() // Handle the message depending on its contents - switch { - case msg.Code == StatusMsg: + switch msg.Code { + case StatusMsg: glog.V(access.LogLevel).Infof("LES: received StatusMsg from peer %v", p.id) // Status messages should never arrive after the handshake return errResp(ErrExtraStatusMsg, "uncontrolled status message") // Block header query, collect the requested headers and reply - case msg.Code == GetBlockHeadersMsg: + case GetBlockHeadersMsg: glog.V(access.LogLevel).Infof("LES: received GetBlockHeadersMsg from peer %v", p.id) // Decode the complex header query var query getBlockHeadersData @@ -313,7 +313,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { } return p.SendBlockHeaders(headers) - case msg.Code == BlockHeadersMsg: + case BlockHeadersMsg: glog.V(access.LogLevel).Infof("LES: received BlockHeadersMsg from peer %v", p.id) // A batch of headers arrived to one of our previous requests var headers []*types.Header @@ -332,7 +332,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { } //} - case msg.Code == GetBlockBodiesMsg: + case GetBlockBodiesMsg: glog.V(access.LogLevel).Infof("LES: received GetBlockBodiesMsg from peer %v", p.id) // Decode the retrieval message msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size)) @@ -360,7 +360,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { } return p.SendBlockBodiesRLP(bodies) - case msg.Code == BlockBodiesMsg: + case BlockBodiesMsg: glog.V(access.LogLevel).Infof("LES: received BlockBodiesMsg from peer %v", p.id) // A batch of block bodies arrived to one of our previous requests var data []*types.Body @@ -372,7 +372,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { Obj: data, }) - case msg.Code == GetNodeDataMsg: + case GetNodeDataMsg: glog.V(access.LogLevel).Infof("LES: received GetNodeDataMsg from peer %v", p.id) // Decode the retrieval message msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size)) @@ -400,7 +400,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { } return p.SendNodeData(data) - case msg.Code == NodeDataMsg: + case NodeDataMsg: glog.V(access.LogLevel).Infof("LES: received NodeDataMsg from peer %v", p.id) // A batch of node state data arrived to one of our previous requests var data [][]byte @@ -412,7 +412,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { Obj: data, }) - case msg.Code == GetReceiptsMsg: + case GetReceiptsMsg: glog.V(access.LogLevel).Infof("LES: received GetReceiptsMsg from peer %v", p.id) // Decode the retrieval message msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size)) @@ -449,19 +449,19 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { } return p.SendReceiptsRLP(receipts) - case msg.Code == ReceiptsMsg: + case ReceiptsMsg: glog.V(access.LogLevel).Infof("LES: received ReceiptsMsg from peer %v", p.id) // A batch of receipts arrived to one of our previous requests - var receipts [][]*types.Receipt + var receipts []types.Receipts if err := msg.Decode(&receipts); err != nil { return errResp(ErrDecode, "msg %v: %v", msg, err) } pm.chainAccess.Deliver(p.id, &access.Msg{ MsgType: access.MsgReceipts, - Obj: receipts[0], + Obj: receipts, }) - case msg.Code == GetProofsMsg: + case GetProofsMsg: glog.V(access.LogLevel).Infof("LES: received GetProofsMsg from peer %v", p.id) // Decode the retrieval message msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size)) @@ -490,7 +490,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { } return p.SendProofs(proofs) - case msg.Code == ProofsMsg: + case ProofsMsg: glog.V(access.LogLevel).Infof("LES: received ProofsMsg from peer %v", p.id) // A batch of merkle proofs arrived to one of our previous requests var data []trie.MerkleProof @@ -502,7 +502,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { Obj: data, }) - case msg.Code == NewBlockHashesMsg: + case NewBlockHashesMsg: glog.V(access.LogLevel).Infof("LES: received NewBlockHashesMsg from peer %v", p.id) // Retrieve and deseralize the remote new block hashes notification type announce struct { diff --git a/les/handler_test.go b/les/handler_test.go new file mode 100644 index 0000000000..7b39924e31 --- /dev/null +++ b/les/handler_test.go @@ -0,0 +1,434 @@ +package les + +import ( + "fmt" + "math/big" + "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/access" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie" +) + +// Tests that block headers can be retrieved from a remote chain based on user queries. +func TestGetBlockHeadersLes1(t *testing.T) { testGetBlockHeaders(t, 1) } + +func testGetBlockHeaders(t *testing.T, protocol int) { + pm, _ := newTestProtocolManagerMust(t, false, downloader.MaxHashFetch+15, nil) + peer, _ := newTestPeer("peer", protocol, pm, true) + defer peer.close() + + // Create a "random" unknown hash for testing + var unknown common.Hash + for i, _ := range unknown { + unknown[i] = byte(i) + } + // Create a batch of tests for various scenarios + limit := uint64(downloader.MaxHeaderFetch) + tests := []struct { + query *getBlockHeadersData // The query to execute for header retrieval + expect []common.Hash // The hashes of the block whose headers are expected + }{ + // A single random block should be retrievable by hash and number too + { + &getBlockHeadersData{Origin: hashOrNumber{Hash: pm.blockchain.GetBlockByNumber(limit/2, access.NoOdr).Hash()}, Amount: 1}, + []common.Hash{pm.blockchain.GetBlockByNumber(limit/2, access.NoOdr).Hash()}, + }, { + &getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 1}, + []common.Hash{pm.blockchain.GetBlockByNumber(limit/2, access.NoOdr).Hash()}, + }, + // Multiple headers should be retrievable in both directions + { + &getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 3}, + []common.Hash{ + pm.blockchain.GetBlockByNumber(limit/2, access.NoOdr).Hash(), + pm.blockchain.GetBlockByNumber(limit/2+1, access.NoOdr).Hash(), + pm.blockchain.GetBlockByNumber(limit/2+2, access.NoOdr).Hash(), + }, + }, { + &getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 3, Reverse: true}, + []common.Hash{ + pm.blockchain.GetBlockByNumber(limit/2, access.NoOdr).Hash(), + pm.blockchain.GetBlockByNumber(limit/2-1, access.NoOdr).Hash(), + pm.blockchain.GetBlockByNumber(limit/2-2, access.NoOdr).Hash(), + }, + }, + // Multiple headers with skip lists should be retrievable + { + &getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3}, + []common.Hash{ + pm.blockchain.GetBlockByNumber(limit/2, access.NoOdr).Hash(), + pm.blockchain.GetBlockByNumber(limit/2+4, access.NoOdr).Hash(), + pm.blockchain.GetBlockByNumber(limit/2+8, access.NoOdr).Hash(), + }, + }, { + &getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3, Reverse: true}, + []common.Hash{ + pm.blockchain.GetBlockByNumber(limit/2, access.NoOdr).Hash(), + pm.blockchain.GetBlockByNumber(limit/2-4, access.NoOdr).Hash(), + pm.blockchain.GetBlockByNumber(limit/2-8, access.NoOdr).Hash(), + }, + }, + // The chain endpoints should be retrievable + { + &getBlockHeadersData{Origin: hashOrNumber{Number: 0}, Amount: 1}, + []common.Hash{pm.blockchain.GetBlockByNumber(0, access.NoOdr).Hash()}, + }, { + &getBlockHeadersData{Origin: hashOrNumber{Number: pm.blockchain.CurrentBlock().NumberU64()}, Amount: 1}, + []common.Hash{pm.blockchain.CurrentBlock().Hash()}, + }, + // Ensure protocol limits are honored + { + &getBlockHeadersData{Origin: hashOrNumber{Number: pm.blockchain.CurrentBlock().NumberU64() - 1}, Amount: limit + 10, Reverse: true}, + pm.blockchain.GetBlockHashesFromHash(pm.blockchain.CurrentBlock().Hash(), limit), + }, + // Check that requesting more than available is handled gracefully + { + &getBlockHeadersData{Origin: hashOrNumber{Number: pm.blockchain.CurrentBlock().NumberU64() - 4}, Skip: 3, Amount: 3}, + []common.Hash{ + pm.blockchain.GetBlockByNumber(pm.blockchain.CurrentBlock().NumberU64()-4, access.NoOdr).Hash(), + pm.blockchain.GetBlockByNumber(pm.blockchain.CurrentBlock().NumberU64(), access.NoOdr).Hash(), + }, + }, { + &getBlockHeadersData{Origin: hashOrNumber{Number: 4}, Skip: 3, Amount: 3, Reverse: true}, + []common.Hash{ + pm.blockchain.GetBlockByNumber(4, access.NoOdr).Hash(), + pm.blockchain.GetBlockByNumber(0, access.NoOdr).Hash(), + }, + }, + // Check that requesting more than available is handled gracefully, even if mid skip + { + &getBlockHeadersData{Origin: hashOrNumber{Number: pm.blockchain.CurrentBlock().NumberU64() - 4}, Skip: 2, Amount: 3}, + []common.Hash{ + pm.blockchain.GetBlockByNumber(pm.blockchain.CurrentBlock().NumberU64()-4, access.NoOdr).Hash(), + pm.blockchain.GetBlockByNumber(pm.blockchain.CurrentBlock().NumberU64()-1, access.NoOdr).Hash(), + }, + }, { + &getBlockHeadersData{Origin: hashOrNumber{Number: 4}, Skip: 2, Amount: 3, Reverse: true}, + []common.Hash{ + pm.blockchain.GetBlockByNumber(4, access.NoOdr).Hash(), + pm.blockchain.GetBlockByNumber(1, access.NoOdr).Hash(), + }, + }, + // Check that non existing headers aren't returned + { + &getBlockHeadersData{Origin: hashOrNumber{Hash: unknown}, Amount: 1}, + []common.Hash{}, + }, { + &getBlockHeadersData{Origin: hashOrNumber{Number: pm.blockchain.CurrentBlock().NumberU64() + 1}, Amount: 1}, + []common.Hash{}, + }, + } + // Run each of the tests and verify the results against the chain + for i, tt := range tests { + // Collect the headers to expect in the response + headers := []*types.Header{} + for _, hash := range tt.expect { + headers = append(headers, pm.blockchain.GetBlock(hash, access.NoOdr).Header()) + } + // Send the hash request and verify the response + p2p.Send(peer.app, GetBlockHeadersMsg, tt.query) + if err := p2p.ExpectMsg(peer.app, BlockHeadersMsg, headers); err != nil { + t.Errorf("test %d: headers mismatch: %v", i, err) + } + } +} + +// Tests that block contents can be retrieved from a remote chain based on their hashes. +func TestGetBlockBodiesLes1(t *testing.T) { testGetBlockBodies(t, 1) } + +func testGetBlockBodies(t *testing.T, protocol int) { + pm, _ := newTestProtocolManagerMust(t, false, downloader.MaxBlockFetch+15, nil) + peer, _ := newTestPeer("peer", protocol, pm, true) + defer peer.close() + + // Create a batch of tests for various scenarios + limit := downloader.MaxBlockFetch + tests := []struct { + random int // Number of blocks to fetch randomly from the chain + explicit []common.Hash // Explicitly requested blocks + available []bool // Availability of explicitly requested blocks + expected int // Total number of existing blocks to expect + }{ + {1, nil, nil, 1}, // A single random block should be retrievable + {10, nil, nil, 10}, // Multiple random blocks should be retrievable + {limit, nil, nil, limit}, // The maximum possible blocks should be retrievable + {limit + 1, nil, nil, limit}, // No more than the possible block count should be returned + {0, []common.Hash{pm.blockchain.Genesis().Hash()}, []bool{true}, 1}, // The genesis block should be retrievable + {0, []common.Hash{pm.blockchain.CurrentBlock().Hash()}, []bool{true}, 1}, // The chains head block should be retrievable + {0, []common.Hash{common.Hash{}}, []bool{false}, 0}, // A non existent block should not be returned + + // Existing and non-existing blocks interleaved should not cause problems + {0, []common.Hash{ + common.Hash{}, + pm.blockchain.GetBlockByNumber(1, access.NoOdr).Hash(), + common.Hash{}, + pm.blockchain.GetBlockByNumber(10, access.NoOdr).Hash(), + common.Hash{}, + pm.blockchain.GetBlockByNumber(100, access.NoOdr).Hash(), + common.Hash{}, + }, []bool{false, true, false, true, false, true, false}, 3}, + } + // Run each of the tests and verify the results against the chain + for i, tt := range tests { + // Collect the hashes to request, and the response to expect + hashes, seen := []common.Hash{}, make(map[int64]bool) + bodies := []*types.Body{} + + for j := 0; j < tt.random; j++ { + for { + num := rand.Int63n(int64(pm.blockchain.CurrentBlock().NumberU64())) + if !seen[num] { + seen[num] = true + + block := pm.blockchain.GetBlockByNumber(uint64(num), access.NoOdr) + hashes = append(hashes, block.Hash()) + if len(bodies) < tt.expected { + bodies = append(bodies, &types.Body{Transactions: block.Transactions(), Uncles: block.Uncles()}) + } + break + } + } + } + for j, hash := range tt.explicit { + hashes = append(hashes, hash) + if tt.available[j] && len(bodies) < tt.expected { + block := pm.blockchain.GetBlock(hash, access.NoOdr) + bodies = append(bodies, &types.Body{Transactions: block.Transactions(), Uncles: block.Uncles()}) + } + } + // Send the hash request and verify the response + p2p.Send(peer.app, GetBlockBodiesMsg, hashes) + if err := p2p.ExpectMsg(peer.app, BlockBodiesMsg, bodies); err != nil { + t.Errorf("test %d: bodies mismatch: %v", i, err) + } + } +} + +// Tests that the node state database can be retrieved based on hashes. +func TestGetNodeDataLes1(t *testing.T) { testGetNodeData(t, 1) } + +func testGetNodeData(t *testing.T, protocol int) { + // Define three accounts to simulate transactions with + acc1Key, _ := crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + acc2Key, _ := crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") + acc1Addr := crypto.PubkeyToAddress(acc1Key.PublicKey) + acc2Addr := crypto.PubkeyToAddress(acc2Key.PublicKey) + + // Create a chain generator with some simple transactions (blatantly stolen from @fjl/chain_makerts_test) + generator := func(i int, block *core.BlockGen) { + switch i { + case 0: + // In block 1, the test bank sends account #1 some ether. + tx, _ := types.NewTransaction(block.TxNonce(testBankAddress), acc1Addr, big.NewInt(10000), params.TxGas, nil, nil).SignECDSA(testBankKey) + block.AddTx(tx) + case 1: + // In block 2, the test bank sends some more ether to account #1. + // acc1Addr passes it on to account #2. + tx1, _ := types.NewTransaction(block.TxNonce(testBankAddress), acc1Addr, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(testBankKey) + tx2, _ := types.NewTransaction(block.TxNonce(acc1Addr), acc2Addr, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(acc1Key) + block.AddTx(tx1) + block.AddTx(tx2) + case 2: + // Block 3 is empty but was mined by account #2. + block.SetCoinbase(acc2Addr) + block.SetExtra([]byte("yeehaw")) + case 3: + // Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data). + b2 := block.PrevBlock(1).Header() + b2.Extra = []byte("foo") + block.AddUncle(b2) + b3 := block.PrevBlock(2).Header() + b3.Extra = []byte("foo") + block.AddUncle(b3) + } + } + // Assemble the test environment + pm, _ := newTestProtocolManagerMust(t, false, 4, generator) + peer, _ := newTestPeer("peer", protocol, pm, true) + defer peer.close() + + // Fetch for now the entire chain db + hashes := []common.Hash{} + for _, key := range pm.chainAccess.Db().(*ethdb.MemDatabase).Keys() { + if len(key) == len(common.Hash{}) { + hashes = append(hashes, common.BytesToHash(key)) + } + } + p2p.Send(peer.app, GetNodeDataMsg, hashes) + msg, err := peer.app.ReadMsg() + if err != nil { + t.Fatalf("failed to read node data response: %v", err) + } + if msg.Code != NodeDataMsg { + t.Fatalf("response packet code mismatch: have %x, want %x", msg.Code, 0x0c) + } + var data [][]byte + if err := msg.Decode(&data); err != nil { + t.Fatalf("failed to decode response node data: %v", err) + } + // Verify that all hashes correspond to the requested data, and reconstruct a state tree + for i, want := range hashes { + if hash := crypto.Sha3Hash(data[i]); hash != want { + fmt.Errorf("data hash mismatch: have %x, want %x", hash, want) + } + } + statedb, _ := ethdb.NewMemDatabase() + for i := 0; i < len(data); i++ { + statedb.Put(hashes[i].Bytes(), data[i]) + } + accounts := []common.Address{testBankAddress, acc1Addr, acc2Addr} + for i := uint64(0); i <= pm.blockchain.CurrentBlock().NumberU64(); i++ { + trie, _ := state.New(pm.blockchain.GetBlockByNumber(i, access.NoOdr).Root(), access.NewDbChainAccess(statedb), access.NullCtx) + + for j, acc := range accounts { + state, _ := pm.blockchain.State(access.NullCtx) + bw := state.GetBalance(acc) + bh := trie.GetBalance(acc) + + if (bw != nil && bh == nil) || (bw == nil && bh != nil) { + t.Errorf("test %d, account %d: balance mismatch: have %v, want %v", i, j, bh, bw) + } + if bw != nil && bh != nil && bw.Cmp(bw) != 0 { + t.Errorf("test %d, account %d: balance mismatch: have %v, want %v", i, j, bh, bw) + } + } + } +} + +// Tests that the transaction receipts can be retrieved based on hashes. +func TestGetReceiptLes1(t *testing.T) { testGetReceipt(t, 1) } + +func testGetReceipt(t *testing.T, protocol int) { + // Define three accounts to simulate transactions with + acc1Key, _ := crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + acc2Key, _ := crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") + acc1Addr := crypto.PubkeyToAddress(acc1Key.PublicKey) + acc2Addr := crypto.PubkeyToAddress(acc2Key.PublicKey) + + // Create a chain generator with some simple transactions (blatantly stolen from @fjl/chain_makerts_test) + generator := func(i int, block *core.BlockGen) { + switch i { + case 0: + // In block 1, the test bank sends account #1 some ether. + tx, _ := types.NewTransaction(block.TxNonce(testBankAddress), acc1Addr, big.NewInt(10000), params.TxGas, nil, nil).SignECDSA(testBankKey) + block.AddTx(tx) + case 1: + // In block 2, the test bank sends some more ether to account #1. + // acc1Addr passes it on to account #2. + tx1, _ := types.NewTransaction(block.TxNonce(testBankAddress), acc1Addr, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(testBankKey) + tx2, _ := types.NewTransaction(block.TxNonce(acc1Addr), acc2Addr, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(acc1Key) + block.AddTx(tx1) + block.AddTx(tx2) + case 2: + // Block 3 is empty but was mined by account #2. + block.SetCoinbase(acc2Addr) + block.SetExtra([]byte("yeehaw")) + case 3: + // Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data). + b2 := block.PrevBlock(1).Header() + b2.Extra = []byte("foo") + block.AddUncle(b2) + b3 := block.PrevBlock(2).Header() + b3.Extra = []byte("foo") + block.AddUncle(b3) + } + } + // Assemble the test environment + pm, _ := newTestProtocolManagerMust(t, false, 4, generator) + peer, _ := newTestPeer("peer", protocol, pm, true) + defer peer.close() + + // Collect the hashes to request, and the response to expect + hashes, receipts := []common.Hash{}, []types.Receipts{} + for i := uint64(0); i <= pm.blockchain.CurrentBlock().NumberU64(); i++ { + block := pm.blockchain.GetBlockByNumber(i, access.NoOdr) + + hashes = append(hashes, block.Hash()) + receipts = append(receipts, core.GetBlockReceipts(pm.chainAccess, block.Hash(), access.NoOdr)) + } + // Send the hash request and verify the response + p2p.Send(peer.app, GetReceiptsMsg, hashes) + if err := p2p.ExpectMsg(peer.app, ReceiptsMsg, receipts); err != nil { + t.Errorf("receipts mismatch: %v", err) + } +} + +// Tests that trie merkle proofs can be retrieved +func TestGetProofsLes1(t *testing.T) { testGetReceipt(t, 1) } + +func testGetProofs(t *testing.T, protocol int) { + // Define three accounts to simulate transactions with + acc1Key, _ := crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + acc2Key, _ := crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") + acc1Addr := crypto.PubkeyToAddress(acc1Key.PublicKey) + acc2Addr := crypto.PubkeyToAddress(acc2Key.PublicKey) + + // Create a chain generator with some simple transactions (blatantly stolen from @fjl/chain_makerts_test) + generator := func(i int, block *core.BlockGen) { + switch i { + case 0: + // In block 1, the test bank sends account #1 some ether. + tx, _ := types.NewTransaction(block.TxNonce(testBankAddress), acc1Addr, big.NewInt(10000), params.TxGas, nil, nil).SignECDSA(testBankKey) + block.AddTx(tx) + case 1: + // In block 2, the test bank sends some more ether to account #1. + // acc1Addr passes it on to account #2. + tx1, _ := types.NewTransaction(block.TxNonce(testBankAddress), acc1Addr, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(testBankKey) + tx2, _ := types.NewTransaction(block.TxNonce(acc1Addr), acc2Addr, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(acc1Key) + block.AddTx(tx1) + block.AddTx(tx2) + case 2: + // Block 3 is empty but was mined by account #2. + block.SetCoinbase(acc2Addr) + block.SetExtra([]byte("yeehaw")) + case 3: + // Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data). + b2 := block.PrevBlock(1).Header() + b2.Extra = []byte("foo") + block.AddUncle(b2) + b3 := block.PrevBlock(2).Header() + b3.Extra = []byte("foo") + block.AddUncle(b3) + } + } + // Assemble the test environment + pm, ca := newTestProtocolManagerMust(t, false, 4, generator) + db := ca.Db() + peer, _ := newTestPeer("peer", protocol, pm, true) + defer peer.close() + + var proofreqs []access.ProofReq + var proofs []trie.MerkleProof + + accounts := []common.Address{testBankAddress, acc1Addr, acc2Addr, common.Address{}} + for i := uint64(0); i <= pm.blockchain.CurrentBlock().NumberU64(); i++ { + root := pm.blockchain.GetBlockByNumber(i, access.NoOdr).Root() + trie, _ := trie.NewSecure(root, db, nil) + + for _, acc := range accounts { + req := access.ProofReq{ + Root: root, + Key: acc[:], + } + proofreqs = append(proofreqs, req) + + proof := trie.Prove(acc[:]) + proofs = append(proofs, proof) + } + } + // Send the proof request and verify the response + p2p.Send(peer.app, GetProofsMsg, proofreqs) + if err := p2p.ExpectMsg(peer.app, ProofsMsg, proofs); err != nil { + t.Errorf("receipts mismatch: %v", err) + } +} diff --git a/les/helper_test.go b/les/helper_test.go new file mode 100644 index 0000000000..edde9a36ac --- /dev/null +++ b/les/helper_test.go @@ -0,0 +1,194 @@ +// This file contains some shares testing functionality, common to multiple +// different files and modules being tested. + +package les + +import ( + "crypto/rand" + "math/big" + "sync" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/access" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/discover" +) + +var ( + testBankKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey) + testBankFunds = big.NewInt(1000000) +) + +// newTestProtocolManager creates a new protocol manager for testing purposes, +// with the given number of blocks already known, and potential notification +// channels for different events. +func newTestProtocolManager(mode Mode, blocks int, generator func(int, *core.BlockGen)) (*ProtocolManager, *access.ChainAccess, error) { + var ( + evmux = new(event.TypeMux) + pow = new(core.FakePow) + db, _ = ethdb.NewMemDatabase() + ca = access.NewChainAccess(db, mode == LightMode) + genesis = core.WriteGenesisBlockForTesting(db, core.GenesisAccount{testBankAddress, testBankFunds}) + blockchain, _ = core.NewBlockChain(ca, pow, evmux) + blockproc = core.NewBlockProcessor(ca, pow, blockchain, evmux) + ) + blockchain.SetProcessor(blockproc) + chain, _ := core.GenerateChain(genesis, db, blocks, generator) + if _, err := blockchain.InsertChain(chain); err != nil { + panic(err) + } + pm, err := NewProtocolManager(mode, NetworkId, evmux, pow, blockchain, ca) + if err != nil { + return nil, nil, err + } + pm.Start() + return pm, ca, nil +} + +// newTestProtocolManagerMust creates a new protocol manager for testing purposes, +// with the given number of blocks already known, and potential notification +// channels for different events. In case of an error, the constructor force- +// fails the test. +func newTestProtocolManagerMust(t *testing.T, lightSync bool, blocks int, generator func(int, *core.BlockGen)) (*ProtocolManager, *access.ChainAccess) { + mode := ArchiveMode + if lightSync { + mode = LightMode + } + pm, ca, err := newTestProtocolManager(mode, blocks, generator) + if err != nil { + t.Fatalf("Failed to create protocol manager: %v", err) + } + return pm, ca +} + +// testTxPool is a fake, helper transaction pool for testing purposes +type testTxPool struct { + pool []*types.Transaction // Collection of all transactions + added chan<- []*types.Transaction // Notification channel for new transactions + + lock sync.RWMutex // Protects the transaction pool +} + +// AddTransactions appends a batch of transactions to the pool, and notifies any +// listeners if the addition channel is non nil +func (p *testTxPool) AddTransactions(txs []*types.Transaction) { + p.lock.Lock() + defer p.lock.Unlock() + + p.pool = append(p.pool, txs...) + if p.added != nil { + p.added <- txs + } +} + +// GetTransactions returns all the transactions known to the pool +func (p *testTxPool) GetTransactions() types.Transactions { + p.lock.RLock() + defer p.lock.RUnlock() + + txs := make([]*types.Transaction, len(p.pool)) + copy(txs, p.pool) + + return txs +} + +// newTestTransaction create a new dummy transaction. +func newTestTransaction(from *crypto.Key, nonce uint64, datasize int) *types.Transaction { + tx := types.NewTransaction(nonce, common.Address{}, big.NewInt(0), big.NewInt(100000), big.NewInt(0), make([]byte, datasize)) + tx, _ = tx.SignECDSA(from.PrivateKey) + + return tx +} + +// testPeer is a simulated peer to allow testing direct network calls. +type testPeer struct { + net p2p.MsgReadWriter // Network layer reader/writer to simulate remote messaging + app *p2p.MsgPipeRW // Application layer reader/writer to simulate the local side + *peer +} + +// newTestPeer creates a new peer registered at the given protocol manager. +func newTestPeer(name string, version int, pm *ProtocolManager, shake bool) (*testPeer, <-chan error) { + // Create a message pipe to communicate through + app, net := p2p.MsgPipe() + + // Generate a random id and create the peer + var id discover.NodeID + rand.Read(id[:]) + + peer := pm.newPeer(version, NetworkId, p2p.NewPeer(id, name, nil), net) + + // Start the peer on a new thread + errc := make(chan error, 1) + go func() { + pm.newPeerCh <- peer + errc <- pm.handle(peer) + }() + tp := &testPeer{ + app: app, + net: net, + peer: peer, + } + // Execute any implicitly requested handshakes and return + if shake { + td, head, genesis := pm.blockchain.Status() + tp.handshake(nil, td, head, genesis) + } + return tp, errc +} + +func newTestPeerPair(name string, version int, pm, pm2 *ProtocolManager) (*peer, <-chan error, *peer, <-chan error) { + // Create a message pipe to communicate through + app, net := p2p.MsgPipe() + + // Generate a random id and create the peer + var id discover.NodeID + rand.Read(id[:]) + + peer := pm.newPeer(version, NetworkId, p2p.NewPeer(id, name, nil), net) + peer2 := pm2.newPeer(version, NetworkId, p2p.NewPeer(id, name, nil), app) + + // Start the peer on a new thread + errc := make(chan error, 1) + errc2 := make(chan error, 1) + go func() { + pm.newPeerCh <- peer + errc <- pm.handle(peer) + }() + go func() { + pm2.newPeerCh <- peer2 + errc2 <- pm2.handle(peer2) + }() + return peer, errc, peer2, errc2 +} + +// handshake simulates a trivial handshake that expects the same state from the +// remote side as we are simulating locally. +func (p *testPeer) handshake(t *testing.T, td *big.Int, head common.Hash, genesis common.Hash) { + msg := &statusData{ + ProtocolVersion: uint32(p.version), + NetworkId: uint32(NetworkId), + TD: td, + CurrentBlock: head, + GenesisBlock: genesis, + } + if err := p2p.ExpectMsg(p.app, StatusMsg, msg); err != nil { + t.Fatalf("status recv: %v", err) + } + if err := p2p.Send(p.app, StatusMsg, msg); err != nil { + t.Fatalf("status send: %v", err) + } +} + +// close terminates the local side of the peer, notifying the remote protocol +// manager of termination. +func (p *testPeer) close() { + p.app.Close() +} diff --git a/les/odr_test.go b/les/odr_test.go new file mode 100644 index 0000000000..6f889ca070 --- /dev/null +++ b/les/odr_test.go @@ -0,0 +1,140 @@ +package les + +import ( + "bytes" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/access" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" +) + +type odrTestFn func(ca *access.ChainAccess, bc *core.BlockChain, ctx *access.OdrContext, bhash common.Hash) []byte + +func TestOdrGetBlockLes1(t *testing.T) { testOdr(t, 1, odrGetBlock) } + +func odrGetBlock(ca *access.ChainAccess, bc *core.BlockChain, ctx *access.OdrContext, bhash common.Hash) []byte { + block := bc.GetBlock(bhash, ctx) + if block == nil { + return nil + } + rlp, _ := rlp.EncodeToBytes(block) + return rlp +} + +func TestOdrGetReceiptsLes1(t *testing.T) { testOdr(t, 1, odrGetReceipts) } + +func odrGetReceipts(ca *access.ChainAccess, bc *core.BlockChain, ctx *access.OdrContext, bhash common.Hash) []byte { + receipts := core.GetBlockReceipts(ca, bhash, ctx) + if receipts == nil { + return nil + } + rlp, _ := rlp.EncodeToBytes(receipts) + return rlp +} + +func TestOdrAccountsLes1(t *testing.T) { testOdr(t, 1, odrAccounts) } + +func odrAccounts(ca *access.ChainAccess, bc *core.BlockChain, ctx *access.OdrContext, bhash common.Hash) []byte { + acc1Key, _ := crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + acc2Key, _ := crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") + acc1Addr := crypto.PubkeyToAddress(acc1Key.PublicKey) + acc2Addr := crypto.PubkeyToAddress(acc2Key.PublicKey) + dummyAddr := common.HexToAddress("1234567812345678123456781234567812345678") + acc := []common.Address{ testBankAddress, acc1Addr, acc2Addr, dummyAddr} + + trie.ClearGlobalCache() + + var res []byte + for _, addr := range acc { + header := bc.GetHeader(bhash) + st, err := state.New(header.Root, ca, ctx) + if err == nil { + bal := st.GetBalance(addr) + rlp, _ := rlp.EncodeToBytes(bal) + res = append(res, rlp...) + } + } + + return res +} + +func testOdr(t *testing.T, protocol int, fn odrTestFn) { + // Define accounts to simulate transactions with + acc1Key, _ := crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + acc2Key, _ := crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") + acc1Addr := crypto.PubkeyToAddress(acc1Key.PublicKey) + acc2Addr := crypto.PubkeyToAddress(acc2Key.PublicKey) + + // Create a chain generator with some simple transactions (blatantly stolen from @fjl/chain_makerts_test) + generator := func(i int, block *core.BlockGen) { + switch i { + case 0: + // In block 1, the test bank sends account #1 some ether. + tx, _ := types.NewTransaction(block.TxNonce(testBankAddress), acc1Addr, big.NewInt(10000), params.TxGas, nil, nil).SignECDSA(testBankKey) + block.AddTx(tx) + case 1: + // In block 2, the test bank sends some more ether to account #1. + // acc1Addr passes it on to account #2. + tx1, _ := types.NewTransaction(block.TxNonce(testBankAddress), acc1Addr, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(testBankKey) + tx2, _ := types.NewTransaction(block.TxNonce(acc1Addr), acc2Addr, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(acc1Key) + block.AddTx(tx1) + block.AddTx(tx2) + case 2: + // Block 3 is empty but was mined by account #2. + block.SetCoinbase(acc2Addr) + block.SetExtra([]byte("yeehaw")) + case 3: + // Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data). + b2 := block.PrevBlock(1).Header() + b2.Extra = []byte("foo") + block.AddUncle(b2) + b3 := block.PrevBlock(2).Header() + b3.Extra = []byte("foo") + block.AddUncle(b3) + } + } + // Assemble the test environment + pm, ca := newTestProtocolManagerMust(t, false, 4, generator) + lpm, lca := newTestProtocolManagerMust(t, true, 0, nil) + _, _, lpeer, _ := newTestPeerPair("peer", protocol, pm, lpm) + time.Sleep(time.Millisecond * 100) + lpm.synchronise(lpeer) + + cid := access.NewChannelID(time.Millisecond * 200) + + test := func(expFail uint64) { + for i := uint64(0); i <= pm.blockchain.CurrentBlock().NumberU64(); i++ { + bhash := core.GetCanonicalHash(ca.Db(), i) + b1 := fn(ca, pm.blockchain, access.NoOdr, bhash) + b2 := fn(lca, lpm.blockchain, access.NewContext(cid), bhash) + eq := bytes.Equal(b1, b2) + exp := i < expFail + if exp && !eq { + t.Errorf("odr mismatch") + } + if !exp && eq { + t.Errorf("unexpected odr match") + } + } + } + + // temporarily remove peer to test odr fails + lca.UnregisterPeer(lpeer.id) + // expect retrievals to fail (except genesis block) without a les peer + test(1) + lca.RegisterPeer(lpeer.id, lpeer.version, lpeer.Head(), lpeer.RequestBodies, lpeer.RequestNodeData, lpeer.RequestReceipts, lpeer.RequestProofs) + // expect all retrievals to pass + test(5) + lca.UnregisterPeer(lpeer.id) + // still expect all retrievals to pass, now data should be cached locally + test(5) +} \ No newline at end of file diff --git a/les/protocol.go b/les/protocol.go index 41461580c2..33222164ba 100644 --- a/les/protocol.go +++ b/les/protocol.go @@ -66,16 +66,16 @@ const ( // Protocol messages belonging to LPV1 StatusMsg = 0x00 NewBlockHashesMsg = 0x01 - GetBlockHeadersMsg = 0x03 - BlockHeadersMsg = 0x04 - GetBlockBodiesMsg = 0x05 - BlockBodiesMsg = 0x06 - GetNodeDataMsg = 0x0d - NodeDataMsg = 0x0e - GetReceiptsMsg = 0x0f - ReceiptsMsg = 0x10 - GetProofsMsg = 0x11 - ProofsMsg = 0x12 + GetBlockHeadersMsg = 0x02 + BlockHeadersMsg = 0x03 + GetBlockBodiesMsg = 0x04 + BlockBodiesMsg = 0x05 + GetNodeDataMsg = 0x06 + NodeDataMsg = 0x07 + GetReceiptsMsg = 0x08 + ReceiptsMsg = 0x09 + GetProofsMsg = 0x0a + ProofsMsg = 0x0b ) type errCode int diff --git a/trie/arc.go b/trie/arc.go index 9da012e168..229420a538 100644 --- a/trie/arc.go +++ b/trie/arc.go @@ -62,6 +62,17 @@ func newARC(c int) *arc { } } +func (a *arc) Clear() { + a.mutex.Lock() + defer a.mutex.Unlock() + a.p = 0 + a.t1 = list.New() + a.b1 = list.New() + a.t2 = list.New() + a.b2 = list.New() + a.cache = make(map[string]*entry, a.c) +} + // Put inserts a new key-value pair into the cache. // This optimizes future access to this entry (side effect). func (a *arc) Put(key hashNode, value node) bool { diff --git a/trie/trie.go b/trie/trie.go index d1a5d6b854..b099b27b6b 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -45,6 +45,10 @@ var ( emptyState = crypto.Sha3Hash(nil) ) +func ClearGlobalCache() { + globalCache.Clear() +} + var ErrMissingRoot = errors.New("missing root node") var ErrTrieOdrFailure = errors.New("can't commit to db because of ODR failure")