2022-01-18 14:08:02 +00:00
|
|
|
# Nimbus - Portal Network
|
|
|
|
# Copyright (c) 2022 Status Research & Development GmbH
|
|
|
|
# Licensed and distributed under either of
|
|
|
|
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
|
|
|
|
# * 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
|
|
|
|
json_serialization, json_serialization/std/tables,
|
2022-02-11 13:43:10 +00:00
|
|
|
stew/[byteutils, io2, results], nimcrypto/keccak, chronos, chronicles,
|
2022-02-22 10:52:44 +00:00
|
|
|
eth/[rlp, common/eth_types],
|
2022-06-01 13:21:22 +00:00
|
|
|
# TODO: `NetworkId` should not be in these private types
|
|
|
|
eth/p2p/private/p2p_types,
|
|
|
|
../nimbus/[chain_config, genesis],
|
2022-01-18 14:08:02 +00:00
|
|
|
./content_db,
|
2022-02-11 13:43:10 +00:00
|
|
|
./network/wire/portal_protocol,
|
2022-01-18 14:08:02 +00:00
|
|
|
./network/history/history_content
|
|
|
|
|
2022-05-18 20:49:35 +00:00
|
|
|
export results
|
|
|
|
|
2022-02-11 13:43:10 +00:00
|
|
|
# Helper calls to, offline, populate the database with the current existing json
|
|
|
|
# files with block data. Might move to some other storage format later on.
|
|
|
|
# Perhaps https://github.com/status-im/nimbus-eth2/blob/stable/docs/e2store.md
|
|
|
|
# can be interesting here too.
|
2022-01-18 14:08:02 +00:00
|
|
|
|
|
|
|
type
|
2022-02-11 13:43:10 +00:00
|
|
|
BlockData* = object
|
2022-01-18 14:08:02 +00:00
|
|
|
rlp: string
|
2022-02-11 13:43:10 +00:00
|
|
|
# TODO:
|
|
|
|
# uint64, but then it expects a string for some reason.
|
|
|
|
# Fix in nim-json-serialization or should I overload something here?
|
|
|
|
number: int
|
2022-01-18 14:08:02 +00:00
|
|
|
|
2022-02-11 13:43:10 +00:00
|
|
|
BlockDataTable* = Table[string, BlockData]
|
2022-01-18 14:08:02 +00:00
|
|
|
|
2022-04-12 13:49:19 +00:00
|
|
|
proc readBlockDataTable*(dataFile: string): Result[BlockDataTable, string] =
|
2022-01-18 14:08:02 +00:00
|
|
|
let blockData = readAllFile(dataFile)
|
|
|
|
if blockData.isErr(): # TODO: map errors
|
|
|
|
return err("Failed reading data-file")
|
|
|
|
|
|
|
|
let decoded =
|
|
|
|
try:
|
|
|
|
Json.decode(blockData.get(), BlockDataTable)
|
|
|
|
except CatchableError as e:
|
|
|
|
return err("Failed decoding json data-file: " & e.msg)
|
|
|
|
|
2022-02-11 13:43:10 +00:00
|
|
|
ok(decoded)
|
|
|
|
|
|
|
|
iterator blockHashes*(blockData: BlockDataTable): BlockHash =
|
|
|
|
for k,v in blockData:
|
2022-02-22 10:52:44 +00:00
|
|
|
var blockHash: BlockHash
|
2022-02-11 13:43:10 +00:00
|
|
|
try:
|
|
|
|
blockHash.data = hexToByteArray[sizeof(BlockHash)](k)
|
|
|
|
except ValueError as e:
|
|
|
|
error "Invalid hex for block hash", error = e.msg, number = v.number
|
2022-02-22 10:52:44 +00:00
|
|
|
continue
|
2022-02-11 13:43:10 +00:00
|
|
|
|
2022-02-22 10:52:44 +00:00
|
|
|
yield blockHash
|
2022-01-18 14:08:02 +00:00
|
|
|
|
2022-05-24 11:27:22 +00:00
|
|
|
func readBlockData(
|
2022-04-12 13:49:19 +00:00
|
|
|
hash: string, blockData: BlockData, verify = false):
|
|
|
|
Result[seq[(ContentKey, seq[byte])], string] =
|
|
|
|
var res: seq[(ContentKey, seq[byte])]
|
|
|
|
|
|
|
|
var rlp =
|
|
|
|
try:
|
|
|
|
rlpFromHex(blockData.rlp)
|
|
|
|
except ValueError as e:
|
|
|
|
return err("Invalid hex for rlp block data, number " &
|
|
|
|
$blockData.number & ": " & e.msg)
|
|
|
|
|
|
|
|
# The data is currently formatted as an rlp encoded `EthBlock`, thus
|
|
|
|
# containing header, txs and uncles: [header, txs, uncles]. No receipts are
|
|
|
|
# available.
|
|
|
|
# TODO: Change to format to rlp data as it gets stored and send over the
|
|
|
|
# network over the network. I.e. [header, [txs, uncles], receipts]
|
|
|
|
if rlp.enterList():
|
|
|
|
var blockHash: BlockHash
|
|
|
|
try:
|
|
|
|
blockHash.data = hexToByteArray[sizeof(BlockHash)](hash)
|
|
|
|
except ValueError as e:
|
|
|
|
return err("Invalid hex for blockhash, number " &
|
|
|
|
$blockData.number & ": " & e.msg)
|
|
|
|
|
|
|
|
let contentKeyType =
|
|
|
|
ContentKeyType(chainId: 1'u16, blockHash: blockHash)
|
|
|
|
|
|
|
|
try:
|
|
|
|
# If wanted the hash for the corresponding header can be verified
|
|
|
|
if verify:
|
|
|
|
if keccak256.digest(rlp.rawData()) != blockHash:
|
|
|
|
return err("Data is not matching hash, number " & $blockData.number)
|
|
|
|
|
|
|
|
block:
|
|
|
|
let contentKey = ContentKey(
|
|
|
|
contentType: blockHeader,
|
|
|
|
blockHeaderKey: contentKeyType)
|
|
|
|
|
|
|
|
res.add((contentKey, @(rlp.rawData())))
|
|
|
|
rlp.skipElem()
|
|
|
|
|
|
|
|
block:
|
|
|
|
let contentKey = ContentKey(
|
|
|
|
contentType: blockBody,
|
|
|
|
blockBodyKey: contentKeyType)
|
|
|
|
|
|
|
|
# Note: Temporary until the data format gets changed.
|
|
|
|
let blockBody = BlockBody(
|
|
|
|
transactions: rlp.read(seq[Transaction]),
|
|
|
|
uncles: rlp.read(seq[BlockHeader]))
|
|
|
|
let rlpdata = encode(blockBody)
|
|
|
|
|
|
|
|
res.add((contentKey, rlpdata))
|
|
|
|
# res.add((contentKey, @(rlp.rawData())))
|
|
|
|
# rlp.skipElem()
|
|
|
|
|
|
|
|
# Note: No receipts yet in the data set
|
|
|
|
# block:
|
|
|
|
# let contentKey = ContentKey(
|
|
|
|
# contentType: receipts,
|
|
|
|
# receiptsKey: contentKeyType)
|
|
|
|
|
|
|
|
# res.add((contentKey, @(rlp.rawData())))
|
|
|
|
# rlp.skipElem()
|
|
|
|
|
|
|
|
except RlpError as e:
|
|
|
|
return err("Invalid rlp data, number " & $blockData.number & ": " & e.msg)
|
|
|
|
|
|
|
|
ok(res)
|
|
|
|
else:
|
|
|
|
err("Item is not a valid rlp list, number " & $blockData.number)
|
|
|
|
|
2022-02-22 10:52:44 +00:00
|
|
|
iterator blocks*(
|
|
|
|
blockData: BlockDataTable, verify = false): seq[(ContentKey, seq[byte])] =
|
|
|
|
for k,v in blockData:
|
2022-04-12 13:49:19 +00:00
|
|
|
let res = readBlockData(k, v, verify)
|
2022-01-18 14:08:02 +00:00
|
|
|
|
2022-04-12 13:49:19 +00:00
|
|
|
if res.isOk():
|
|
|
|
yield res.get()
|
2022-02-22 10:52:44 +00:00
|
|
|
else:
|
2022-04-12 13:49:19 +00:00
|
|
|
error "Failed reading block from block data", error = res.error
|
2022-02-11 13:43:10 +00:00
|
|
|
|
2022-05-24 11:27:22 +00:00
|
|
|
func readBlockHeader*(blockData: BlockData): Result[BlockHeader, string] =
|
2022-05-18 20:49:35 +00:00
|
|
|
var rlp =
|
|
|
|
try:
|
|
|
|
rlpFromHex(blockData.rlp)
|
|
|
|
except ValueError as e:
|
|
|
|
return err("Invalid hex for rlp block data, number " &
|
|
|
|
$blockData.number & ": " & e.msg)
|
|
|
|
|
|
|
|
if rlp.enterList():
|
|
|
|
try:
|
|
|
|
return ok(rlp.read(BlockHeader))
|
|
|
|
except RlpError as e:
|
|
|
|
return err("Invalid header, number " & $blockData.number & ": " & e.msg)
|
|
|
|
else:
|
|
|
|
return err("Item is not a valid rlp list, number " & $blockData.number)
|
|
|
|
|
2022-06-01 13:21:22 +00:00
|
|
|
proc getGenesisHeader*(id: NetworkId = MainNet): BlockHeader =
|
|
|
|
let params =
|
|
|
|
try:
|
|
|
|
networkParams(id)
|
|
|
|
except ValueError, RlpError:
|
|
|
|
raise (ref Defect)(msg: "Network parameters should be valid")
|
|
|
|
|
|
|
|
try:
|
|
|
|
toGenesisHeader(params)
|
|
|
|
except RlpError:
|
|
|
|
raise (ref Defect)(msg: "Genesis should be valid")
|
|
|
|
|
2022-05-24 11:27:22 +00:00
|
|
|
proc historyStore*(
|
|
|
|
p: PortalProtocol, dataFile: string, verify = false):
|
|
|
|
Result[void, string] =
|
2022-04-12 13:49:19 +00:00
|
|
|
let blockData = ? readBlockDataTable(dataFile)
|
2022-02-11 13:43:10 +00:00
|
|
|
|
2022-02-22 10:52:44 +00:00
|
|
|
for b in blocks(blockData, verify):
|
|
|
|
for value in b:
|
|
|
|
# Note: This is the slowest part due to the hashing that takes place.
|
2022-05-24 11:27:22 +00:00
|
|
|
p.storeContent(history_content.toContentId(value[0]), value[1])
|
2022-01-18 14:08:02 +00:00
|
|
|
|
|
|
|
ok()
|
2022-02-11 13:43:10 +00:00
|
|
|
|
2022-05-24 11:27:22 +00:00
|
|
|
proc historyPropagate*(
|
2022-02-11 13:43:10 +00:00
|
|
|
p: PortalProtocol, dataFile: string, verify = false):
|
|
|
|
Future[Result[void, string]] {.async.} =
|
2022-05-07 11:50:16 +00:00
|
|
|
|
|
|
|
const concurrentGossips = 20
|
|
|
|
|
|
|
|
var gossipQueue =
|
|
|
|
newAsyncQueue[(ContentKeysList, seq[byte])](concurrentGossips)
|
|
|
|
var gossipWorkers: seq[Future[void]]
|
|
|
|
|
|
|
|
proc gossipWorker(p: PortalProtocol) {.async.} =
|
|
|
|
while true:
|
|
|
|
let (keys, content) = await gossipQueue.popFirst()
|
|
|
|
|
|
|
|
await p.neighborhoodGossip(keys, content)
|
|
|
|
|
|
|
|
for i in 0 ..< concurrentGossips:
|
|
|
|
gossipWorkers.add(gossipWorker(p))
|
|
|
|
|
2022-04-12 13:49:19 +00:00
|
|
|
let blockData = readBlockDataTable(dataFile)
|
2022-02-11 13:43:10 +00:00
|
|
|
|
|
|
|
if blockData.isOk():
|
2022-02-22 10:52:44 +00:00
|
|
|
for b in blocks(blockData.get(), verify):
|
|
|
|
for value in b:
|
2022-04-13 09:17:07 +00:00
|
|
|
info "Seeding block content into the network", contentKey = value[0]
|
2022-02-22 10:52:44 +00:00
|
|
|
# Note: This is the slowest part due to the hashing that takes place.
|
2022-04-12 13:49:19 +00:00
|
|
|
let contentId = history_content.toContentId(value[0])
|
2022-05-24 11:27:22 +00:00
|
|
|
p.storeContent(contentId, value[1])
|
2022-02-22 10:52:44 +00:00
|
|
|
|
2022-05-07 11:50:16 +00:00
|
|
|
await gossipQueue.addLast(
|
|
|
|
(ContentKeysList(@[encode(value[0])]), value[1]))
|
2022-04-01 16:01:50 +00:00
|
|
|
|
2022-02-11 13:43:10 +00:00
|
|
|
return ok()
|
|
|
|
else:
|
|
|
|
return err(blockData.error)
|
2022-04-12 13:49:19 +00:00
|
|
|
|
2022-05-24 11:27:22 +00:00
|
|
|
proc historyPropagateBlock*(
|
2022-04-12 13:49:19 +00:00
|
|
|
p: PortalProtocol, dataFile: string, blockHash: string, verify = false):
|
|
|
|
Future[Result[void, string]] {.async.} =
|
|
|
|
let blockDataTable = readBlockDataTable(dataFile)
|
|
|
|
|
|
|
|
if blockDataTable.isOk():
|
|
|
|
let b =
|
|
|
|
try:
|
|
|
|
blockDataTable.get()[blockHash]
|
|
|
|
except KeyError:
|
|
|
|
return err("Block hash not found in block data file")
|
|
|
|
|
|
|
|
let blockDataRes = readBlockData(blockHash, b)
|
|
|
|
if blockDataRes.isErr:
|
|
|
|
return err(blockDataRes.error)
|
|
|
|
|
|
|
|
let blockData = blockDataRes.get()
|
|
|
|
|
|
|
|
for value in blockData:
|
2022-04-13 09:17:07 +00:00
|
|
|
info "Seeding block content into the network", contentKey = value[0]
|
2022-04-12 13:49:19 +00:00
|
|
|
let contentId = history_content.toContentId(value[0])
|
2022-05-24 11:27:22 +00:00
|
|
|
p.storeContent(contentId, value[1])
|
2022-04-12 13:49:19 +00:00
|
|
|
|
|
|
|
await p.neighborhoodGossip(ContentKeysList(@[encode(value[0])]), value[1])
|
|
|
|
|
|
|
|
return ok()
|
|
|
|
else:
|
|
|
|
return err(blockDataTable.error)
|