Add exporting of headersWithProof and configurable web3 url (#1539)

This commit is contained in:
Kim De Mey 2023-04-11 14:06:45 +02:00 committed by GitHub
parent 9facab91cb
commit 034d0e6305
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 175 additions and 13 deletions

View File

@ -122,7 +122,7 @@ func getEpochIndex*(header: BlockHeader): uint64 =
## Get the index for the historical epochs ## Get the index for the historical epochs
getEpochIndex(blockNumber) getEpochIndex(blockNumber)
func getHeaderRecordIndex(blockNumber: uint64, epochIndex: uint64): uint64 = func getHeaderRecordIndex*(blockNumber: uint64, epochIndex: uint64): uint64 =
## Get the relative header index for the epoch accumulator ## Get the relative header index for the epoch accumulator
uint64(blockNumber - epochIndex * epochSize) uint64(blockNumber - epochIndex * epochSize)

View File

@ -37,7 +37,7 @@
{.push raises: [].} {.push raises: [].}
import import
std/[json, typetraits, strutils, strformat, os], std/[json, typetraits, strutils, strformat, os, uri],
confutils, confutils,
stew/[byteutils, io2], stew/[byteutils, io2],
json_serialization, json_serialization,
@ -64,10 +64,19 @@ proc defaultDataDir*(): string =
getHomeDir() / dataDir getHomeDir() / dataDir
type
Web3UrlKind* = enum
HttpUrl, WsUrl
Web3Url* = object
kind*: Web3UrlKind
url*: string
const const
defaultDataDirDesc = defaultDataDir() defaultDataDirDesc = defaultDataDir()
defaultBlockFileName = "eth-block-data" defaultBlockFileName = "eth-block-data"
defaultAccumulatorFileName = "mainnet-master-accumulator.ssz" defaultAccumulatorFileName = "mainnet-master-accumulator.ssz"
defaultWeb3Url = Web3Url(kind: HttpUrl, url: "http://127.0.0.1:8545")
type type
ExporterCmd* = enum ExporterCmd* = enum
@ -85,6 +94,8 @@ type
"Print the root hash of the master accumulator and of all historical epoch accumulators. Requires data generated by exportAccumulatorData command" "Print the root hash of the master accumulator and of all historical epoch accumulators. Requires data generated by exportAccumulatorData command"
exportHeaderRange = exportHeaderRange =
"Export block headers from an Ethereum JSON RPC Execution endpoint to *.e2s files (unlimited amount)" "Export block headers from an Ethereum JSON RPC Execution endpoint to *.e2s files (unlimited amount)"
exportHeadersWithProof =
"Export block headers with proof from *.e2s headers file and epochAccumulator files"
StorageMode* = enum StorageMode* = enum
Json, Db Json, Db
@ -100,6 +111,10 @@ type
defaultValue: defaultDataDir() defaultValue: defaultDataDir()
defaultValueDesc: $defaultDataDirDesc defaultValueDesc: $defaultDataDirDesc
name: "data-dir" .}: OutDir name: "data-dir" .}: OutDir
web3Url* {.
desc: "Execution layer JSON-RPC API URL"
defaultValue: defaultWeb3Url
name: "web3-url" .}: Web3Url
case cmd* {. case cmd* {.
command command
defaultValue: exportBlockData .}: ExporterCmd defaultValue: exportBlockData .}: ExporterCmd
@ -169,6 +184,32 @@ type
endBlockNumber* {. endBlockNumber* {.
desc: "Number of the last block header to be exported" desc: "Number of the last block header to be exported"
name: "end-block" .}: uint64 name: "end-block" .}: uint64
of exportHeadersWithProof:
startBlockNumber2* {.
desc: "Number of the first block header to be exported"
name: "start-block" .}: uint64
endBlockNumber2* {.
desc: "Number of the last block header to be exported"
name: "end-block" .}: uint64
proc parseCmdArg*(
T: type Web3Url, p: string): T {.raises: [ConfigurationError].} =
let
url = parseUri(p)
normalizedScheme = url.scheme.toLowerAscii()
if (normalizedScheme == "http" or normalizedScheme == "https"):
Web3Url(kind: HttpUrl, url: p)
elif (normalizedScheme == "ws" or normalizedScheme == "wss"):
Web3Url(kind: WsUrl, url: p)
else:
raise newException(
ConfigurationError,
"The Web3 URL must specify one of following protocols: http/https/ws/wss"
)
proc completeCmdArg*(T: type Web3Url, val: string): seq[string] =
return @[]
proc parseCmdArg*(T: type StorageMode, p: string): T proc parseCmdArg*(T: type StorageMode, p: string): T
{.raises: [ConfigurationError].} = {.raises: [ConfigurationError].} =
@ -319,6 +360,34 @@ proc exportBlocks(config: ExporterConf, client: RpcClient) =
else: else:
writeBlocksToDb(config, client) writeBlocksToDb(config, client)
proc newRpcClient(web3Url: Web3Url): RpcClient =
# TODO: I don't like this API. I think the creation of the RPC clients should
# already include the URL. And then an optional connect may be necessary
# depending on the protocol.
let client: RpcClient =
case web3Url.kind
of HttpUrl:
newRpcHttpClient()
of WsUrl:
newRpcWebSocketClient()
client
proc connectRpcClient(
client: RpcClient, web3Url: Web3Url):
Future[Result[void, string]] {.async.} =
case web3Url.kind
of HttpUrl:
try:
await RpcHttpClient(client).connect(web3Url.url)
except CatchableError as e:
return err(e.msg)
of WsUrl:
try:
await RpcWebSocketClient(client).connect(web3Url.url)
except CatchableError as e:
return err(e.msg)
when isMainModule: when isMainModule:
{.pop.} {.pop.}
let config = ExporterConf.load() let config = ExporterConf.load()
@ -334,19 +403,14 @@ when isMainModule:
dir = dataDir, error = ioErrorMsg(res.error) dir = dataDir, error = ioErrorMsg(res.error)
quit 1 quit 1
var client: RpcClient
try:
let c = newRpcWebSocketClient()
# TODO: Hardcoded to the default geth ws address. This should become
# a configurable cli option
waitFor c.connect("ws://127.0.0.1:8546")
client = c
except CatchableError as e:
fatal "Error while connecting to data provider", error = e.msg
quit 1
case config.cmd case config.cmd
of ExporterCmd.exportBlockData: of ExporterCmd.exportBlockData:
let client = newRpcClient(config.web3Url)
let connectRes = waitFor client.connectRpcClient(config.web3Url)
if connectRes.isErr():
fatal "Failed connecting to JSON-RPC client", error = connectRes.error
quit 1
if (config.endBlock < config.startBlock): if (config.endBlock < config.startBlock):
fatal "Initial block number should be smaller than end block number", fatal "Initial block number should be smaller than end block number",
startBlock = config.startBlock, startBlock = config.startBlock,
@ -359,6 +423,12 @@ when isMainModule:
waitFor client.close() waitFor client.close()
of ExporterCmd.exportEpochHeaders: of ExporterCmd.exportEpochHeaders:
let client = newRpcClient(config.web3Url)
let connectRes = waitFor client.connectRpcClient(config.web3Url)
if connectRes.isErr():
fatal "Failed connecting to JSON-RPC client", error = connectRes.error
quit 1
proc exportEpochHeaders(file: string, epoch: uint64): Result[void, string] = proc exportEpochHeaders(file: string, epoch: uint64): Result[void, string] =
# Downloading headers from JSON RPC endpoint # Downloading headers from JSON RPC endpoint
info "Requesting epoch headers", epoch info "Requesting epoch headers", epoch
@ -547,6 +617,12 @@ when isMainModule:
echo &"{i.uint64:05} 0x{root.toHex()}" echo &"{i.uint64:05} 0x{root.toHex()}"
of ExporterCmd.exportHeaderRange: of ExporterCmd.exportHeaderRange:
let client = newRpcClient(config.web3Url)
let connectRes = waitFor client.connectRpcClient(config.web3Url)
if connectRes.isErr():
fatal "Failed connecting to JSON-RPC client", error = connectRes.error
quit 1
let let
startBlockNumber = config.startBlockNumber startBlockNumber = config.startBlockNumber
endBlockNumber = config.endBlockNumber endBlockNumber = config.endBlockNumber
@ -585,3 +661,89 @@ when isMainModule:
if res.isErr(): if res.isErr():
fatal "Failed exporting headers", error = res.error fatal "Failed exporting headers", error = res.error
quit 1 quit 1
of ExporterCmd.exportHeadersWithProof:
let
startBlockNumber = config.startBlockNumber2
endBlockNumber = config.endBlockNumber2
if (endBlockNumber < startBlockNumber):
fatal "Start block number should be smaller than end block number",
startBlockNumber, endBlockNumber
quit 1
type
JsonPortalContent = object
content_key*: string
content_value*: string
JsonPortalContentTable = OrderedTable[uint64, JsonPortalContent]
proc writePortalContentToJson(
fh: OutputStreamHandle, content: JsonPortalContentTable) =
try:
var writer = JsonWriter[DefaultFlavor].init(fh.s, pretty = true)
writer.writeValue(content)
except IOError as e:
fatal "Error occured while writing to file", error = e.msg
quit 1
let file = &"mainnet-headersWithProof-{startBlockNumber:05}-{endBlockNumber:05}.json"
let fh = createAndOpenFile(string config.dataDir, file)
var contentTable: JsonPortalContentTable
for blockNumber in startBlockNumber..endBlockNumber:
let
epochIndex = getEpochIndex(blockNumber)
epochHeadersFile =
dataDir / &"mainnet-headers-epoch-{epochIndex:05}.e2s"
epochAccumulatorFile =
dataDir / &"mainnet-epoch-accumulator-{epochIndex:05}.ssz"
let res = readBlockHeaders(epochHeadersFile)
if res.isErr():
error "Could not read headers epoch file", error = res.error
quit 1
let blockHeaders = res.get()
let epochAccumulatorRes = readEpochAccumulatorCached(epochAccumulatorFile)
if epochAccumulatorRes.isErr():
error "Could not read epoch accumulator file", error = res.error
quit 1
let epochAccumulator = epochAccumulatorRes.get()
let headerIndex = getHeaderRecordIndex(blockNumber, epochIndex)
let header = blockHeaders[headerIndex]
if header.isPreMerge():
let headerWithProof = buildHeaderWithProof(header, epochAccumulator)
if headerWithProof.isErr:
error "Error building proof", error = headerWithProof.error
quit 1
let
content = headerWithProof.get()
contentKey = ContentKey(
contentType: blockHeader,
blockHeaderKey: BlockKey(blockHash: header.blockHash()))
encodedContentKey = history_content.encode(contentKey)
encodedContent = SSZ.encode(content)
let portalContent = JsonPortalContent(
content_key: encodedContentKey.asSeq().to0xHex(),
content_value: encodedContent.to0xHex())
contentTable[blockNumber] = portalContent
else:
# TODO: Deal with writing post merge headers
error "Not a pre merge header"
quit 1
writePortalContentToJson(fh, contentTable)
try:
fh.close()
except IOError as e:
fatal "Error occured while closing file", error = e.msg
quit 1