Add Era1 backfill to portal_bridge history mode (#2077)

This commit is contained in:
Kim De Mey 2024-03-15 10:41:41 +01:00 committed by GitHub
parent 0d73637f14
commit 461c7e7d85
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 172 additions and 4 deletions

View File

@ -329,7 +329,13 @@ proc run(config: PortalConf) {.raises: [CatchableError].} =
## Starting the JSON-RPC APIs
if config.rpcEnabled:
let ta = initTAddress(config.rpcAddress, config.rpcPort)
var rpcHttpServerWithProxy = RpcProxy.new([ta], config.proxyUri)
let rpcHttpServer = RpcHttpServer.new()
# Note: Set maxRequestBodySize to 4MB instead of 1MB as there are blocks
# that reach that limit (in hex, for gossip method).
rpcHttpServer.addHttpServer(ta, maxRequestBodySize = 4 * 1_048_576)
var rpcHttpServerWithProxy = RpcProxy.new(rpcHttpServer, config.proxyUri)
rpcHttpServerWithProxy.installDiscoveryApiHandlers(d)
rpcHttpServerWithProxy.installWeb3ApiHandlers()
if stateNetwork.isSome():

View File

@ -19,6 +19,7 @@ createRpcSigsFromNim(RpcClient):
era1File: string, epochAccumulatorFile: Opt[string]
): bool
proc portal_historyGossipHeaders(era1File: string): bool
proc portal_historyGossipBlockContent(era1File: string): bool
proc portal_history_storeContent(dataFile: string): bool
proc portal_history_propagate(dataFile: string): bool

View File

@ -7,10 +7,24 @@
{.push raises: [].}
import std/uri, confutils, confutils/std/net, nimcrypto/hash, ../../logging
import std/[os, uri], confutils, confutils/std/net, nimcrypto/hash, ../../logging
export net
proc defaultEthDataDir*(): string =
let dataDir =
when defined(windows):
"AppData" / "Roaming" / "EthData"
elif defined(macosx):
"Library" / "Application Support" / "EthData"
else:
".cache" / "eth-data"
getHomeDir() / dataDir
proc defaultEra1DataDir*(): string =
defaultEthDataDir() / "era1"
type
TrustedDigest* = MDigest[32 * 8]
@ -83,6 +97,27 @@ type
defaultValue: false,
name: "block-verify"
.}: bool
latest* {.
desc:
"Follow the head of the chain and gossip latest block header, body and receipts into the network",
defaultValue: true,
name: "latest"
.}: bool
backfill* {.
desc:
"Randomly backfill block headers, bodies and receipts into the network from the era1 files",
defaultValue: false,
name: "backfill"
.}: bool
era1Dir* {.
desc: "The directory where all era1 files are stored",
defaultValue: defaultEra1DataDir(),
defaultValueDesc: defaultEra1DataDir(),
name: "era1-dir"
.}: InputDir
of PortalBridgeCmd.state:
discard

View File

