Skip to content

Commit 2ada4b5

Browse files
committed
feat: add block knowledge request mechanism, implement tests
1 parent d7c403e commit 2ada4b5

File tree

16 files changed

+255
-92
lines changed

16 files changed

+255
-92
lines changed

codex/blockexchange/engine/engine.nim

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,6 @@ proc blockexcTaskRunner(self: BlockExcEngine) {.async: (raises: []).}
121121
proc start*(self: BlockExcEngine) {.async: (raises: []).} =
122122
## Start the blockexc task
123123
##
124-
125124
await self.discovery.start()
126125
await self.advertiser.start()
127126

@@ -200,11 +199,14 @@ proc refreshBlockKnowledge(self: BlockExcEngine) {.async: (raises: [CancelledErr
200199

201200
# In dynamic swarms, staleness will dominate latency.
202201
if peer.lastRefresh < self.pendingBlocks.lastInclusion or peer.isKnowledgeStale:
202+
trace "Refreshing block knowledge for peer", peer = peer.id
203203
peer.refreshRequested()
204204
# TODO: optimize this by keeping track of what was sent and sending deltas.
205205
# This should allow us to run much more frequent refreshes, and be way more
206206
# efficient about it.
207207
await self.refreshBlockKnowledge(peer)
208+
else:
209+
trace "Not refreshing: peer is up to date", peer = peer.id
208210

209211
proc searchForNewPeers(self: BlockExcEngine, cid: Cid) =
210212
if self.lastDiscRequest + DiscoveryRateLimit < Moment.now():
@@ -336,10 +338,11 @@ proc requestBlocks*(
336338
for address in addresses:
337339
self.trackedFutures.track(self.downloadInternal(address))
338340

339-
var completed: int = 0
341+
let totalHandles = handles.len
342+
var completed = 0
340343

341344
proc isFinished(): bool =
342-
completed == handles.len
345+
completed == totalHandles
343346

344347
proc genNext(): Future[?!Block] {.async: (raises: [CancelledError]).} =
345348
# Be it success or failure, we're completing this future.

codex/blockexchange/engine/pendingblocks.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ declareGauge(
3434

3535
const
3636
DefaultBlockRetries* = 3000
37-
DefaultRetryInterval* = 5.seconds
37+
DefaultRetryInterval* = 1.seconds
3838

3939
type
4040
RetriesExhaustedError* = object of CatchableError

codex/blockexchange/network/network.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ proc handlePeerDeparted*(
341341
if not self.handlers.onPeerDeparted.isNil:
342342
await self.handlers.onPeerDeparted(peer)
343343

344-
method init*(self: BlockExcNetwork) =
344+
method init*(self: BlockExcNetwork) {.raises: [].} =
345345
## Perform protocol initialization
346346
##
347347

codex/blockexchange/peers/peercontext.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import ../../logutils
2626
export payments, nitro
2727

2828
const
29-
MinRefreshInterval = 5.seconds
29+
MinRefreshInterval = 1.seconds
3030
MaxRefreshBackoff = 36 # 3 minutes
3131

3232
type BlockExcPeerCtx* = ref object of RootObj

codex/stores/cachestore.nim

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,21 @@ method getBlock*(
6666
trace "Error requesting block from cache", cid, error = exc.msg
6767
return failure exc
6868

69+
method getBlocks*(
70+
self: CacheStore, addresses: seq[BlockAddress]
71+
): Future[SafeAsyncIter[Block]] {.async: (raises: [CancelledError]).} =
72+
var i = 0
73+
74+
proc isFinished(): bool =
75+
i == addresses.len
76+
77+
proc genNext(): Future[?!Block] {.async: (raises: [CancelledError]).} =
78+
let value = await self.getBlock(addresses[i])
79+
inc(i)
80+
return value
81+
82+
return SafeAsyncIter[Block].new(genNext, isFinished)
83+
6984
method getCidAndProof*(
7085
self: CacheStore, treeCid: Cid, index: Natural
7186
): Future[?!(Cid, CodexProof)] {.async: (raises: [CancelledError]).} =

tests/codex/blockexchange/discovery/testdiscovery.nim

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ asyncchecksuite "Block Advertising and Discovery":
5454
peerStore = PeerCtxStore.new()
5555
pendingBlocks = PendingBlocksManager.new()
5656

57-
(manifest, tree) = makeManifestAndTree(blocks).tryGet()
57+
(_, tree, manifest) = makeDataset(blocks).tryGet()
5858
manifestBlock =
5959
bt.Block.new(manifest.encode().tryGet(), codec = ManifestCodec).tryGet()
6060

@@ -172,7 +172,7 @@ asyncchecksuite "E2E - Multiple Nodes Discovery":
172172
break
173173

174174
blocks.add(bt.Block.new(chunk).tryGet())
175-
let (manifest, tree) = makeManifestAndTree(blocks).tryGet()
175+
let (_, tree, manifest) = makeDataset(blocks).tryGet()
176176
manifests.add(manifest)
177177
mBlocks.add(manifest.asBlock())
178178
trees.add(tree)

tests/codex/blockexchange/discovery/testdiscoveryengine.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ asyncchecksuite "Test Discovery Engine":
4343

4444
blocks.add(bt.Block.new(chunk).tryGet())
4545

46-
(manifest, tree) = makeManifestAndTree(blocks).tryGet()
46+
(_, tree, manifest) = makeDataset(blocks).tryGet()
4747
manifestBlock = manifest.asBlock()
4848
blocks.add(manifestBlock)
4949

tests/codex/blockexchange/engine/testblockexc.nim

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,12 @@ asyncchecksuite "NetworkStore engine - 2 nodes":
2424
pendingBlocks1, pendingBlocks2: seq[BlockHandle]
2525

2626
setup:
27-
blocks1 = await makeRandomBlocks(datasetSize = 2048, blockSize = 256'nb)
28-
blocks2 = await makeRandomBlocks(datasetSize = 2048, blockSize = 256'nb)
27+
blocks1 = makeRandomBlocks(nBlocks = 8, blockSize = 256'nb)
28+
blocks2 = makeRandomBlocks(nBlocks = 8, blockSize = 256'nb)
2929
nodeCmps1 = generateNodes(1, blocks1)[0]
3030
nodeCmps2 = generateNodes(1, blocks2)[0]
3131

32-
await allFuturesThrowing(
33-
nodeCmps1.switch.start(),
34-
nodeCmps1.blockDiscovery.start(),
35-
nodeCmps1.engine.start(),
36-
nodeCmps2.switch.start(),
37-
nodeCmps2.blockDiscovery.start(),
38-
nodeCmps2.engine.start(),
39-
)
32+
await allFuturesThrowing(nodeCmps1.start(), nodeCmps2.start())
4033

4134
# initialize our want lists
4235
pendingBlocks1 =
@@ -65,14 +58,7 @@ asyncchecksuite "NetworkStore engine - 2 nodes":
6558
check isNil(peerCtx2).not
6659

6760
teardown:
68-
await allFuturesThrowing(
69-
nodeCmps1.blockDiscovery.stop(),
70-
nodeCmps1.engine.stop(),
71-
nodeCmps1.switch.stop(),
72-
nodeCmps2.blockDiscovery.stop(),
73-
nodeCmps2.engine.stop(),
74-
nodeCmps2.switch.stop(),
75-
)
61+
await allFuturesThrowing(nodeCmps1.stop(), nodeCmps2.stop())
7662

7763
test "Should exchange blocks on connect":
7864
await allFuturesThrowing(allFinished(pendingBlocks1)).wait(10.seconds)
@@ -145,7 +131,7 @@ asyncchecksuite "NetworkStore - multiple nodes":
145131
blocks: seq[bt.Block]
146132

147133
setup:
148-
blocks = await makeRandomBlocks(datasetSize = 4096, blockSize = 256'nb)
134+
blocks = makeRandomBlocks(nBlocks = 16, blockSize = 256'nb)
149135
nodes = generateNodes(5)
150136
for e in nodes:
151137
await e.engine.start()
@@ -203,3 +189,28 @@ asyncchecksuite "NetworkStore - multiple nodes":
203189

204190
check pendingBlocks1.mapIt(it.read) == blocks[0 .. 3]
205191
check pendingBlocks2.mapIt(it.read) == blocks[12 .. 15]
192+
193+
asyncchecksuite "NetworkStore - dissemination":
194+
var nodes: seq[NodesComponents]
195+
196+
teardown:
197+
if nodes.len > 0:
198+
await nodes.stop()
199+
200+
test "Should disseminate blocks across large diameter swarm":
201+
let dataset = makeRandomDataset(nBlocks = 60, blockSize = 256'nb).tryGet()
202+
203+
nodes = generateNodes(6, enableDiscovery = false)
204+
205+
await assignBlocks(nodes[0], dataset, 0 .. 9)
206+
await assignBlocks(nodes[1], dataset, 10 .. 19)
207+
await assignBlocks(nodes[2], dataset, 20 .. 29)
208+
await assignBlocks(nodes[3], dataset, 30 .. 39)
209+
await assignBlocks(nodes[4], dataset, 40 .. 49)
210+
await assignBlocks(nodes[5], dataset, 50 .. 59)
211+
212+
await nodes.start()
213+
await nodes.linearTopology()
214+
215+
let downloads = nodes.mapIt(downloadDataset(it, dataset))
216+
await allFuturesThrowing(downloads).wait(20.seconds)

tests/codex/helpers.nim

Lines changed: 6 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@ import pkg/codex/rng
1212
import pkg/codex/utils
1313

1414
import ./helpers/nodeutils
15+
import ./helpers/datasetutils
1516
import ./helpers/randomchunker
1617
import ./helpers/mockchunker
1718
import ./helpers/mockdiscovery
1819
import ./helpers/always
1920
import ../checktest
2021

21-
export randomchunker, nodeutils, mockdiscovery, mockchunker, always, checktest, manifest
22+
export
23+
randomchunker, nodeutils, datasetutils, mockdiscovery, mockchunker, always, checktest,
24+
manifest
2225

2326
export libp2p except setup, eventually
2427

@@ -46,23 +49,6 @@ proc lenPrefix*(msg: openArray[byte]): seq[byte] =
4649

4750
return buf
4851

49-
proc makeManifestAndTree*(blocks: seq[Block]): ?!(Manifest, CodexTree) =
50-
if blocks.len == 0:
51-
return failure("Blocks list was empty")
52-
53-
let
54-
datasetSize = blocks.mapIt(it.data.len).foldl(a + b)
55-
blockSize = blocks.mapIt(it.data.len).foldl(max(a, b))
56-
tree = ?CodexTree.init(blocks.mapIt(it.cid))
57-
treeCid = ?tree.rootCid
58-
manifest = Manifest.new(
59-
treeCid = treeCid,
60-
blockSize = NBytes(blockSize),
61-
datasetSize = NBytes(datasetSize),
62-
)
63-
64-
return success((manifest, tree))
65-
6652
proc makeWantList*(
6753
cids: seq[Cid],
6854
priority: int = 0,
@@ -91,7 +77,7 @@ proc storeDataGetManifest*(
9177
(await store.putBlock(blk)).tryGet()
9278

9379
let
94-
(manifest, tree) = makeManifestAndTree(blocks).tryGet()
80+
(_, tree, manifest) = makeDataset(blocks).tryGet()
9581
treeCid = tree.rootCid.tryGet()
9682

9783
for i in 0 ..< tree.leavesCount:
@@ -110,19 +96,6 @@ proc storeDataGetManifest*(
11096

11197
return await storeDataGetManifest(store, blocks)
11298

113-
proc makeRandomBlocks*(
114-
datasetSize: int, blockSize: NBytes
115-
): Future[seq[Block]] {.async.} =
116-
var chunker =
117-
RandomChunker.new(Rng.instance(), size = datasetSize, chunkSize = blockSize)
118-
119-
while true:
120-
let chunk = await chunker.getBytes()
121-
if chunk.len <= 0:
122-
break
123-
124-
result.add(Block.new(chunk).tryGet())
125-
12699
proc corruptBlocks*(
127100
store: BlockStore, manifest: Manifest, blks, bytes: int
128101
): Future[seq[int]] {.async.} =
@@ -147,4 +120,5 @@ proc corruptBlocks*(
147120

148121
bytePos.add(ii)
149122
blk.data[ii] = byte 0
123+
150124
return pos
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import std/random
2+
3+
import pkg/codex/blocktype as bt
4+
import pkg/codex/merkletree
5+
import pkg/codex/manifest
6+
7+
type TestDataset* = tuple[blocks: seq[Block], tree: CodexTree, manifest: Manifest]
8+
9+
proc makeRandomBlock*(size: NBytes): Block =
10+
let bytes = newSeqWith(size.int, rand(uint8))
11+
Block.new(bytes).tryGet()
12+
13+
proc makeRandomBlocks*(nBlocks: int, blockSize: NBytes): seq[Block] =
14+
for i in 0 ..< nBlocks:
15+
result.add(makeRandomBlock(blockSize))
16+
17+
proc makeDataset*(blocks: seq[Block]): ?!TestDataset =
18+
if blocks.len == 0:
19+
return failure("Blocks list was empty")
20+
21+
let
22+
datasetSize = blocks.mapIt(it.data.len).foldl(a + b)
23+
blockSize = blocks.mapIt(it.data.len).foldl(max(a, b))
24+
tree = ?CodexTree.init(blocks.mapIt(it.cid))
25+
treeCid = ?tree.rootCid
26+
manifest = Manifest.new(
27+
treeCid = treeCid,
28+
blockSize = NBytes(blockSize),
29+
datasetSize = NBytes(datasetSize),
30+
)
31+
32+
return success((blocks, tree, manifest))
33+
34+
proc makeRandomDataset*(nBlocks: int, blockSize: NBytes): ?!TestDataset =
35+
makeDataset(makeRandomBlocks(nBlocks, blockSize))

0 commit comments

Comments
 (0)