portal_bridge: Add concurrency to the history content gossip (#2855)
This commit is contained in:
parent
107db3ae16
commit
453cb2f33e
|
@ -141,6 +141,13 @@ type
|
||||||
defaultValueDesc: defaultEra1DataDir(),
|
defaultValueDesc: defaultEra1DataDir(),
|
||||||
name: "era1-dir"
|
name: "era1-dir"
|
||||||
.}: InputDir
|
.}: 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:
|
of PortalBridgeCmd.state:
|
||||||
web3UrlState* {.desc: "Execution layer JSON-RPC API URL", name: "web3-url".}:
|
web3UrlState* {.desc: "Execution layer JSON-RPC API URL", name: "web3-url".}:
|
||||||
JsonRpcUrl
|
JsonRpcUrl
|
||||||
|
|
|
@ -31,6 +31,11 @@ from eth/common/eth_types_rlp import rlpHash
|
||||||
|
|
||||||
const newHeadPollInterval = 6.seconds # Slot with potential block is every 12s
|
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
|
## Conversion functions for Block and Receipts
|
||||||
|
|
||||||
func asEthBlock(blockObject: BlockObject): EthBlock =
|
func asEthBlock(blockObject: BlockObject): EthBlock =
|
||||||
|
@ -139,63 +144,34 @@ proc getBlockReceipts(
|
||||||
## Portal JSON-RPC API helper calls for pushing block and receipts
|
## Portal JSON-RPC API helper calls for pushing block and receipts
|
||||||
|
|
||||||
proc gossipBlockHeader(
|
proc gossipBlockHeader(
|
||||||
client: RpcClient, id: Hash32 | uint64, headerWithProof: BlockHeaderWithProof
|
bridge: PortalHistoryBridge,
|
||||||
): Future[Result[void, string]] {.async: (raises: [CancelledError]).} =
|
id: Hash32 | uint64,
|
||||||
let
|
headerWithProof: BlockHeaderWithProof,
|
||||||
contentKey = blockHeaderContentKey(id)
|
): Future[void] {.async: (raises: [CancelledError]).} =
|
||||||
encodedContentKeyHex = contentKey.encode.asSeq().toHex()
|
let contentKey = blockHeaderContentKey(id)
|
||||||
|
|
||||||
peers =
|
await bridge.gossipQueue.addLast(
|
||||||
try:
|
(contentKey.encode.asSeq(), SSZ.encode(headerWithProof))
|
||||||
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()
|
|
||||||
|
|
||||||
proc gossipBlockBody(
|
proc gossipBlockBody(
|
||||||
client: RpcClient,
|
bridge: PortalHistoryBridge,
|
||||||
hash: Hash32,
|
hash: Hash32,
|
||||||
body: PortalBlockBodyLegacy | PortalBlockBodyShanghai,
|
body: PortalBlockBodyLegacy | PortalBlockBodyShanghai,
|
||||||
): Future[Result[void, string]] {.async: (raises: [CancelledError]).} =
|
): Future[void] {.async: (raises: [CancelledError]).} =
|
||||||
let
|
let contentKey = blockBodyContentKey(hash)
|
||||||
contentKey = blockBodyContentKey(hash)
|
|
||||||
encodedContentKeyHex = contentKey.encode.asSeq().toHex()
|
|
||||||
|
|
||||||
peers =
|
await bridge.gossipQueue.addLast((contentKey.encode.asSeq(), SSZ.encode(body)))
|
||||||
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()
|
|
||||||
|
|
||||||
proc gossipReceipts(
|
proc gossipReceipts(
|
||||||
client: RpcClient, hash: Hash32, receipts: PortalReceipts
|
bridge: PortalHistoryBridge, hash: Hash32, receipts: PortalReceipts
|
||||||
): Future[Result[void, string]] {.async: (raises: [CancelledError]).} =
|
): Future[void] {.async: (raises: [CancelledError]).} =
|
||||||
let
|
let contentKey = receiptsContentKey(hash)
|
||||||
contentKey = receiptsContentKey(hash)
|
|
||||||
encodedContentKeyHex = contentKey.encode.asSeq().toHex()
|
|
||||||
|
|
||||||
peers =
|
await bridge.gossipQueue.addLast((contentKey.encode.asSeq(), SSZ.encode(receipts)))
|
||||||
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()
|
|
||||||
|
|
||||||
proc runLatestLoop(
|
proc runLatestLoop(
|
||||||
portalClient: RpcClient, web3Client: RpcClient, validate = false
|
bridge: PortalHistoryBridge, validate = false
|
||||||
) {.async: (raises: [CancelledError]).} =
|
) {.async: (raises: [CancelledError]).} =
|
||||||
## Loop that requests the latest block + receipts and pushes them into the
|
## Loop that requests the latest block + receipts and pushes them into the
|
||||||
## Portal network.
|
## Portal network.
|
||||||
|
@ -211,14 +187,14 @@ proc runLatestLoop(
|
||||||
var lastBlockNumber = 0'u64
|
var lastBlockNumber = 0'u64
|
||||||
while true:
|
while true:
|
||||||
let t0 = Moment.now()
|
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
|
error "Failed to get latest block", error
|
||||||
await sleepAsync(1.seconds)
|
await sleepAsync(1.seconds)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
let blockNumber = distinctBase(blockObject.number)
|
let blockNumber = distinctBase(blockObject.number)
|
||||||
if blockNumber > lastBlockNumber:
|
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
|
error "Failed to get latest receipts", error
|
||||||
await sleepAsync(1.seconds)
|
await sleepAsync(1.seconds)
|
||||||
continue
|
continue
|
||||||
|
@ -250,11 +226,9 @@ proc runLatestLoop(
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# gossip block header by hash
|
# gossip block header by hash
|
||||||
(await portalClient.gossipBlockHeader(hash, headerWithProof)).isOkOr:
|
await bridge.gossipBlockHeader(hash, headerWithProof)
|
||||||
error "Failed to gossip block header", error, hash
|
|
||||||
# gossip block header by number
|
# gossip block header by number
|
||||||
(await portalClient.gossipBlockHeader(blockNumber, headerWithProof)).isOkOr:
|
await bridge.gossipBlockHeader(blockNumber, headerWithProof)
|
||||||
error "Failed to gossip block header", error, hash
|
|
||||||
|
|
||||||
# For bodies & receipts to get verified, the header needs to be available
|
# For bodies & receipts to get verified, the header needs to be available
|
||||||
# on the network. Wait a little to get the headers propagated through
|
# on the network. Wait a little to get the headers propagated through
|
||||||
|
@ -262,12 +236,9 @@ proc runLatestLoop(
|
||||||
await sleepAsync(2.seconds)
|
await sleepAsync(2.seconds)
|
||||||
|
|
||||||
# gossip block body
|
# gossip block body
|
||||||
(await portalClient.gossipBlockBody(hash, body)).isOkOr:
|
await bridge.gossipBlockBody(hash, body)
|
||||||
error "Failed to gossip block body", error, hash
|
|
||||||
|
|
||||||
# gossip receipts
|
# gossip receipts
|
||||||
(await portalClient.gossipReceipts(hash, portalReceipts)).isOkOr:
|
await bridge.gossipReceipts(hash, portalReceipts)
|
||||||
error "Failed to gossip receipts", error, hash
|
|
||||||
|
|
||||||
# Making sure here that we poll enough times not to miss a block.
|
# 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
|
# 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"
|
warn "Block gossip took longer than slot interval"
|
||||||
|
|
||||||
proc gossipHeadersWithProof(
|
proc gossipHeadersWithProof(
|
||||||
portalClient: RpcClient,
|
bridge: PortalHistoryBridge,
|
||||||
era1File: string,
|
era1File: string,
|
||||||
epochRecordFile: Opt[string] = Opt.none(string),
|
epochRecordFile: Opt[string] = Opt.none(string),
|
||||||
verifyEra = false,
|
verifyEra = false,
|
||||||
|
@ -312,15 +283,15 @@ proc gossipHeadersWithProof(
|
||||||
blockHash = blockHeader.rlpHash()
|
blockHash = blockHeader.rlpHash()
|
||||||
|
|
||||||
# gossip block header by hash
|
# gossip block header by hash
|
||||||
?(await portalClient.gossipBlockHeader(blockHash, headerWithProof))
|
await bridge.gossipBlockHeader(blockHash, headerWithProof)
|
||||||
# gossip block header by number
|
# 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()
|
ok()
|
||||||
|
|
||||||
proc gossipBlockContent(
|
proc gossipBlockContent(
|
||||||
portalClient: RpcClient, era1File: string, verifyEra = false
|
bridge: PortalHistoryBridge, era1File: string, verifyEra = false
|
||||||
): Future[Result[void, string]] {.async: (raises: [CancelledError]).} =
|
): Future[Result[void, string]] {.async: (raises: [CancelledError]).} =
|
||||||
let f = ?Era1File.open(era1File)
|
let f = ?Era1File.open(era1File)
|
||||||
|
|
||||||
|
@ -333,28 +304,15 @@ proc gossipBlockContent(
|
||||||
let blockHash = header.rlpHash()
|
let blockHash = header.rlpHash()
|
||||||
|
|
||||||
# gossip block body
|
# gossip block body
|
||||||
?(
|
await bridge.gossipBlockBody(blockHash, PortalBlockBodyLegacy.fromBlockBody(body))
|
||||||
await portalClient.gossipBlockBody(
|
|
||||||
blockHash, PortalBlockBodyLegacy.fromBlockBody(body)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# gossip receipts
|
# gossip receipts
|
||||||
?(
|
await bridge.gossipReceipts(blockHash, PortalReceipts.fromReceipts(receipts))
|
||||||
await portalClient.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()
|
ok()
|
||||||
|
|
||||||
proc runBackfillLoop(
|
proc runBackfillLoop(
|
||||||
portalClient: RpcClient,
|
bridge: PortalHistoryBridge, era1Dir: string, startEra: uint64, endEra: uint64
|
||||||
web3Client: RpcClient,
|
|
||||||
era1Dir: string,
|
|
||||||
startEra: uint64,
|
|
||||||
endEra: uint64,
|
|
||||||
) {.async: (raises: [CancelledError]).} =
|
) {.async: (raises: [CancelledError]).} =
|
||||||
let accumulator = loadAccumulator()
|
let accumulator = loadAccumulator()
|
||||||
|
|
||||||
|
@ -381,7 +339,7 @@ proc runBackfillLoop(
|
||||||
info "Gossip headers from era1 file", era1File
|
info "Gossip headers from era1 file", era1File
|
||||||
let headerRes =
|
let headerRes =
|
||||||
try:
|
try:
|
||||||
await portalClient.portal_debug_historyGossipHeaders(era1File)
|
await bridge.portalClient.portal_debug_historyGossipHeaders(era1File)
|
||||||
except CatchableError as e:
|
except CatchableError as e:
|
||||||
error "JSON-RPC portal_debug_historyGossipHeaders failed", error = e.msg
|
error "JSON-RPC portal_debug_historyGossipHeaders failed", error = e.msg
|
||||||
false
|
false
|
||||||
|
@ -390,7 +348,7 @@ proc runBackfillLoop(
|
||||||
info "Gossip block content from era1 file", era1File
|
info "Gossip block content from era1 file", era1File
|
||||||
let res =
|
let res =
|
||||||
try:
|
try:
|
||||||
await portalClient.portal_debug_historyGossipBlockContent(era1File)
|
await bridge.portalClient.portal_debug_historyGossipBlockContent(era1File)
|
||||||
except CatchableError as e:
|
except CatchableError as e:
|
||||||
error "JSON-RPC portal_debug_historyGossipBlockContent failed",
|
error "JSON-RPC portal_debug_historyGossipBlockContent failed",
|
||||||
error = e.msg
|
error = e.msg
|
||||||
|
@ -400,16 +358,16 @@ proc runBackfillLoop(
|
||||||
else:
|
else:
|
||||||
error "Failed to gossip headers from era1 file", era1File
|
error "Failed to gossip headers from era1 file", era1File
|
||||||
else:
|
else:
|
||||||
(await portalClient.gossipHeadersWithProof(era1File)).isOkOr:
|
(await bridge.gossipHeadersWithProof(era1File)).isOkOr:
|
||||||
error "Failed to gossip headers from era1 file", error, era1File
|
error "Failed to gossip headers from era1 file", error, era1File
|
||||||
continue
|
continue
|
||||||
|
|
||||||
(await portalClient.gossipBlockContent(era1File)).isOkOr:
|
(await bridge.gossipBlockContent(era1File)).isOkOr:
|
||||||
error "Failed to gossip block content from era1 file", error, era1File
|
error "Failed to gossip block content from era1 file", error, era1File
|
||||||
continue
|
continue
|
||||||
|
|
||||||
proc runBackfillLoopAuditMode(
|
proc runBackfillLoopAuditMode(
|
||||||
portalClient: RpcClient, web3Client: RpcClient, era1Dir: string
|
bridge: PortalHistoryBridge, era1Dir: string
|
||||||
) {.async: (raises: [CancelledError]).} =
|
) {.async: (raises: [CancelledError]).} =
|
||||||
let
|
let
|
||||||
rng = newRng()
|
rng = newRng()
|
||||||
|
@ -436,7 +394,7 @@ proc runBackfillLoopAuditMode(
|
||||||
contentHex =
|
contentHex =
|
||||||
try:
|
try:
|
||||||
(
|
(
|
||||||
await portalClient.portal_historyGetContent(
|
await bridge.portalClient.portal_historyGetContent(
|
||||||
contentKey.encode.asSeq().toHex()
|
contentKey.encode.asSeq().toHex()
|
||||||
)
|
)
|
||||||
).content
|
).content
|
||||||
|
@ -468,7 +426,7 @@ proc runBackfillLoopAuditMode(
|
||||||
contentHex =
|
contentHex =
|
||||||
try:
|
try:
|
||||||
(
|
(
|
||||||
await portalClient.portal_historyGetContent(
|
await bridge.portalClient.portal_historyGetContent(
|
||||||
contentKey.encode.asSeq().toHex()
|
contentKey.encode.asSeq().toHex()
|
||||||
)
|
)
|
||||||
).content
|
).content
|
||||||
|
@ -496,7 +454,7 @@ proc runBackfillLoopAuditMode(
|
||||||
contentHex =
|
contentHex =
|
||||||
try:
|
try:
|
||||||
(
|
(
|
||||||
await portalClient.portal_historyGetContent(
|
await bridge.portalClient.portal_historyGetContent(
|
||||||
contentKey.encode.asSeq().toHex()
|
contentKey.encode.asSeq().toHex()
|
||||||
)
|
)
|
||||||
).content
|
).content
|
||||||
|
@ -526,44 +484,58 @@ proc runBackfillLoopAuditMode(
|
||||||
raiseAssert "Failed to build header with proof: " & error
|
raiseAssert "Failed to build header with proof: " & error
|
||||||
|
|
||||||
# gossip block header by hash
|
# gossip block header by hash
|
||||||
(await portalClient.gossipBlockHeader(blockHash, headerWithProof)).isOkOr:
|
await bridge.gossipBlockHeader(blockHash, headerWithProof)
|
||||||
error "Failed to gossip block header", error, blockHash
|
|
||||||
# gossip block header by number
|
# gossip block header by number
|
||||||
(await portalClient.gossipBlockHeader(blockNumber, headerWithProof)).isOkOr:
|
await bridge.gossipBlockHeader(blockNumber, headerWithProof)
|
||||||
error "Failed to gossip block header", error, blockHash
|
|
||||||
if not bodySuccess:
|
if not bodySuccess:
|
||||||
(
|
await bridge.gossipBlockBody(blockHash, PortalBlockBodyLegacy.fromBlockBody(body))
|
||||||
await portalClient.gossipBlockBody(
|
|
||||||
blockHash, PortalBlockBodyLegacy.fromBlockBody(body)
|
|
||||||
)
|
|
||||||
).isOkOr:
|
|
||||||
error "Failed to gossip block body", error, blockHash
|
|
||||||
if not receiptsSuccess:
|
if not receiptsSuccess:
|
||||||
(
|
await bridge.gossipReceipts(blockHash, PortalReceipts.fromReceipts(receipts))
|
||||||
await portalClient.gossipReceipts(
|
|
||||||
blockHash, PortalReceipts.fromReceipts(receipts)
|
|
||||||
)
|
|
||||||
).isOkOr:
|
|
||||||
error "Failed to gossip receipts", error, blockHash
|
|
||||||
|
|
||||||
await sleepAsync(2.seconds)
|
await sleepAsync(2.seconds)
|
||||||
|
|
||||||
proc runHistory*(config: PortalBridgeConf) =
|
proc runHistory*(config: PortalBridgeConf) =
|
||||||
let
|
let bridge = PortalHistoryBridge(
|
||||||
portalClient = newRpcClientConnect(config.portalRpcUrl)
|
portalClient: newRpcClientConnect(config.portalRpcUrl),
|
||||||
web3Client = newRpcClientConnect(config.web3Url)
|
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:
|
if config.latest:
|
||||||
asyncSpawn runLatestLoop(portalClient, web3Client, config.blockVerify)
|
asyncSpawn bridge.runLatestLoop(config.blockVerify)
|
||||||
|
|
||||||
if config.backfill:
|
if config.backfill:
|
||||||
if config.audit:
|
if config.audit:
|
||||||
asyncSpawn runBackfillLoopAuditMode(
|
asyncSpawn bridge.runBackfillLoopAuditMode(config.era1Dir.string)
|
||||||
portalClient, web3Client, config.era1Dir.string
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
asyncSpawn runBackfillLoop(
|
asyncSpawn bridge.runBackfillLoop(
|
||||||
portalClient, web3Client, config.era1Dir.string, config.startEra, config.endEra
|
config.era1Dir.string, config.startEra, config.endEra
|
||||||
)
|
)
|
||||||
|
|
||||||
while true:
|
while true:
|
||||||
|
|
Loading…
Reference in New Issue