@ -14,10 +14,14 @@ import
results,
stew/byteutils,
eth/common/[eth_types, eth_types_rlp],
eth/keys,
eth/p2p/discoveryv5/random2,
../../../nimbus/beacon/web3_eth_conv,
../../../hive_integration/nodocker/engine/engine_client,
../../rpc/[portal_rpc_client],
../../rpc/portal_rpc_client,
../../network/history/[history_content, history_network],
../../network_metadata,
../../eth_data/[era1, history_data_ssz_e2s, history_data_seeding],
./portal_bridge_conf
from stew/objects import checkedEnumAssign
@ -285,6 +289,124 @@ proc runLatestLoop(
else:
warn "Block gossip took longer than the poll interval"
proc gossipHeadersWithProof(
portalClient: RpcClient,
era1File: string,
epochAccumulatorFile: Opt[string] = Opt.none(string),
verifyEra = false,
): Future[Result[void, string]] {.async: (raises: []).} =
let f = ?Era1File.open(era1File)
if verifyEra:
let _ = ?f.verify()
# Note: building the accumulator takes about 150ms vs 10ms for reading it,
# so it is probably not really worth using the read version considering the
# UX hassle it adds to provide the accumulator ssz files.
let epochAccumulator =
if epochAccumulatorFile.isNone:
?f.buildAccumulator()
else:
?readEpochAccumulatorCached(epochAccumulatorFile.get())
for (contentKey, contentValue) in f.headersWithProof(epochAccumulator):
let peers =
try:
await portalClient.portal_historyGossip(
contentKey.asSeq.toHex(), contentValue.toHex()
)
except CatchableError as e:
return err("JSON-RPC error: " & $e.msg)
info "Block header gossiped", peers, contentKey
ok()
proc gossipBlockContent(
portalClient: RpcClient, era1File: string, verifyEra = false
): Future[Result[void, string]] {.async: (raises: []).} =
let f = ?Era1File.open(era1File)
if verifyEra:
let _ = ?f.verify()
for (contentKey, contentValue) in f.blockContent():
let peers =
try:
await portalClient.portal_historyGossip(
contentKey.asSeq.toHex(), contentValue.toHex()
)
except CatchableError as e:
return err("JSON-RPC error: " & $e.msg)
info "Block content gossiped", peers, contentKey
ok()
proc runBackfillLoop(
portalClient: RpcClient, web3Client: RpcClient, era1Dir: string
) {.async: (raises: [CancelledError]).} =
let
rng = newRng()
accumulator =
try:
SSZ.decode(finishedAccumulator, FinishedAccumulator)
except SerializationError as err:
raiseAssert "Invalid baked-in accumulator: " & err.msg
while true:
let
# Grab a random era1 to backfill
era = rng[].rand(int(era(network_metadata.mergeBlockNumber - 1)))
root = accumulator.historicalEpochs[era]
eraFile = era1FileName("mainnet", Era1(era), Digest(data: root))
# Note:
# There are two design options here:
# 1. Provide the Era1 file through the fluffy custom debug API and let
# fluffy process the Era1 file and gossip the content from there.
# 2. Process the Era1 files in the bridge and call the
# standardized gossip JSON-RPC method.
#
# Option 2. is more conceptually clean and compatible due to no usage of
# custom API, however it will involve invoking a lot of JSON-RPC calls
# to pass along block data (in hex).
# Option 2. is used here. Switch to Option 1. can be made in case efficiency
# turns out the be a problem. It is however a bit more tricky to know when a
# new era1 can be gossiped (might need another custom json-rpc that checks
# the offer queue)
when false:
info "Gossip headers from era1 file", eraFile
let headerRes =
try:
await portalClient.portal_historyGossipHeaders(eraFile)
except CatchableError as e:
error "JSON-RPC method failed", error = e.msg
false
if headerRes:
info "Gossip block content from era1 file", eraFile
let res =
try:
await portalClient.portal_historyGossipBlockContent(eraFile)
except CatchableError as e:
error "JSON-RPC method failed", error = e.msg
false
if res:
error "Failed to gossip block content from era1 file", eraFile
else:
error "Failed to gossip headers from era1 file", eraFile
else:
info "Gossip headers from era1 file", eraFile
(await portalClient.gossipHeadersWithProof(eraFile)).isOkOr:
error "Failed to gossip headers from era1 file", error, eraFile
continue
info "Gossip block content from era1 file", eraFile
(await portalClient.gossipBlockContent(eraFile)).isOkOr:
error "Failed to gossip block content from era1 file", error, eraFile
continue
info "Succesfully gossiped era1 file", eraFile
proc runHistory*(config: PortalBridgeConf) =
let
portalClient = newRpcHttpClient()
@ -306,7 +428,11 @@ proc runHistory*(config: PortalBridgeConf) =
except CatchableError as e:
error "Failed to connect to web3 RPC", error = $e.msg
asyncSpawn runLatestLoop(portalClient, web3Client, config.blockVerify)
if config.latest:
asyncSpawn runLatestLoop(portalClient, web3Client, config.blockVerify)
if config.backfill:
asyncSpawn runBackfillLoop(portalClient, web3Client, config.era1Dir.string)
while true:
poll()