portal_bridge: Add concurrency to the history content gossip (#2855)

This commit is contained in:
Kim De Mey 2024-11-21 21:30:42 +07:00 committed by GitHub
parent 107db3ae16
commit 453cb2f33e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 91 additions and 112 deletions

View File

@ -141,6 +141,13 @@ type
defaultValueDesc: defaultEra1DataDir(),
name: "era1-dir"
.}: InputDir
gossipConcurrency* {.
desc:
"The number of concurrent gossip workers for gossiping content into the portal network",
defaultValue: 50,
name: "gossip-concurrency"
.}: int
of PortalBridgeCmd.state:
web3UrlState* {.desc: "Execution layer JSON-RPC API URL", name: "web3-url".}:
JsonRpcUrl

View File

@ -31,6 +31,11 @@ from eth/common/eth_types_rlp import rlpHash
const newHeadPollInterval = 6.seconds # Slot with potential block is every 12s
type PortalHistoryBridge = ref object
portalClient: RpcClient
web3Client: RpcClient
gossipQueue: AsyncQueue[(seq[byte], seq[byte])]
## Conversion functions for Block and Receipts
func asEthBlock(blockObject: BlockObject): EthBlock =
@ -139,63 +144,34 @@ proc getBlockReceipts(
## Portal JSON-RPC API helper calls for pushing block and receipts
proc gossipBlockHeader(
client: RpcClient, id: Hash32 | uint64, headerWithProof: BlockHeaderWithProof
): Future[Result[void, string]] {.async: (raises: [CancelledError]).} =
let
contentKey = blockHeaderContentKey(id)
encodedContentKeyHex = contentKey.encode.asSeq().toHex()
bridge: PortalHistoryBridge,
id: Hash32 | uint64,
headerWithProof: BlockHeaderWithProof,
): Future[void] {.async: (raises: [CancelledError]).} =
let contentKey = blockHeaderContentKey(id)
peers =
try:
await client.portal_historyGossip(
encodedContentKeyHex, SSZ.encode(headerWithProof).toHex()
)
except CatchableError as e:
return err("JSON-RPC portal_historyGossip failed: " & $e.msg)
info "Block header gossiped", peers, contentKey = encodedContentKeyHex
ok()
await bridge.gossipQueue.addLast(
(contentKey.encode.asSeq(), SSZ.encode(headerWithProof))
)
proc gossipBlockBody(
client: RpcClient,
bridge: PortalHistoryBridge,
hash: Hash32,
body: PortalBlockBodyLegacy | PortalBlockBodyShanghai,
): Future[Result[void, string]] {.async: (raises: [CancelledError]).} =
let
contentKey = blockBodyContentKey(hash)
encodedContentKeyHex = contentKey.encode.asSeq().toHex()
): Future[void] {.async: (raises: [CancelledError]).} =
let contentKey = blockBodyContentKey(hash)
peers =
try:
await client.portal_historyGossip(
encodedContentKeyHex, SSZ.encode(body).toHex()
)
except CatchableError as e:
return err("JSON-RPC portal_historyGossip failed: " & $e.msg)
info "Block body gossiped", peers, contentKey = encodedContentKeyHex
ok()
await bridge.gossipQueue.addLast((contentKey.encode.asSeq(), SSZ.encode(body)))
proc gossipReceipts(
client: RpcClient, hash: Hash32, receipts: PortalReceipts
): Future[Result[void, string]] {.async: (raises: [CancelledError]).} =
let
contentKey = receiptsContentKey(hash)
encodedContentKeyHex = contentKey.encode.asSeq().toHex()
bridge: PortalHistoryBridge, hash: Hash32, receipts: PortalReceipts
): Future[void] {.async: (raises: [CancelledError]).} =
let contentKey = receiptsContentKey(hash)
peers =
try:
await client.portal_historyGossip(
encodedContentKeyHex, SSZ.encode(receipts).toHex()
)
except CatchableError as e:
return err("JSON-RPC portal_historyGossip failed: " & $e.msg)
info "Receipts gossiped", peers, contentKey = encodedContentKeyHex
return ok()
await bridge.gossipQueue.addLast((contentKey.encode.asSeq(), SSZ.encode(receipts)))
proc runLatestLoop(
portalClient: RpcClient, web3Client: RpcClient, validate = false
bridge: PortalHistoryBridge, validate = false
) {.async: (raises: [CancelledError]).} =
## Loop that requests the latest block + receipts and pushes them into the
## Portal network.
@ -211,14 +187,14 @@ proc runLatestLoop(
var lastBlockNumber = 0'u64
while true:
let t0 = Moment.now()
let blockObject = (await getBlockByNumber(web3Client, blockId)).valueOr:
let blockObject = (await bridge.web3Client.getBlockByNumber(blockId)).valueOr:
error "Failed to get latest block", error
await sleepAsync(1.seconds)
continue
let blockNumber = distinctBase(blockObject.number)
if blockNumber > lastBlockNumber:
let receiptObjects = (await web3Client.getBlockReceipts(blockNumber)).valueOr:
let receiptObjects = (await bridge.web3Client.getBlockReceipts(blockNumber)).valueOr:
error "Failed to get latest receipts", error
await sleepAsync(1.seconds)
continue
@ -250,11 +226,9 @@ proc runLatestLoop(
continue
# gossip block header by hash
(await portalClient.gossipBlockHeader(hash, headerWithProof)).isOkOr:
error "Failed to gossip block header", error, hash
await bridge.gossipBlockHeader(hash, headerWithProof)
# gossip block header by number
(await portalClient.gossipBlockHeader(blockNumber, headerWithProof)).isOkOr:
error "Failed to gossip block header", error, hash
await bridge.gossipBlockHeader(blockNumber, headerWithProof)
# For bodies & receipts to get verified, the header needs to be available
# on the network. Wait a little to get the headers propagated through
@ -262,12 +236,9 @@ proc runLatestLoop(
await sleepAsync(2.seconds)
# gossip block body
(await portalClient.gossipBlockBody(hash, body)).isOkOr:
error "Failed to gossip block body", error, hash
await bridge.gossipBlockBody(hash, body)
# gossip receipts
(await portalClient.gossipReceipts(hash, portalReceipts)).isOkOr:
error "Failed to gossip receipts", error, hash
await bridge.gossipReceipts(hash, portalReceipts)
# Making sure here that we poll enough times not to miss a block.
# We could also do some work without awaiting it, e.g. the gossiping or
@ -281,7 +252,7 @@ proc runLatestLoop(
warn "Block gossip took longer than slot interval"
proc gossipHeadersWithProof(
portalClient: RpcClient,
bridge: PortalHistoryBridge,
era1File: string,
epochRecordFile: Opt[string] = Opt.none(string),
verifyEra = false,
@ -312,15 +283,15 @@ proc gossipHeadersWithProof(
blockHash = blockHeader.rlpHash()
# gossip block header by hash
?(await portalClient.gossipBlockHeader(blockHash, headerWithProof))
await bridge.gossipBlockHeader(blockHash, headerWithProof)
# gossip block header by number
?(await portalClient.gossipBlockHeader(blockHeader.number, headerWithProof))
await bridge.gossipBlockHeader(blockHeader.number, headerWithProof)
info "Succesfully gossiped headers from era1 file", era1File
info "Succesfully put headers from era1 file in gossip queue", era1File
ok()
proc gossipBlockContent(
portalClient: RpcClient, era1File: string, verifyEra = false
bridge: PortalHistoryBridge, era1File: string, verifyEra = false
): Future[Result[void, string]] {.async: (raises: [CancelledError]).} =
let f = ?Era1File.open(era1File)
@ -333,28 +304,15 @@ proc gossipBlockContent(
let blockHash = header.rlpHash()
# gossip block body
?(
await portalClient.gossipBlockBody(
blockHash, PortalBlockBodyLegacy.fromBlockBody(body)
)
)
await bridge.gossipBlockBody(blockHash, PortalBlockBodyLegacy.fromBlockBody(body))
# gossip receipts
?(
await portalClient.gossipReceipts(
blockHash, PortalReceipts.fromReceipts(receipts)
)
)
await bridge.gossipReceipts(blockHash, PortalReceipts.fromReceipts(receipts))
info "Succesfully gossiped bodies and receipts from era1 file", era1File
info "Succesfully put bodies and receipts from era1 file in gossip queue", era1File
ok()
proc runBackfillLoop(
portalClient: RpcClient,
web3Client: RpcClient,
era1Dir: string,
startEra: uint64,
endEra: uint64,
bridge: PortalHistoryBridge, era1Dir: string, startEra: uint64, endEra: uint64
) {.async: (raises: [CancelledError]).} =
let accumulator = loadAccumulator()
@ -381,7 +339,7 @@ proc runBackfillLoop(
info "Gossip headers from era1 file", era1File
let headerRes =
try:
await portalClient.portal_debug_historyGossipHeaders(era1File)
await bridge.portalClient.portal_debug_historyGossipHeaders(era1File)
except CatchableError as e:
error "JSON-RPC portal_debug_historyGossipHeaders failed", error = e.msg
false
@ -390,7 +348,7 @@ proc runBackfillLoop(
info "Gossip block content from era1 file", era1File
let res =
try:
await portalClient.portal_debug_historyGossipBlockContent(era1File)
await bridge.portalClient.portal_debug_historyGossipBlockContent(era1File)
except CatchableError as e:
error "JSON-RPC portal_debug_historyGossipBlockContent failed",
error = e.msg
@ -400,16 +358,16 @@ proc runBackfillLoop(
else:
error "Failed to gossip headers from era1 file", era1File
else:
(await portalClient.gossipHeadersWithProof(era1File)).isOkOr:
(await bridge.gossipHeadersWithProof(era1File)).isOkOr:
error "Failed to gossip headers from era1 file", error, era1File
continue
(await portalClient.gossipBlockContent(era1File)).isOkOr:
(await bridge.gossipBlockContent(era1File)).isOkOr:
error "Failed to gossip block content from era1 file", error, era1File
continue
proc runBackfillLoopAuditMode(
portalClient: RpcClient, web3Client: RpcClient, era1Dir: string
bridge: PortalHistoryBridge, era1Dir: string
) {.async: (raises: [CancelledError]).} =
let
rng = newRng()
@ -436,7 +394,7 @@ proc runBackfillLoopAuditMode(
contentHex =
try:
(
await portalClient.portal_historyGetContent(
await bridge.portalClient.portal_historyGetContent(
contentKey.encode.asSeq().toHex()
)
).content
@ -468,7 +426,7 @@ proc runBackfillLoopAuditMode(
contentHex =
try:
(
await portalClient.portal_historyGetContent(
await bridge.portalClient.portal_historyGetContent(
contentKey.encode.asSeq().toHex()
)
).content
@ -496,7 +454,7 @@ proc runBackfillLoopAuditMode(
contentHex =
try:
(
await portalClient.portal_historyGetContent(
await bridge.portalClient.portal_historyGetContent(
contentKey.encode.asSeq().toHex()
)
).content
@ -526,44 +484,58 @@ proc runBackfillLoopAuditMode(
raiseAssert "Failed to build header with proof: " & error
# gossip block header by hash
(await portalClient.gossipBlockHeader(blockHash, headerWithProof)).isOkOr:
error "Failed to gossip block header", error, blockHash
await bridge.gossipBlockHeader(blockHash, headerWithProof)
# gossip block header by number
(await portalClient.gossipBlockHeader(blockNumber, headerWithProof)).isOkOr:
error "Failed to gossip block header", error, blockHash
await bridge.gossipBlockHeader(blockNumber, headerWithProof)
if not bodySuccess:
(
await portalClient.gossipBlockBody(
blockHash, PortalBlockBodyLegacy.fromBlockBody(body)
)
).isOkOr:
error "Failed to gossip block body", error, blockHash
await bridge.gossipBlockBody(blockHash, PortalBlockBodyLegacy.fromBlockBody(body))
if not receiptsSuccess:
(
await portalClient.gossipReceipts(
blockHash, PortalReceipts.fromReceipts(receipts)
)
).isOkOr:
error "Failed to gossip receipts", error, blockHash
await bridge.gossipReceipts(blockHash, PortalReceipts.fromReceipts(receipts))
await sleepAsync(2.seconds)
proc runHistory*(config: PortalBridgeConf) =
let
portalClient = newRpcClientConnect(config.portalRpcUrl)
web3Client = newRpcClientConnect(config.web3Url)
let bridge = PortalHistoryBridge(
portalClient: newRpcClientConnect(config.portalRpcUrl),
web3Client: newRpcClientConnect(config.web3Url),
gossipQueue: newAsyncQueue[(seq[byte], seq[byte])](config.gossipConcurrency),
)
proc gossipWorker(bridge: PortalHistoryBridge) {.async: (raises: []).} =
try:
while true:
let
(contentKey, contentValue) = await bridge.gossipQueue.popFirst()
contentKeyHex = contentKey.toHex()
contentValueHex = contentValue.toHex()
try:
let peers = await bridge.portalClient.portal_historyGossip(
contentKeyHex, contentValueHex
)
debug "Content gossiped", peers, contentKey = contentKeyHex
except CancelledError as e:
trace "Cancelled gossipWorker"
raise e
except CatchableError as e:
error "JSON-RPC portal_historyGossip failed",
error = $e.msg, contentKey = contentKeyHex
except CancelledError:
trace "gossipWorker canceled"
var workers: seq[Future[void]] = @[]
for i in 0 ..< config.gossipConcurrency:
workers.add bridge.gossipWorker()
if config.latest:
asyncSpawn runLatestLoop(portalClient, web3Client, config.blockVerify)
asyncSpawn bridge.runLatestLoop(config.blockVerify)
if config.backfill:
if config.audit:
asyncSpawn runBackfillLoopAuditMode(
portalClient, web3Client, config.era1Dir.string
)
asyncSpawn bridge.runBackfillLoopAuditMode(config.era1Dir.string)
else:
asyncSpawn runBackfillLoop(
portalClient, web3Client, config.era1Dir.string, config.startEra, config.endEra
asyncSpawn bridge.runBackfillLoop(
config.era1Dir.string, config.startEra, config.endEra
)
while true: