Skip to content

Commit 6bf89c4

Browse files
authored
portal bridge: Add sync backfill mode + a bit of bridge rework (#3770)
1 parent 2bd0433 commit 6bf89c4

File tree

3 files changed

+148
-85
lines changed

3 files changed

+148
-85
lines changed

portal/bridge/history/portal_history_bridge.nim

Lines changed: 107 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,15 @@ import
2626
../../database/era1_db,
2727
../../../execution_chain/common/[hardforks, chain_config],
2828
../common/rpc_helpers,
29-
../nimbus_portal_bridge_conf
29+
../nimbus_portal_bridge_conf,
30+
beacon_chain/process_state
3031

3132
from ../../network/network_metadata import loadAccumulator
3233

3334
const newHeadPollInterval = 6.seconds # Slot with potential block is every 12s
3435

3536
type PortalHistoryBridge = ref object
36-
portalClient: RpcClient
37+
portalClient: PortalRpcClient
3738
web3Client: RpcClient
3839
gossipQueue: AsyncQueue[(seq[byte], seq[byte])]
3940
cfg*: ChainConfig
@@ -128,18 +129,86 @@ proc gossipBlockContent(
128129
ok()
129130

130131
proc runBackfillLoop(
131-
bridge: PortalHistoryBridge, era1Dir: string, startEra: uint64, endEra: uint64
132+
bridge: PortalHistoryBridge,
133+
era1Dir: string,
134+
startEra: uint64,
135+
endEra: uint64,
136+
loop: bool,
132137
) {.async: (raises: [CancelledError]).} =
133138
let accumulator = loadAccumulator()
134139

135-
for era in startEra .. endEra:
136-
let
137-
root = accumulator.historicalEpochs[era]
138-
era1File = era1Dir / era1FileName("mainnet", Era1(era), Digest(data: root))
140+
while true:
141+
for era in startEra .. endEra:
142+
let
143+
root = accumulator.historicalEpochs[era]
144+
era1File = era1Dir / era1FileName("mainnet", Era1(era), Digest(data: root))
139145

140-
(await bridge.gossipBlockContent(era1File)).isOkOr:
141-
error "Failed to gossip block content from era1 file", error, era1File
142-
continue
146+
(await bridge.gossipBlockContent(era1File)).isOkOr:
147+
error "Failed to gossip block content from era1 file", error, era1File
148+
continue
149+
150+
if not loop:
151+
ProcessState.scheduleStop("backfill_complete")
152+
break
153+
154+
info "Completed backfill loop, starting over"
155+
156+
proc runBackfillLoopSyncMode(
157+
bridge: PortalHistoryBridge,
158+
era1Dir: string,
159+
startEra: uint64,
160+
endEra: uint64,
161+
loop: bool,
162+
) {.async: (raises: [CancelledError]).} =
163+
let
164+
rng = newRng()
165+
db = Era1DB.new(era1Dir, "mainnet", loadAccumulator(), bridge.cfg.posBlock.get())
166+
blockLowerBound = startEra * EPOCH_SIZE # inclusive
167+
blockUpperBound = ((endEra + 1) * EPOCH_SIZE) - 1 # inclusive
168+
blockNumberQueue = newAsyncQueue[uint64](50)
169+
170+
proc blockWorker() {.async: (raises: [CancelledError]).} =
171+
while true:
172+
let blockNumber = await blockNumberQueue.popFirst()
173+
174+
logScope:
175+
blockNumber = blockNumber
176+
177+
var blockTuple: BlockTuple
178+
db.getBlockTuple(blockNumber, blockTuple).isOkOr:
179+
error "Failed to get block tuple", error
180+
continue
181+
182+
block bodyBlock:
183+
let _ = (await historyGetBlockBody(bridge.portalClient, blockTuple.header)).valueOr:
184+
info "Failed to find block body content, gossiping..", error = $error
185+
await bridge.gossipBlockBody(blockNumber, blockTuple.body)
186+
break bodyBlock
187+
188+
block receiptsBlock:
189+
let _ = (await historyGetReceipts(bridge.portalClient, blockTuple.header)).valueOr:
190+
info "Failed to find block receipts content, gossiping..", error = $error
191+
await bridge.gossipReceipts(
192+
blockNumber, blockTuple.receipts.to(StoredReceipts)
193+
)
194+
break receiptsBlock
195+
196+
var workers: seq[Future[void]] = @[]
197+
for i in 0 ..< 50:
198+
workers.add blockWorker()
199+
200+
while true:
201+
for blockNumber in blockLowerBound .. blockUpperBound:
202+
if blockNumber mod EPOCH_SIZE == 0:
203+
info "Backfilling task at block", blockNumber, era = blockNumber div EPOCH_SIZE
204+
205+
await blockNumberQueue.addLast(blockNumber)
206+
207+
if not loop:
208+
ProcessState.scheduleStop("backfill_complete")
209+
break
210+
211+
info "Completed backfill loop, starting over"
143212

144213
proc runBackfillLoopAuditMode(
145214
bridge: PortalHistoryBridge, era1Dir: string, startEra: uint64, endEra: uint64
@@ -163,51 +232,23 @@ proc runBackfillLoopAuditMode(
163232
error "Failed to get block tuple", error
164233
continue
165234

166-
var bodySuccess, receiptsSuccess = false
167-
168-
# body
169235
block bodyBlock:
170-
let _ =
171-
try:
172-
(
173-
await bridge.portalClient.portal_historyGetBlockBody(
174-
rlp.encode(blockTuple.header).to0xHex()
175-
)
176-
)
177-
except CatchableError as e:
178-
error "Failed to find block body content", error = e.msg
179-
break bodyBlock
180-
181-
info "Retrieved block body from Portal network"
182-
bodySuccess = true
236+
let _ = (await historyGetBlockBody(bridge.portalClient, blockTuple.header)).valueOr:
237+
info "Failed to find block body content, gossiping..", error = $error
238+
await bridge.gossipBlockBody(blockNumber, blockTuple.body)
239+
break bodyBlock
183240

184-
# receipts
185241
block receiptsBlock:
186-
let _ =
187-
try:
188-
(
189-
await bridge.portalClient.portal_historyGetReceipts(
190-
rlp.encode(blockTuple.header).to0xHex()
191-
)
192-
)
193-
except CatchableError as e:
194-
error "Failed to find block receipts content", error = e.msg
195-
break receiptsBlock
196-
197-
info "Retrieved block receipts from Portal network"
198-
receiptsSuccess = true
199-
200-
# Gossip missing content
201-
if not bodySuccess:
202-
await bridge.gossipBlockBody(blockNumber, blockTuple.body)
203-
if not receiptsSuccess:
204-
await bridge.gossipReceipts(blockNumber, blockTuple.receipts.to(StoredReceipts))
242+
let _ = (await historyGetReceipts(bridge.portalClient, blockTuple.header)).valueOr:
243+
info "Failed to find block receipts content, gossiping..", error = $error
244+
await bridge.gossipReceipts(blockNumber, blockTuple.receipts.to(StoredReceipts))
245+
break receiptsBlock
205246

206-
await sleepAsync(2.seconds)
247+
await sleepAsync(1.seconds)
207248

208249
proc runHistory*(config: PortalBridgeConf) =
209250
let bridge = PortalHistoryBridge(
210-
portalClient: newRpcClientConnect(config.portalRpcUrl),
251+
portalClient: PortalRpcClient.init(newRpcClientConnect(config.portalRpcUrl)),
211252
web3Client: newRpcClientConnect(config.web3Url),
212253
gossipQueue: newAsyncQueue[(seq[byte], seq[byte])](config.gossipConcurrency),
213254
cfg: chainConfigForNetwork(MainNet),
@@ -223,9 +264,8 @@ proc runHistory*(config: PortalBridgeConf) =
223264

224265
while true:
225266
try:
226-
let putContentResult = await bridge.portalClient.portal_historyPutContent(
227-
contentKeyHex, contentValueHex
228-
)
267+
let putContentResult = await RpcClient(bridge.portalClient)
268+
.portal_historyPutContent(contentKeyHex, contentValueHex)
229269
let
230270
peers = putContentResult.peerCount
231271
accepted = putContentResult.acceptMetadata.acceptedCount
@@ -287,12 +327,19 @@ proc runHistory*(config: PortalBridgeConf) =
287327
if config.latest:
288328
asyncSpawn bridge.runLatestLoop(config.blockVerify)
289329

290-
if config.backfill:
291-
if config.audit:
292-
asyncSpawn bridge.runBackfillLoopAuditMode(
293-
config.era1Dir.string, config.startEra, config.endEra
294-
)
295-
else:
296-
asyncSpawn bridge.runBackfillLoop(
297-
config.era1Dir.string, config.startEra, config.endEra
298-
)
330+
case config.backfillMode
331+
of BackfillMode.none:
332+
if not config.latest:
333+
ProcessState.scheduleStop("no_backfill_no_latest")
334+
of BackfillMode.regular:
335+
asyncSpawn bridge.runBackfillLoop(
336+
config.era1Dir.string, config.startEra, config.endEra, config.backfillLoop
337+
)
338+
of BackfillMode.sync:
339+
asyncSpawn bridge.runBackfillLoopSyncMode(
340+
config.era1Dir.string, config.startEra, config.endEra, config.backfillLoop
341+
)
342+
of BackfillMode.audit:
343+
asyncSpawn bridge.runBackfillLoopAuditMode(
344+
config.era1Dir.string, config.startEra, config.endEra
345+
)

portal/bridge/nimbus_portal_bridge_conf.nim

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ proc defaultEra1DataDir*(): string =
3636
const defaultEndEra* = uint64(era(network_metadata.mergeBlockNumber - 1))
3737

3838
type
39+
BackfillMode* = enum
40+
none
41+
regular
42+
sync
43+
audit
44+
3945
TrustedDigest* = MDigest[32 * 8]
4046

4147
PortalBridgeCmd* = enum
@@ -91,23 +97,36 @@ type
9197
.}: JsonRpcUrl
9298

9399
blockVerify* {.
94-
desc: "Verify the block header, body and receipts",
100+
desc:
101+
"Verify the block body and receipts against the received header. Does not verify against the chain.",
95102
defaultValue: false,
96103
name: "block-verify"
97104
.}: bool
98105

99106
latest* {.
100107
desc:
101-
"Follow the head of the chain and gossip latest block header, body and receipts into the network",
108+
"Follow the head of the chain and gossip latest block body and receipts into the network. Requires web3-url to be set.",
102109
defaultValue: false,
103110
name: "latest"
104111
.}: bool
105112

106-
backfill* {.
113+
backfillMode* {.
107114
desc:
108-
"Randomly backfill pre-merge block headers, bodies and receipts into the network from the era1 files",
109-
defaultValue: false,
110-
name: "backfill"
115+
"Backfill mode to use for the history bridge. Requires access to era1 files.",
116+
longDesc:
117+
"Valid values:\n" & " none — backfill disabled\n" &
118+
" regular — Gossip all block bodies and receipts from era1\n" &
119+
" sync — Download all block bodies and receipts and gossip missing content\n" &
120+
" audit — Download randomly sampled block bodies and receipts and gossip missing content",
121+
defaultValue: BackfillMode.regular,
122+
name: "backfill-mode"
123+
.}: BackfillMode
124+
125+
backfillLoop* {.
126+
desc:
127+
"Restart the backfill loop when it finishes (only applies to sync and regular modes)",
128+
defaultValue: true,
129+
name: "backfill-loop"
111130
.}: bool
112131

113132
startEra* {.desc: "The era to start from", defaultValue: 0, name: "start-era".}:
@@ -117,13 +136,6 @@ type
117136
desc: "The era to stop at", defaultValue: defaultEndEra, name: "end-era"
118137
.}: uint64
119138

120-
audit* {.
121-
desc:
122-
"Run pre-merge backfill in audit mode, which will only gossip content that if failed to fetch from the network",
123-
defaultValue: true,
124-
name: "audit"
125-
.}: bool
126-
127139
era1Dir* {.
128140
desc: "The directory where all era1 files are stored",
129141
defaultValue: defaultEra1DataDir(),

portal/docs/the_fluffy_book/docs/history-content-bridging.md

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,9 @@ Run a Portal client with the Portal JSON-RPC API enabled, e.g. Nimbus Portal cli
1717
> Note: The `--storage-capacity:0` option is not required, but it is added here
1818
for the use case where the node's only focus is on gossiping content from the portal bridge.
1919

20-
### Step 2: Run an EL client
20+
### Step 2: Run an EL client (Optional)
2121

22-
The Nimbus Portal bridge needs access to the EL JSON-RPC API, either through a local
23-
Ethereum client or via a web3 provider.
22+
To seed the latest blocks, the Nimbus Portal bridge needs access to the EL JSON-RPC API, either through a local Ethereum client or via a web3 provider.
2423

2524
### Step 3: Run the Portal bridge in history mode
2625

@@ -29,19 +28,24 @@ Build & run the Nimbus Portal bridge:
2928
make nimbus_portal_bridge
3029

3130
WEB3_URL="http://127.0.0.1:8548" # Replace with your provider.
32-
./build/nimbus_portal_bridge history --web3-url:${WEB3_URL}
31+
./build/nimbus_portal_bridge history --era1-dir:/somedir/era1/ --web3-url:${WEB3_URL}
3332
```
3433

35-
By default the Nimbus Portal bridge will run in `--latest` mode, which means that only the
36-
latest block content will be gossiped into the network.
34+
By default the Nimbus Portal bridge runs in `--backfill-mode:regular`. This means the bridge will access era1 files to gossip block bodies and receipts into the network. There are other backfill modes available which first try to download the data from the network before gossiping it into the network, e.g. sync and audit mode.
3735

38-
It also has a `--backfill` mode which will gossip pre-merge blocks
39-
from `era1` files into the network. By default the bridge will audit first whether
40-
the content is available on the network and if not it will gossip it into the
41-
network.
36+
Example: regular backfill (gossip everything):
37+
```bash
38+
./build/nimbus_portal_bridge history --backfill-mode:regular --era1-dir:/somedir/era1/
39+
```
40+
41+
Example: audit-mode backfill (random sample & gossip missing):
42+
```bash
43+
./build/nimbus_portal_bridge history --backfill-mode:audit --era1-dir:/somedir/era1/
44+
```
45+
46+
It is also possible to enable `--latest` mode, which means that the latest block content will be gossiped into the network.
4247

43-
E.g. run latest + backfill with audit mode:
4448
```bash
4549
WEB3_URL="http://127.0.0.1:8548" # Replace with your provider.
46-
./build/nimbus_portal_bridge history --latest:true --backfill:true --audit:true --era1-dir:/somedir/era1/ --web3-url:${WEB3_URL}
50+
./build/nimbus_portal_bridge history --latest:true --backfill-mode:none --era1-dir:/somedir/era1/ --web3-url:${WEB3_URL}
4751
```

0 commit comments

Comments
 (0)