diff --git a/eth/backend.go b/eth/backend.go index fbf4dd7bbc..52ec40f5ae 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -481,6 +481,7 @@ func (s *Ethereum) EthVersion() int { return int(s.protocolMa func (s *Ethereum) NetVersion() uint64 { return s.networkID } func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader } func (s *Ethereum) Synced() bool { return atomic.LoadUint32(&s.protocolManager.acceptTxs) == 1 } +func (s *Ethereum) ArchiveMode() bool { return s.config.NoPruning } // Protocols implements node.Service, returning all the currently configured // network protocols to start. diff --git a/les/handler.go b/les/handler.go index 59bfd81cd7..32f1903d1c 100644 --- a/les/handler.go +++ b/les/handler.go @@ -698,6 +698,13 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { p.Log().Warn("Failed to retrieve header for code", "block", *number, "hash", request.BHash) continue } + // Refuse to search stale state data in the database since looking for + // a non-exist key is kind of expensive. + local := pm.blockchain.CurrentHeader().Number.Uint64() + if !pm.server.archiveMode && header.Number.Uint64()+core.TriesInMemory <= local { + p.Log().Debug("Reject stale code request", "number", header.Number.Uint64(), "head", local) + continue + } triedb := pm.blockchain.StateCache().TrieDB() account, err := pm.getAccount(triedb, header.Root, common.BytesToHash(request.AccKey)) @@ -852,6 +859,13 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { p.Log().Warn("Failed to retrieve header for proof", "block", *number, "hash", request.BHash) continue } + // Refuse to search stale state data in the database since looking for + // a non-exist key is kind of expensive. + local := pm.blockchain.CurrentHeader().Number.Uint64() + if !pm.server.archiveMode && header.Number.Uint64()+core.TriesInMemory <= local { + p.Log().Debug("Reject stale trie request", "number", header.Number.Uint64(), "head", local) + continue + } root = header.Root } // Open the account or storage trie for the request diff --git a/les/handler_test.go b/les/handler_test.go index 6e24165cf6..51f0a1a0e2 100644 --- a/les/handler_test.go +++ b/les/handler_test.go @@ -278,6 +278,31 @@ func testGetCode(t *testing.T, protocol int) { } } +// Tests that the stale contract codes can't be retrieved based on account addresses. +func TestGetStaleCodeLes2(t *testing.T) { testGetStaleCode(t, 2) } +func TestGetStaleCodeLes3(t *testing.T) { testGetStaleCode(t, 3) } + +func testGetStaleCode(t *testing.T, protocol int) { + server, tearDown := newServerEnv(t, core.TriesInMemory+4, protocol, nil) + defer tearDown() + bc := server.pm.blockchain.(*core.BlockChain) + + check := func(number uint64, expected [][]byte) { + req := &CodeReq{ + BHash: bc.GetHeaderByNumber(number).Hash(), + AccKey: crypto.Keccak256(testContractAddr[:]), + } + cost := server.tPeer.GetRequestCost(GetCodeMsg, 1) + sendRequest(server.tPeer.app, GetCodeMsg, 42, cost, []*CodeReq{req}) + if err := expectResponse(server.tPeer.app, CodeMsg, 42, testBufLimit, expected); err != nil { + t.Errorf("codes mismatch: %v", err) + } + } + check(0, [][]byte{}) // Non-exist contract + check(testContractDeployed, [][]byte{}) // Stale contract + check(bc.CurrentHeader().Number.Uint64(), [][]byte{testContractCodeDeployed}) // Fresh contract +} + // Tests that the transaction receipts can be retrieved based on hashes. func TestGetReceiptLes2(t *testing.T) { testGetReceipt(t, 2) } @@ -338,6 +363,43 @@ func testGetProofs(t *testing.T, protocol int) { } } +// Tests that the stale contract codes can't be retrieved based on account addresses. +func TestGetStaleProofLes2(t *testing.T) { testGetStaleProof(t, 2) } +func TestGetStaleProofLes3(t *testing.T) { testGetStaleProof(t, 3) } + +func testGetStaleProof(t *testing.T, protocol int) { + server, tearDown := newServerEnv(t, core.TriesInMemory+4, protocol, nil) + defer tearDown() + bc := server.pm.blockchain.(*core.BlockChain) + + check := func(number uint64, wantOK bool) { + var ( + header = bc.GetHeaderByNumber(number) + account = crypto.Keccak256(testBankAddress.Bytes()) + ) + req := &ProofReq{ + BHash: header.Hash(), + Key: account, + } + cost := server.tPeer.GetRequestCost(GetProofsV2Msg, 1) + sendRequest(server.tPeer.app, GetProofsV2Msg, 42, cost, []*ProofReq{req}) + + var expected []rlp.RawValue + if wantOK { + proofsV2 := light.NewNodeSet() + t, _ := trie.New(header.Root, trie.NewDatabase(server.db)) + t.Prove(crypto.Keccak256(account), 0, proofsV2) + expected = proofsV2.NodeList() + } + if err := expectResponse(server.tPeer.app, ProofsV2Msg, 42, testBufLimit, expected); err != nil { + t.Errorf("codes mismatch: %v", err) + } + } + check(0, false) // Non-exist proof + check(2, false) // Stale proof + check(bc.CurrentHeader().Number.Uint64(), true) // Fresh proof +} + // Tests that CHT proofs can be correctly retrieved. func TestGetCHTProofsLes2(t *testing.T) { testGetCHTProofs(t, 2) } diff --git a/les/peer.go b/les/peer.go index 6792d0611e..56d316f505 100644 --- a/les/peer.go +++ b/les/peer.go @@ -551,7 +551,14 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis send = send.add("serveHeaders", nil) send = send.add("serveChainSince", uint64(0)) send = send.add("serveStateSince", uint64(0)) - send = send.add("serveRecentState", uint64(core.TriesInMemory-4)) + + // If local ethereum node is running in archive mode, advertise ourselves we have + // all version state data. Otherwise only recent state is available. + stateRecent := uint64(core.TriesInMemory - 4) + if server.archiveMode { + stateRecent = 0 + } + send = send.add("serveRecentState", stateRecent) send = send.add("txRelay", nil) } send = send.add("flowControl/BL", server.defParams.BufLimit) diff --git a/les/server.go b/les/server.go index 836fa0d552..fbdf6cf1ee 100644 --- a/les/server.go +++ b/les/server.go @@ -51,6 +51,8 @@ const ( type LesServer struct { lesCommons + archiveMode bool // Flag whether the ethereum node runs in archive mode. + fcManager *flowcontrol.ClientManager // nil if our node is client only costTracker *costTracker testCost uint64 @@ -93,7 +95,8 @@ func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) { nil, quitSync, new(sync.WaitGroup), - config.ULC, eth.Synced) + config.ULC, + eth.Synced) if err != nil { return nil, err } @@ -120,6 +123,7 @@ func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) { bloomTrieIndexer: light.NewBloomTrieIndexer(eth.ChainDb(), nil, params.BloomBitsBlocks, params.BloomTrieFrequency), protocolManager: pm, }, + archiveMode: eth.ArchiveMode(), quitSync: quitSync, lesTopics: lesTopics, onlyAnnounce: config.OnlyAnnounce,