mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-01-12 21:34:33 +00:00
Add basic validation of history network (#983)
* Add basic validation of history network * Save only valid objects to db
This commit is contained in:
parent
0d18b606f5
commit
b951139af9
@ -5,11 +5,15 @@
|
||||
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
{.push raises: [Defect].}
|
||||
|
||||
import
|
||||
std/[options, sugar],
|
||||
stew/results, chronos,
|
||||
eth/[common/eth_types, rlp],
|
||||
eth/p2p/discoveryv5/[protocol, enr],
|
||||
../../content_db,
|
||||
../../../nimbus/utils,
|
||||
../wire/[portal_protocol, portal_stream, portal_protocol_config],
|
||||
./history_content
|
||||
|
||||
@ -17,9 +21,12 @@ const
|
||||
historyProtocolId* = [byte 0x50, 0x0B]
|
||||
|
||||
# TODO: Extract common parts from the different networks
|
||||
type HistoryNetwork* = ref object
|
||||
portalProtocol*: PortalProtocol
|
||||
contentDB*: ContentDB
|
||||
type
|
||||
HistoryNetwork* = ref object
|
||||
portalProtocol*: PortalProtocol
|
||||
contentDB*: ContentDB
|
||||
|
||||
Block* = (BlockHeader, BlockBody)
|
||||
|
||||
func setStreamTransport*(n: HistoryNetwork, transport: UtpDiscv5Protocol) =
|
||||
setTransport(n.portalProtocol.stream, transport)
|
||||
@ -27,28 +34,138 @@ func setStreamTransport*(n: HistoryNetwork, transport: UtpDiscv5Protocol) =
|
||||
proc toContentIdHandler(contentKey: ByteList): Option[ContentId] =
|
||||
some(toContentId(contentKey))
|
||||
|
||||
proc getContent*(n: HistoryNetwork, key: ContentKey):
|
||||
Future[Option[seq[byte]]] {.async.} =
|
||||
let
|
||||
keyEncoded = encode(key)
|
||||
contentId = toContentId(keyEncoded)
|
||||
contentInRange = n.portalProtocol.inRange(contentId)
|
||||
func encodeKey(k: ContentKey): (ByteList, ContentId) =
|
||||
let keyEncoded = encode(k)
|
||||
return (keyEncoded, toContentId(keyEncoded))
|
||||
|
||||
# When the content id is in the radius range, try to look it up in the db.
|
||||
if contentInRange:
|
||||
let contentFromDB = n.contentDB.get(contentId)
|
||||
func getEncodedKeyForContent(cType: ContentType, chainId: uint16, hash: BlockHash): (ByteList, ContentId) =
|
||||
let contentKeyType = ContentKeyType(chainId: chainId, blockHash: hash)
|
||||
|
||||
let contentKey =
|
||||
case cType
|
||||
of blockHeader:
|
||||
ContentKey(contentType: cType, blockHeaderKey: contentKeyType)
|
||||
of blockBody:
|
||||
ContentKey(contentType: cType, blockBodyKey: contentKeyType)
|
||||
of receipts:
|
||||
ContentKey(contentType: cType, receiptsKey: contentKeyType)
|
||||
|
||||
return encodeKey(contentKey)
|
||||
|
||||
proc validateHeaderBytes*(bytes: seq[byte], hash: BlockHash): Option[BlockHeader] =
|
||||
try:
|
||||
var rlp = rlpFromBytes(bytes)
|
||||
|
||||
let blockHeader = rlp.read(BlockHeader)
|
||||
|
||||
if not (blockHeader.blockHash() == hash):
|
||||
# TODO: Header with different hash than expected maybe we should punish peer which sent
|
||||
# us this ?
|
||||
return none(BlockHeader)
|
||||
|
||||
return some(blockHeader)
|
||||
|
||||
except MalformedRlpError, UnsupportedRlpError, RlpTypeMismatch:
|
||||
# TODO add some logging about failed decoding
|
||||
return none(BlockHeader)
|
||||
|
||||
proc validateBodyBytes*(bytes: seq[byte], txRoot: KeccakHash, ommersHash: KeccakHash): Option[BlockBody] =
|
||||
try:
|
||||
var rlp = rlpFromBytes(bytes)
|
||||
|
||||
let blockBody = rlp.read(BlockBody)
|
||||
|
||||
let calculatedTxRoot = calcTxRoot(blockBody.transactions)
|
||||
let calculatedOmmersHash = rlpHash(blockBody.uncles)
|
||||
|
||||
if txRoot != calculatedTxRoot or ommersHash != calculatedOmmersHash:
|
||||
# we got block body (bundle of transactions and uncles) which do not match
|
||||
# header. For now just ignore it, but maybe we should penalize peer sending us such data?
|
||||
return none(BlockBody)
|
||||
|
||||
return some(blockBody)
|
||||
|
||||
except RlpError, MalformedRlpError, UnsupportedRlpError, RlpTypeMismatch:
|
||||
# TODO add some logging about failed decoding
|
||||
return none(BlockBody)
|
||||
|
||||
proc getContentFromDb(h: HistoryNetwork, T: type, contentId: ContentId): Option[T] =
|
||||
if h.portalProtocol.inRange(contentId):
|
||||
let contentFromDB = h.contentDB.get(contentId)
|
||||
if contentFromDB.isSome():
|
||||
return contentFromDB
|
||||
var rlp = rlpFromBytes(contentFromDB.unsafeGet())
|
||||
try:
|
||||
let content = rlp.read(T)
|
||||
return some(content)
|
||||
except CatchableError as e:
|
||||
# Content in db should always have valid formatting, so this should not happen
|
||||
raiseAssert(e.msg)
|
||||
else:
|
||||
return none(T)
|
||||
else:
|
||||
return none(T)
|
||||
|
||||
let content = await n.portalProtocol.contentLookup(keyEncoded, contentId)
|
||||
proc getBlockHeader*(h: HistoryNetwork, chainId: uint16, hash: BlockHash): Future[Option[BlockHeader]] {.async.} =
|
||||
let (keyEncoded, contentId) = getEncodedKeyForContent(blockHeader, chainId, hash)
|
||||
|
||||
# When content is found and is in the radius range, store it.
|
||||
if content.isSome() and contentInRange:
|
||||
n.contentDB.put(contentId, content.get())
|
||||
let maybeHeaderFromDb = h.getContentFromDb(BlockHeader, contentId)
|
||||
|
||||
# TODO: for now returning bytes, ultimately it would be nice to return proper
|
||||
# domain types.
|
||||
return content
|
||||
if maybeHeaderFromDb.isSome():
|
||||
return maybeHeaderFromDb
|
||||
|
||||
let maybeHeaderContent = await h.portalProtocol.contentLookup(keyEncoded, contentId)
|
||||
|
||||
if maybeHeaderContent.isNone():
|
||||
return none(BlockHeader)
|
||||
|
||||
let headerContent = maybeHeaderContent.unsafeGet()
|
||||
|
||||
let maybeHeader = validateHeaderBytes(headerContent, hash)
|
||||
|
||||
# content is in range and valid, put into db
|
||||
if maybeHeader.isSome() and h.portalProtocol.inRange(contentId):
|
||||
h.contentDB.put(contentId, headerContent)
|
||||
|
||||
return maybeHeader
|
||||
|
||||
proc getBlock*(h: HistoryNetwork, chainId: uint16, hash: BlockHash): Future[Option[Block]] {.async.} =
|
||||
let maybeHeader = await h.getBlockHeader(chainId, hash)
|
||||
|
||||
if maybeHeader.isNone():
|
||||
# we do not have header for given hash,so we would not be able to validate
|
||||
# that received body really belong it
|
||||
return none(Block)
|
||||
|
||||
let header = maybeHeader.unsafeGet()
|
||||
|
||||
let (keyEncoded, contentId) = getEncodedKeyForContent(blockBody, chainId, hash)
|
||||
|
||||
let maybeBodyFromDb = h.getContentFromDb(BlockBody, contentId)
|
||||
|
||||
if maybeBodyFromDb.isSome():
|
||||
return some[Block]((header, maybeBodyFromDb.unsafeGet()))
|
||||
|
||||
let maybeBodyContent = await h.portalProtocol.contentLookup(keyEncoded, contentId)
|
||||
|
||||
if maybeBodyContent.isNone():
|
||||
return none(Block)
|
||||
|
||||
let bodyContent = maybeBodyContent.unsafeGet()
|
||||
|
||||
let maybeBody = validateBodyBytes(bodyContent, header.txRoot, header.ommersHash)
|
||||
|
||||
if maybeBody.isNone():
|
||||
return none(Block)
|
||||
|
||||
let blockBody = maybeBody.unsafeGet()
|
||||
|
||||
# content is in range and valid, put into db
|
||||
if h.portalProtocol.inRange(contentId):
|
||||
h.contentDB.put(contentId, bodyContent)
|
||||
|
||||
return some[Block]((header, blockBody))
|
||||
|
||||
# TODO Add getRecepits call
|
||||
|
||||
proc new*(
|
||||
T: type HistoryNetwork,
|
||||
|
@ -169,29 +169,15 @@ proc installEthApiHandlers*(
|
||||
## Returns BlockObject or nil when no block was found.
|
||||
let
|
||||
blockHash = data.toHash()
|
||||
contentKeyType = ContentKeyType(chainId: 1'u16, blockHash: blockHash)
|
||||
|
||||
contentKeyHeader =
|
||||
ContentKey(contentType: blockHeader, blockHeaderKey: contentKeyType)
|
||||
contentKeyBody =
|
||||
ContentKey(contentType: blockBody, blockBodyKey: contentKeyType)
|
||||
let maybeHeaderAndBody = await historyNetwork.getBlock(1'u16, blockHash)
|
||||
|
||||
let headerContent = await historyNetwork.getContent(contentKeyHeader)
|
||||
if headerContent.isNone():
|
||||
if maybeHeaderAndBody.isNone():
|
||||
return none(BlockObject)
|
||||
|
||||
var rlp = rlpFromBytes(headerContent.get())
|
||||
let blockHeader = rlp.read(BlockHeader)
|
||||
|
||||
let bodyContent = await historyNetwork.getContent(contentKeyBody)
|
||||
|
||||
if bodyContent.isSome():
|
||||
var rlp = rlpFromBytes(bodyContent.get())
|
||||
let blockBody = rlp.read(BlockBody)
|
||||
|
||||
return some(buildBlockObject(blockHeader, blockBody))
|
||||
else:
|
||||
return none(BlockObject)
|
||||
let (header, body) = maybeHeaderAndBody.unsafeGet()
|
||||
return some(buildBlockObject(header, body))
|
||||
|
||||
|
||||
rpcServerWithProxy.rpc("eth_getBlockTransactionCountByHash") do(
|
||||
data: EthHashStr) -> HexQuantityStr:
|
||||
@ -202,23 +188,18 @@ proc installEthApiHandlers*(
|
||||
## Returns integer of the number of transactions in this block.
|
||||
let
|
||||
blockHash = data.toHash()
|
||||
contentKeyType = ContentKeyType(chainId: 1'u16, blockHash: blockHash)
|
||||
contentKeyBody =
|
||||
ContentKey(contentType: blockBody, blockBodyKey: contentKeyType)
|
||||
|
||||
let bodyContent = await historyNetwork.getContent(contentKeyBody)
|
||||
|
||||
if bodyContent.isSome():
|
||||
var rlp = rlpFromBytes(bodyContent.get())
|
||||
let blockBody = rlp.read(BlockBody)
|
||||
let maybeHeaderAndBody = await historyNetwork.getBlock(1'u16, blockHash)
|
||||
|
||||
if maybeHeaderAndBody.isNone():
|
||||
raise newException(ValueError, "Could not find block with requested hash")
|
||||
else:
|
||||
let (_, body) = maybeHeaderAndBody.unsafeGet()
|
||||
var txCount:uint = 0
|
||||
for tx in blockBody.transactions:
|
||||
for tx in body.transactions:
|
||||
txCount.inc()
|
||||
|
||||
return encodeQuantity(txCount)
|
||||
else:
|
||||
raise newException(ValueError, "Could not find block with requested hash")
|
||||
|
||||
# Note: can't implement this yet as the fluffy node doesn't know the relation
|
||||
# of tx hash -> block number -> block hash, in order to get the receipt
|
||||
|
@ -14,6 +14,7 @@ import
|
||||
./test_state_content,
|
||||
./test_state_network,
|
||||
./test_history_content,
|
||||
./test_history_validation,
|
||||
./test_content_db,
|
||||
./test_discovery_rpc,
|
||||
./test_bridge_parser
|
||||
|
96
fluffy/tests/test_history_validation.nim
Normal file
96
fluffy/tests/test_history_validation.nim
Normal file
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user