parent
4721fc7a54
commit
aa945f1ed9
|
@ -314,6 +314,11 @@ proc getBlockHeader*(
|
||||||
proc getBlockBody*(
|
proc getBlockBody*(
|
||||||
n: HistoryNetwork, chainId: uint16, hash: BlockHash, header: BlockHeader):
|
n: HistoryNetwork, chainId: uint16, hash: BlockHash, header: BlockHeader):
|
||||||
Future[Option[BlockBody]] {.async.} =
|
Future[Option[BlockBody]] {.async.} =
|
||||||
|
|
||||||
|
# Got header with empty body, no need to make any db calls or network requests
|
||||||
|
if header.txRoot == BLANK_ROOT_HASH and header.ommersHash == EMPTY_UNCLE_HASH:
|
||||||
|
return some(BlockBody(transactions: @[], uncles: @[]))
|
||||||
|
|
||||||
let
|
let
|
||||||
(keyEncoded, contentId) = getEncodedKeyForContent(blockBody, chainId, hash)
|
(keyEncoded, contentId) = getEncodedKeyForContent(blockBody, chainId, hash)
|
||||||
bodyFromDb = n.getContentFromDb(BlockBody, contentId)
|
bodyFromDb = n.getContentFromDb(BlockBody, contentId)
|
||||||
|
@ -325,8 +330,10 @@ proc getBlockBody*(
|
||||||
for i in 0..<requestRetries:
|
for i in 0..<requestRetries:
|
||||||
let bodyContentLookup =
|
let bodyContentLookup =
|
||||||
await n.portalProtocol.contentLookup(keyEncoded, contentId)
|
await n.portalProtocol.contentLookup(keyEncoded, contentId)
|
||||||
|
|
||||||
if bodyContentLookup.isNone():
|
if bodyContentLookup.isNone():
|
||||||
warn "Failed fetching block body from the network", hash
|
warn "Failed fetching block body from the network", hash
|
||||||
|
return none(BlockBody)
|
||||||
|
|
||||||
let bodyContent = bodyContentLookup.unsafeGet()
|
let bodyContent = bodyContentLookup.unsafeGet()
|
||||||
|
|
||||||
|
@ -493,7 +500,6 @@ proc processContentLoop(n: HistoryNetwork) {.async.} =
|
||||||
|
|
||||||
# content passed here can have less items then contentKeys, but not more.
|
# content passed here can have less items then contentKeys, but not more.
|
||||||
for i, contentItem in contentItems:
|
for i, contentItem in contentItems:
|
||||||
echo contentItem.len()
|
|
||||||
let contentKey = contentKeys[i]
|
let contentKey = contentKeys[i]
|
||||||
if await n.validateContent(contentItem, contentKey):
|
if await n.validateContent(contentItem, contentKey):
|
||||||
let contentIdOpt = n.portalProtocol.toContentId(contentKey)
|
let contentIdOpt = n.portalProtocol.toContentId(contentKey)
|
||||||
|
|
|
@ -16,7 +16,7 @@ import
|
||||||
bearssl, ssz_serialization, metrics, faststreams,
|
bearssl, ssz_serialization, metrics, faststreams,
|
||||||
eth/rlp, eth/p2p/discoveryv5/[protocol, node, enr, routing_table, random2,
|
eth/rlp, eth/p2p/discoveryv5/[protocol, node, enr, routing_table, random2,
|
||||||
nodes_verification, lru],
|
nodes_verification, lru],
|
||||||
../../content_db,
|
".."/../[content_db, seed_db],
|
||||||
"."/[portal_stream, portal_protocol_config],
|
"."/[portal_stream, portal_protocol_config],
|
||||||
./messages
|
./messages
|
||||||
|
|
||||||
|
@ -1329,3 +1329,118 @@ proc resolve*(p: PortalProtocol, id: NodeId): Future[Option[Node]] {.async.} =
|
||||||
return some(n)
|
return some(n)
|
||||||
|
|
||||||
return node
|
return node
|
||||||
|
|
||||||
|
proc resolveWithRadius*(p: PortalProtocol, id: NodeId): Future[Option[(Node, UInt256)]] {.async.} =
|
||||||
|
## Resolve a `Node` based on provided `NodeId`, also try to establish what
|
||||||
|
## is known radius of found node.
|
||||||
|
##
|
||||||
|
## This will first look in the own routing table. If the node is known, it
|
||||||
|
## will try to contact if for newer information. If node is not known or it
|
||||||
|
## does not reply, a lookup is done to see if it can find a (newer) record of
|
||||||
|
## the node on the network.
|
||||||
|
##
|
||||||
|
## If node is found, radius will be first checked in radius cache, it radius
|
||||||
|
## is not known node will be pinged to establish what is its current radius
|
||||||
|
##
|
||||||
|
|
||||||
|
let n = await p.resolve(id)
|
||||||
|
|
||||||
|
if n.isNone():
|
||||||
|
return none((Node, UInt256))
|
||||||
|
|
||||||
|
let node = n.unsafeGet()
|
||||||
|
|
||||||
|
let r = p.radiusCache.get(id)
|
||||||
|
|
||||||
|
if r.isSome():
|
||||||
|
return some((node, r.unsafeGet()))
|
||||||
|
|
||||||
|
let pongResult = await p.ping(node)
|
||||||
|
|
||||||
|
if pongResult.isOk():
|
||||||
|
let maybeRadius = p.radiusCache.get(id)
|
||||||
|
|
||||||
|
# After successful ping radius should already be in cache, but for the unlikely
|
||||||
|
# case that it is not, check it just to be sure.
|
||||||
|
# TODO: rafactor ping to return node radius.
|
||||||
|
if maybeRadius.isNone():
|
||||||
|
return none((Node, UInt256))
|
||||||
|
|
||||||
|
# If pong is successful, radius of the node should definitly be in local
|
||||||
|
# radius cache
|
||||||
|
return some((node, maybeRadius.unsafeGet()))
|
||||||
|
else:
|
||||||
|
return none((Node, UInt256))
|
||||||
|
|
||||||
|
proc offerContentInNodeRange*(
|
||||||
|
p: PortalProtocol,
|
||||||
|
seedDbPath: string,
|
||||||
|
nodeId: NodeId,
|
||||||
|
max: uint32,
|
||||||
|
starting: uint32): Future[PortalResult[void]] {.async.} =
|
||||||
|
## Offers `max` closest elements starting from `starting` index to peer
|
||||||
|
## with given `nodeId`.
|
||||||
|
## Maximum value of `max` is 64 , as this is limit for single offer.
|
||||||
|
## `starting` argument is needed as seed_db is read only, so if there is
|
||||||
|
## more content in peer range than max, then to offer 64 closest elements
|
||||||
|
# it needs to be set to 0. To offer next 64 elements it need to be set to
|
||||||
|
# 64 etc.
|
||||||
|
|
||||||
|
let maybePathAndDbName = getDbBasePathAndName(seedDbPath)
|
||||||
|
|
||||||
|
if maybePathAndDbName.isNone():
|
||||||
|
return err("Provided path is not valid sqlite database path")
|
||||||
|
|
||||||
|
let (dbPath, dbName) = maybePathAndDbName.unsafeGet()
|
||||||
|
|
||||||
|
let maybeNodeAndRadius = await p.resolveWithRadius(nodeId)
|
||||||
|
|
||||||
|
if maybeNodeAndRadius.isNone():
|
||||||
|
return err("Could not find node with provided nodeId")
|
||||||
|
|
||||||
|
let
|
||||||
|
db = SeedDb.new(path = dbPath, name = dbName)
|
||||||
|
(node, radius) = maybeNodeAndRadius.unsafeGet()
|
||||||
|
content = db.getContentInRange(node.id, radius, int64(max), int64(starting))
|
||||||
|
|
||||||
|
# We got all we wanted from seed_db, it can be closed now.
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
var ci: seq[ContentInfo]
|
||||||
|
|
||||||
|
for cont in content:
|
||||||
|
let k = ByteList.init(cont.contentKey)
|
||||||
|
let info = ContentInfo(contentKey: k, content: cont.content)
|
||||||
|
ci.add(info)
|
||||||
|
|
||||||
|
let offerResult = await p.offer(node, ci)
|
||||||
|
|
||||||
|
# waiting for offer result, by the end of this call remote node should
|
||||||
|
# have received offered content
|
||||||
|
return offerResult
|
||||||
|
|
||||||
|
proc storeContentInNodeRange*(
|
||||||
|
p: PortalProtocol,
|
||||||
|
seedDbPath: string,
|
||||||
|
max: uint32,
|
||||||
|
starting: uint32): PortalResult[void] =
|
||||||
|
let maybePathAndDbName = getDbBasePathAndName(seedDbPath)
|
||||||
|
|
||||||
|
if maybePathAndDbName.isNone():
|
||||||
|
return err("Provided path is not valid sqlite database path")
|
||||||
|
|
||||||
|
let (dbPath, dbName) = maybePathAndDbName.unsafeGet()
|
||||||
|
|
||||||
|
let
|
||||||
|
localRadius = p.dataRadius
|
||||||
|
db = SeedDb.new(path = dbPath, name = dbName)
|
||||||
|
localId = p.localNode.id
|
||||||
|
contentInRange = db.getContentInRange(localId, localRadius, int64(max), int64(starting))
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
for contentData in contentInRange:
|
||||||
|
let cid = UInt256.fromBytesBE(contentData.contentId)
|
||||||
|
p.storeContent(cid, contentData.content)
|
||||||
|
|
||||||
|
return ok()
|
||||||
|
|
|
@ -14,7 +14,7 @@ import
|
||||||
# TODO: `NetworkId` should not be in these private types
|
# TODO: `NetworkId` should not be in these private types
|
||||||
eth/p2p/private/p2p_types,
|
eth/p2p/private/p2p_types,
|
||||||
../nimbus/[chain_config, genesis],
|
../nimbus/[chain_config, genesis],
|
||||||
./content_db,
|
"."/[content_db, seed_db],
|
||||||
./network/wire/portal_protocol,
|
./network/wire/portal_protocol,
|
||||||
./network/history/history_content
|
./network/history/history_content
|
||||||
|
|
||||||
|
@ -118,6 +118,15 @@ iterator blocks*(
|
||||||
else:
|
else:
|
||||||
error "Failed reading block from block data", error = res.error
|
error "Failed reading block from block data", error = res.error
|
||||||
|
|
||||||
|
iterator blocksContent*(
|
||||||
|
blockData: BlockDataTable, verify = false): (ContentId, seq[byte], seq[byte]) =
|
||||||
|
for b in blocks(blockData, verify):
|
||||||
|
for value in b:
|
||||||
|
if len(value[1]) > 0:
|
||||||
|
let ckBytes = history_content.encode(value[0])
|
||||||
|
let contentId = history_content.toContentId(ckBytes)
|
||||||
|
yield (contentId, ckBytes.asSeq(), value[1])
|
||||||
|
|
||||||
func readBlockHeader*(blockData: BlockData): Result[BlockHeader, string] =
|
func readBlockHeader*(blockData: BlockData): Result[BlockHeader, string] =
|
||||||
var rlp =
|
var rlp =
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -3,3 +3,13 @@ proc portal_history_store(contentKey: string, content: string): bool
|
||||||
proc portal_history_storeContent(dataFile: string): bool
|
proc portal_history_storeContent(dataFile: string): bool
|
||||||
proc portal_history_propagate(dataFile: string): bool
|
proc portal_history_propagate(dataFile: string): bool
|
||||||
proc portal_history_propagateBlock(dataFile: string, blockHash: string): bool
|
proc portal_history_propagateBlock(dataFile: string, blockHash: string): bool
|
||||||
|
proc portal_history_storeContentInNodeRange(
|
||||||
|
dbPath: string,
|
||||||
|
max: uint32,
|
||||||
|
starting: uint32): bool
|
||||||
|
|
||||||
|
proc portal_history_offerContentInNodeRange(
|
||||||
|
dbPath: string,
|
||||||
|
nodeId: NodeId,
|
||||||
|
max: uint32,
|
||||||
|
starting: uint32): bool
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
import
|
import
|
||||||
json_rpc/[rpcproxy, rpcserver], stew/byteutils,
|
json_rpc/[rpcproxy, rpcserver], stew/byteutils,
|
||||||
../network/wire/portal_protocol,
|
../network/wire/portal_protocol,
|
||||||
../content_db
|
".."/[content_db, seed_db]
|
||||||
|
|
||||||
export rpcserver
|
export rpcserver
|
||||||
|
|
||||||
|
@ -54,3 +54,29 @@ proc installPortalDebugApiHandlers*(
|
||||||
return true
|
return true
|
||||||
else:
|
else:
|
||||||
raise newException(ValueError, $res.error)
|
raise newException(ValueError, $res.error)
|
||||||
|
|
||||||
|
rpcServer.rpc("portal_" & network & "_storeContentInNodeRange") do(
|
||||||
|
dbPath: string,
|
||||||
|
max: uint32,
|
||||||
|
starting: uint32) -> bool:
|
||||||
|
|
||||||
|
let storeResult = p.storeContentInNodeRange(dbPath, max, starting)
|
||||||
|
|
||||||
|
if storeResult.isOk():
|
||||||
|
return true
|
||||||
|
else:
|
||||||
|
raise newException(ValueError, $storeResult.error)
|
||||||
|
|
||||||
|
rpcServer.rpc("portal_" & network & "_offerContentInNodeRange") do(
|
||||||
|
dbPath: string,
|
||||||
|
nodeId: NodeId,
|
||||||
|
max: uint32,
|
||||||
|
starting: uint32) -> bool:
|
||||||
|
# waiting for offer result, by the end of this call remote node should
|
||||||
|
# have received offered content
|
||||||
|
let offerResult = await p.offerContentInNodeRange(dbPath, nodeId, max, starting)
|
||||||
|
|
||||||
|
if offerResult.isOk():
|
||||||
|
return true
|
||||||
|
else:
|
||||||
|
raise newException(ValueError, $offerResult.error)
|
||||||
|
|
|
@ -6,15 +6,20 @@
|
||||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||||
|
|
||||||
import
|
import
|
||||||
|
os,
|
||||||
std/sequtils,
|
std/sequtils,
|
||||||
unittest2, testutils, confutils, chronos,
|
unittest2, testutils, confutils, chronos,
|
||||||
eth/p2p/discoveryv5/random2, eth/keys,
|
eth/p2p/discoveryv5/random2, eth/keys,
|
||||||
../../nimbus/rpc/[hexstrings, rpc_types],
|
../../nimbus/rpc/[hexstrings, rpc_types],
|
||||||
../rpc/portal_rpc_client,
|
../rpc/portal_rpc_client,
|
||||||
../rpc/eth_rpc_client,
|
../rpc/eth_rpc_client,
|
||||||
../populate_db
|
".."/[populate_db, seed_db]
|
||||||
|
|
||||||
type
|
type
|
||||||
|
FutureCallback[A] = proc (): Future[A] {.gcsafe, raises: [Defect].}
|
||||||
|
|
||||||
|
CheckCallback[A] = proc (a: A): bool {.gcsafe, raises: [Defect].}
|
||||||
|
|
||||||
PortalTestnetConf* = object
|
PortalTestnetConf* = object
|
||||||
nodeCount* {.
|
nodeCount* {.
|
||||||
defaultValue: 17
|
defaultValue: 17
|
||||||
|
@ -42,6 +47,42 @@ proc connectToRpcServers(config: PortalTestnetConf):
|
||||||
|
|
||||||
return clients
|
return clients
|
||||||
|
|
||||||
|
proc withRetries[A](
|
||||||
|
f: FutureCallback[A],
|
||||||
|
check: CheckCallback[A],
|
||||||
|
numRetries: int,
|
||||||
|
initialWait: Duration): Future[A] {.async.} =
|
||||||
|
## Retries given future callback until either:
|
||||||
|
## it returns successfuly and given check is true
|
||||||
|
## or
|
||||||
|
## function reaches max specified retries
|
||||||
|
|
||||||
|
var tries = 0
|
||||||
|
var currentDuration = initialWait
|
||||||
|
|
||||||
|
while true:
|
||||||
|
try:
|
||||||
|
let res = await f()
|
||||||
|
|
||||||
|
if check(res):
|
||||||
|
return res
|
||||||
|
except CatchableError as exc:
|
||||||
|
inc tries
|
||||||
|
if tries > numRetries:
|
||||||
|
# if we reached max number of retries fail
|
||||||
|
raise exc
|
||||||
|
|
||||||
|
# wait before new retry
|
||||||
|
await sleepAsync(currentDuration)
|
||||||
|
currentDuration = currentDuration * 2
|
||||||
|
|
||||||
|
# Sometimes we need to wait till data will be propagated over the network.
|
||||||
|
# To avoid long sleeps, this combinator can be used to retry some calls until
|
||||||
|
# success or until some condition hold (or both)
|
||||||
|
proc retryUntilDataPropagated[A](f: FutureCallback[A], c: CheckCallback[A]): Future[A] =
|
||||||
|
# some reasonable limits, which will cause waits as: 1, 2, 4, 8, 16 seconds
|
||||||
|
return withRetries(f, c, 5, seconds(1))
|
||||||
|
|
||||||
# Note:
|
# Note:
|
||||||
# When doing json-rpc requests following `RpcPostError` can occur:
|
# When doing json-rpc requests following `RpcPostError` can occur:
|
||||||
# "Failed to send POST Request with JSON-RPC." when a `HttpClientRequestRef`
|
# "Failed to send POST Request with JSON-RPC." when a `HttpClientRequestRef`
|
||||||
|
@ -188,12 +229,6 @@ procSuite "Portal testnet tests":
|
||||||
check (await clients[0].portal_history_propagate(dataFile))
|
check (await clients[0].portal_history_propagate(dataFile))
|
||||||
await clients[0].close()
|
await clients[0].close()
|
||||||
|
|
||||||
# Note: Sleeping to make a test work is never great. Here it is needed
|
|
||||||
# because the data needs to propagate over the nodes. What one could do is
|
|
||||||
# add a json-rpc debug proc that returns whether the offer queue is empty or
|
|
||||||
# not. And then poll every node until all nodes have an empty queue.
|
|
||||||
await sleepAsync(60.seconds)
|
|
||||||
|
|
||||||
let blockData = readBlockDataTable(dataFile)
|
let blockData = readBlockDataTable(dataFile)
|
||||||
check blockData.isOk()
|
check blockData.isOk()
|
||||||
|
|
||||||
|
@ -201,8 +236,23 @@ procSuite "Portal testnet tests":
|
||||||
# Note: Once there is the Canonical Indices Network, we don't need to
|
# Note: Once there is the Canonical Indices Network, we don't need to
|
||||||
# access this file anymore here for the block hashes.
|
# access this file anymore here for the block hashes.
|
||||||
for hash in blockData.get().blockHashes():
|
for hash in blockData.get().blockHashes():
|
||||||
let content = await client.eth_getBlockByHash(
|
|
||||||
hash.ethHashStr(), false)
|
# Note: More flexible approach instead of generic retries could be to
|
||||||
|
# add a json-rpc debug proc that returns whether the offer queue is empty or
|
||||||
|
# not. And then poll every node until all nodes have an empty queue.
|
||||||
|
|
||||||
|
let content = await retryUntilDataPropagated(
|
||||||
|
proc (): Future[Option[BlockObject]] {.async.} =
|
||||||
|
try:
|
||||||
|
let res = await client.eth_getBlockByHash(hash.ethHashStr(), false)
|
||||||
|
await client.close()
|
||||||
|
return res
|
||||||
|
except CatchableError as exc:
|
||||||
|
await client.close()
|
||||||
|
raise exc
|
||||||
|
,
|
||||||
|
proc (mc: Option[BlockObject]): bool = return mc.isSome()
|
||||||
|
)
|
||||||
check content.isSome()
|
check content.isSome()
|
||||||
let blockObj = content.get()
|
let blockObj = content.get()
|
||||||
check blockObj.hash.get() == hash
|
check blockObj.hash.get() == hash
|
||||||
|
@ -216,7 +266,18 @@ procSuite "Portal testnet tests":
|
||||||
blockHash: some(hash)
|
blockHash: some(hash)
|
||||||
)
|
)
|
||||||
|
|
||||||
let logs = await client.eth_getLogs(filterOptions)
|
let logs = await retryUntilDataPropagated(
|
||||||
|
proc (): Future[seq[FilterLog]] {.async.} =
|
||||||
|
try:
|
||||||
|
let res = await client.eth_getLogs(filterOptions)
|
||||||
|
await client.close()
|
||||||
|
return res
|
||||||
|
except CatchableError as exc:
|
||||||
|
await client.close()
|
||||||
|
raise exc
|
||||||
|
,
|
||||||
|
proc (mc: seq[FilterLog]): bool = return true
|
||||||
|
)
|
||||||
|
|
||||||
for l in logs:
|
for l in logs:
|
||||||
check:
|
check:
|
||||||
|
@ -227,3 +288,75 @@ procSuite "Portal testnet tests":
|
||||||
# discard
|
# discard
|
||||||
|
|
||||||
await client.close()
|
await client.close()
|
||||||
|
|
||||||
|
asyncTest "Portal History - Propagate content from seed db":
|
||||||
|
let clients = await connectToRpcServers(config)
|
||||||
|
|
||||||
|
var nodeInfos: seq[NodeInfo]
|
||||||
|
for client in clients:
|
||||||
|
let nodeInfo = await client.portal_history_nodeInfo()
|
||||||
|
await client.close()
|
||||||
|
nodeInfos.add(nodeInfo)
|
||||||
|
|
||||||
|
const dataPath = "./fluffy/tests/blocks/mainnet_blocks_1000000_1000020.json"
|
||||||
|
|
||||||
|
# path for temporary db, separate dir is used as sqlite usually also creates
|
||||||
|
# wal files, and we do not want for those to linger in filesystem
|
||||||
|
const tempDbPath = "./fluffy/tests/blocks/tempDir/mainnet_blocks_1000000_1000020.sqlite3"
|
||||||
|
|
||||||
|
let (dbFile, dbName) = getDbBasePathAndName(tempDbPath).unsafeGet()
|
||||||
|
|
||||||
|
let blockData = readBlockDataTable(dataPath)
|
||||||
|
check blockData.isOk()
|
||||||
|
let bd = blockData.get()
|
||||||
|
|
||||||
|
createDir(dbFile)
|
||||||
|
let db = SeedDb.new(path = dbFile, name = dbName)
|
||||||
|
|
||||||
|
try:
|
||||||
|
let lastNodeIdx = len(nodeInfos) - 1
|
||||||
|
|
||||||
|
# populate temp database from json file
|
||||||
|
for t in blocksContent(bd, false):
|
||||||
|
db.put(t[0], t[1], t[2])
|
||||||
|
|
||||||
|
# store content in node0 database
|
||||||
|
check (await clients[0].portal_history_storeContentInNodeRange(tempDbPath, 100, 0))
|
||||||
|
await clients[0].close()
|
||||||
|
|
||||||
|
# offer content to node 1..63
|
||||||
|
for i in 1..lastNodeIdx:
|
||||||
|
let receipientId = nodeInfos[i].nodeId
|
||||||
|
check (await clients[0].portal_history_offerContentInNodeRange(tempDbPath, receipientId, 64, 0))
|
||||||
|
await clients[0].close()
|
||||||
|
|
||||||
|
for client in clients:
|
||||||
|
# Note: Once there is the Canonical Indices Network, we don't need to
|
||||||
|
# access this file anymore here for the block hashes.
|
||||||
|
for hash in bd.blockHashes():
|
||||||
|
let content = await retryUntilDataPropagated(
|
||||||
|
proc (): Future[Option[BlockObject]] {.async.} =
|
||||||
|
try:
|
||||||
|
let res = await client.eth_getBlockByHash(hash.ethHashStr(), false)
|
||||||
|
await client.close()
|
||||||
|
return res
|
||||||
|
except CatchableError as exc:
|
||||||
|
await client.close()
|
||||||
|
raise exc
|
||||||
|
,
|
||||||
|
proc (mc: Option[BlockObject]): bool = return mc.isSome()
|
||||||
|
)
|
||||||
|
check content.isSome()
|
||||||
|
|
||||||
|
let blockObj = content.get()
|
||||||
|
check blockObj.hash.get() == hash
|
||||||
|
|
||||||
|
for tx in blockObj.transactions:
|
||||||
|
var txObj: TransactionObject
|
||||||
|
tx.fromJson("tx", txObj)
|
||||||
|
check txObj.blockHash.get() == hash
|
||||||
|
|
||||||
|
await client.close()
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
removeDir(dbFile)
|
||||||
|
|
|
@ -8,7 +8,8 @@
|
||||||
{.push raises: [Defect].}
|
{.push raises: [Defect].}
|
||||||
|
|
||||||
import
|
import
|
||||||
std/options,
|
std/[options, os],
|
||||||
|
strutils,
|
||||||
eth/db/kvstore,
|
eth/db/kvstore,
|
||||||
eth/db/kvstore_sqlite3,
|
eth/db/kvstore_sqlite3,
|
||||||
stint
|
stint
|
||||||
|
@ -31,7 +32,7 @@ type
|
||||||
store: SqStoreRef
|
store: SqStoreRef
|
||||||
putStmt: SqliteStmt[(array[32, byte], seq[byte], seq[byte]), void]
|
putStmt: SqliteStmt[(array[32, byte], seq[byte], seq[byte]), void]
|
||||||
getStmt: SqliteStmt[array[32, byte], ContentData]
|
getStmt: SqliteStmt[array[32, byte], ContentData]
|
||||||
getInRangeStmt: SqliteStmt[(array[32, byte], array[32, byte], int64), ContentDataDist]
|
getInRangeStmt: SqliteStmt[(array[32, byte], array[32, byte], int64, int64), ContentDataDist]
|
||||||
|
|
||||||
func xorDistance(
|
func xorDistance(
|
||||||
a: openArray[byte],
|
a: openArray[byte],
|
||||||
|
@ -54,6 +55,18 @@ template expectDb(x: auto): untyped =
|
||||||
# full disk - this requires manual intervention, so we'll panic for now
|
# full disk - this requires manual intervention, so we'll panic for now
|
||||||
x.expect("working database (disk broken/full?)")
|
x.expect("working database (disk broken/full?)")
|
||||||
|
|
||||||
|
proc getDbBasePathAndName*(path: string): Option[(string, string)] =
|
||||||
|
let (basePath, name) = splitPath(path)
|
||||||
|
if len(basePath) > 0 and len(name) > 0 and name.endsWith(".sqlite3"):
|
||||||
|
let nameAndExt = rsplit(name, ".", 1)
|
||||||
|
|
||||||
|
if len(nameAndExt) < 2 and len(nameAndExt[0]) == 0:
|
||||||
|
return none((string, string))
|
||||||
|
|
||||||
|
return some((basePath, nameAndExt[0]))
|
||||||
|
else:
|
||||||
|
return none((string, string))
|
||||||
|
|
||||||
proc new*(T: type SeedDb, path: string, name: string, inMemory = false): SeedDb =
|
proc new*(T: type SeedDb, path: string, name: string, inMemory = false): SeedDb =
|
||||||
let db =
|
let db =
|
||||||
if inMemory:
|
if inMemory:
|
||||||
|
@ -94,9 +107,10 @@ proc new*(T: type SeedDb, path: string, name: string, inMemory = false): SeedDb
|
||||||
SELECT contentid, contentkey, content, xorDistance(?, contentid) as distance
|
SELECT contentid, contentkey, content, xorDistance(?, contentid) as distance
|
||||||
FROM seed_data
|
FROM seed_data
|
||||||
WHERE distance <= ?
|
WHERE distance <= ?
|
||||||
LIMIT ?;
|
LIMIT ?
|
||||||
|
OFFSET ?;
|
||||||
""",
|
""",
|
||||||
(array[32, byte], array[32, byte], int64),
|
(array[32, byte], array[32, byte], int64, int64),
|
||||||
ContentDataDist
|
ContentDataDist
|
||||||
).get()
|
).get()
|
||||||
|
|
||||||
|
@ -125,13 +139,25 @@ proc getContentInRange*(
|
||||||
db: SeedDb,
|
db: SeedDb,
|
||||||
nodeId: UInt256,
|
nodeId: UInt256,
|
||||||
nodeRadius: UInt256,
|
nodeRadius: UInt256,
|
||||||
max: int64): seq[ContentDataDist] =
|
max: int64,
|
||||||
|
offset: int64): seq[ContentDataDist] =
|
||||||
|
## Return `max` amount of content in `nodeId` range, starting from `offset` position
|
||||||
|
## i.e using `offset` 0 will return `max` closest items, using `offset` `10` will
|
||||||
|
## will retrun `max` closest items except first 10
|
||||||
|
|
||||||
var res: seq[ContentDataDist] = @[]
|
var res: seq[ContentDataDist] = @[]
|
||||||
var cd: ContentDataDist
|
var cd: ContentDataDist
|
||||||
for e in db.getInRangeStmt.exec((nodeId.toByteArrayBE(), nodeRadius.toByteArrayBE(), max), cd):
|
for e in db.getInRangeStmt.exec((nodeId.toByteArrayBE(), nodeRadius.toByteArrayBE(), max, offset), cd):
|
||||||
res.add(cd)
|
res.add(cd)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
proc getContentInRange*(
|
||||||
|
db: SeedDb,
|
||||||
|
nodeId: UInt256,
|
||||||
|
nodeRadius: UInt256,
|
||||||
|
max: int64): seq[ContentDataDist] =
|
||||||
|
## Return `max` amount of content in `nodeId` range, starting from closest content
|
||||||
|
return db.getContentInRange(nodeId, nodeRadius, max, 0)
|
||||||
|
|
||||||
proc close*(db: SeedDb) =
|
proc close*(db: SeedDb) =
|
||||||
db.store.close()
|
db.store.close()
|
||||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue