From c0a034ec899655e6014a2c84f73e0c625bbd87d0 Mon Sep 17 00:00:00 2001
From: gary rong <garyrong0905@gmail.com>
Date: Tue, 11 Jun 2019 15:40:32 +0800
Subject: [PATCH] eth, les: reject stale request (#19689)

* eth, les: reject stale request

* les: reuse local head number
---
 eth/backend.go      |  1 +
 les/handler.go      | 14 ++++++++++
 les/handler_test.go | 62 +++++++++++++++++++++++++++++++++++++++++++++
 les/peer.go         |  9 ++++++-
 les/server.go       |  6 ++++-
 5 files changed, 90 insertions(+), 2 deletions(-)

diff --git a/eth/backend.go b/eth/backend.go
index fbf4dd7bb..52ec40f5a 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 59bfd81cd..32f1903d1 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 6e24165cf..51f0a1a0e 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 6792d0611..56d316f50 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 836fa0d55..fbdf6cf1e 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,
-- 
GitLab