2018-11-26 13:33:06 +00:00
|
|
|
import
|
2020-10-14 14:04:08 +00:00
|
|
|
std/[deques, tables, hashes, options, strformat, strutils],
|
2020-07-02 15:14:11 +00:00
|
|
|
chronos, web3, web3/ethtypes as web3Types, json, chronicles,
|
|
|
|
eth/common/eth_types, eth/async_utils,
|
2020-10-15 11:49:02 +00:00
|
|
|
spec/[datatypes, digest, crypto, beaconstate, helpers],
|
2020-10-12 01:07:20 +00:00
|
|
|
ssz, beacon_chain_db, network_metadata, merkle_minimal, beacon_node_status
|
2018-11-26 13:33:06 +00:00
|
|
|
|
2020-06-27 12:01:19 +00:00
|
|
|
from times import epochTime
|
|
|
|
|
2020-06-19 17:42:28 +00:00
|
|
|
export
|
2020-07-02 15:14:11 +00:00
|
|
|
web3Types
|
2020-06-19 17:42:28 +00:00
|
|
|
|
2020-03-24 11:13:07 +00:00
|
|
|
contract(DepositContract):
|
|
|
|
proc deposit(pubkey: Bytes48,
|
|
|
|
withdrawalCredentials: Bytes32,
|
|
|
|
signature: Bytes96,
|
|
|
|
deposit_data_root: FixedBytes[32])
|
|
|
|
|
|
|
|
proc get_deposit_root(): FixedBytes[32]
|
|
|
|
proc get_deposit_count(): Bytes8
|
|
|
|
|
|
|
|
proc DepositEvent(pubkey: Bytes48,
|
|
|
|
withdrawalCredentials: Bytes32,
|
|
|
|
amount: Bytes8,
|
|
|
|
signature: Bytes96,
|
|
|
|
index: Bytes8) {.event.}
|
|
|
|
# TODO
|
|
|
|
# The raises list of this module are still not usable due to general
|
|
|
|
# Exceptions being reported from Chronos's asyncfutures2.
|
|
|
|
|
2018-11-26 13:33:06 +00:00
|
|
|
type
|
2020-03-24 11:13:07 +00:00
|
|
|
Eth1BlockNumber* = uint64
|
|
|
|
Eth1BlockTimestamp* = uint64
|
2020-07-02 15:14:11 +00:00
|
|
|
Eth1BlockHeader = web3Types.BlockHeader
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2020-10-12 01:07:20 +00:00
|
|
|
Database* = object
|
|
|
|
|
2020-03-24 11:13:07 +00:00
|
|
|
Eth1Block* = ref object
|
|
|
|
number*: Eth1BlockNumber
|
|
|
|
timestamp*: Eth1BlockTimestamp
|
|
|
|
deposits*: seq[Deposit]
|
|
|
|
voteData*: Eth1Data
|
2020-10-12 01:07:20 +00:00
|
|
|
knownValidatorsCount*: Option[uint64]
|
2020-06-25 23:33:06 +00:00
|
|
|
|
2020-03-24 11:13:07 +00:00
|
|
|
Eth1Chain* = object
|
2020-06-27 12:01:19 +00:00
|
|
|
knownStart: Eth1Data
|
|
|
|
knownStartBlockNum: Option[Eth1BlockNumber]
|
|
|
|
|
2020-03-24 11:13:07 +00:00
|
|
|
blocks: Deque[Eth1Block]
|
|
|
|
blocksByHash: Table[BlockHash, Eth1Block]
|
|
|
|
|
2019-09-09 15:59:02 +00:00
|
|
|
MainchainMonitor* = ref object
|
2020-10-12 01:07:20 +00:00
|
|
|
db: BeaconChainDB
|
2020-07-07 23:02:14 +00:00
|
|
|
preset: RuntimePreset
|
2020-10-14 14:04:08 +00:00
|
|
|
|
|
|
|
dataProvider: Web3DataProviderRef
|
|
|
|
depositQueue: AsyncQueue[Eth1BlockHeader]
|
|
|
|
eth1Chain: Eth1Chain
|
2018-11-26 13:33:06 +00:00
|
|
|
|
2020-04-23 18:58:54 +00:00
|
|
|
genesisState: NilableBeaconStateRef
|
2019-09-09 15:59:02 +00:00
|
|
|
genesisStateFut: Future[void]
|
2020-06-27 12:01:19 +00:00
|
|
|
genesisMonitoringFut: Future[void]
|
2018-11-26 13:33:06 +00:00
|
|
|
|
2019-11-22 13:16:07 +00:00
|
|
|
runFut: Future[void]
|
|
|
|
|
2020-10-14 14:04:08 +00:00
|
|
|
Web3DataProvider* = object
|
2020-03-24 11:13:07 +00:00
|
|
|
url: string
|
|
|
|
web3: Web3
|
|
|
|
ns: Sender[DepositContract]
|
2020-06-27 12:01:19 +00:00
|
|
|
blockHeadersSubscription: Subscription
|
2020-03-24 11:13:07 +00:00
|
|
|
|
|
|
|
Web3DataProviderRef* = ref Web3DataProvider
|
|
|
|
|
|
|
|
ReorgDepthLimitExceeded = object of CatchableError
|
|
|
|
CorruptDataProvider = object of CatchableError
|
|
|
|
|
|
|
|
DisconnectHandler* = proc () {.gcsafe, raises: [Defect].}
|
|
|
|
|
|
|
|
DepositEventHandler* = proc (
|
|
|
|
pubkey: Bytes48,
|
|
|
|
withdrawalCredentials: Bytes32,
|
|
|
|
amount: Bytes8,
|
2020-05-08 14:24:47 +00:00
|
|
|
signature: Bytes96, merkleTreeIndex: Bytes8, j: JsonNode) {.raises: [Defect], gcsafe.}
|
2020-03-24 11:13:07 +00:00
|
|
|
|
|
|
|
const
|
2020-06-17 16:39:16 +00:00
|
|
|
web3Timeouts = 5.seconds
|
2020-07-07 23:02:14 +00:00
|
|
|
|
2020-10-14 14:04:08 +00:00
|
|
|
template depositContractAddress(m: MainchainMonitor): Eth1Address =
|
|
|
|
m.dataProvider.ns.contractAddress
|
|
|
|
|
|
|
|
template web3Url(m: MainchainMonitor): string =
|
|
|
|
m.dataProvider.url
|
|
|
|
|
2020-07-07 23:02:14 +00:00
|
|
|
# TODO: Add preset validation
|
|
|
|
# MIN_GENESIS_ACTIVE_VALIDATOR_COUNT should be larger than SLOTS_PER_EPOCH
|
2020-08-06 09:08:54 +00:00
|
|
|
# doAssert SECONDS_PER_ETH1_BLOCK * preset.ETH1_FOLLOW_DISTANCE < GENESIS_DELAY,
|
2020-07-07 23:02:14 +00:00
|
|
|
# "Invalid configuration: GENESIS_DELAY is set too low"
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2020-10-12 08:59:24 +00:00
|
|
|
# https://github.com/ethereum/eth2.0-specs/blob/v1.0.0-rc.0/specs/phase0/validator.md#get_eth1_data
|
2020-03-24 11:13:07 +00:00
|
|
|
func compute_time_at_slot(state: BeaconState, slot: Slot): uint64 =
|
2020-06-15 09:38:05 +00:00
|
|
|
state.genesis_time + slot * SECONDS_PER_SLOT
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2020-10-12 08:59:24 +00:00
|
|
|
# https://github.com/ethereum/eth2.0-specs/blob/v1.0.0-rc.0/specs/phase0/validator.md#get_eth1_data
|
2020-03-24 11:13:07 +00:00
|
|
|
func voting_period_start_time*(state: BeaconState): uint64 =
|
2020-06-15 09:38:05 +00:00
|
|
|
let eth1_voting_period_start_slot =
|
|
|
|
state.slot - state.slot mod SLOTS_PER_ETH1_VOTING_PERIOD.uint64
|
|
|
|
compute_time_at_slot(state, eth1_voting_period_start_slot)
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2020-10-12 08:59:24 +00:00
|
|
|
# https://github.com/ethereum/eth2.0-specs/blob/v1.0.0-rc.0/specs/phase0/validator.md#get_eth1_data
|
2020-08-06 09:08:54 +00:00
|
|
|
func is_candidate_block(preset: RuntimePreset, blk: Eth1Block, period_start: uint64): bool =
|
|
|
|
(blk.timestamp + SECONDS_PER_ETH1_BLOCK.uint64 * preset.ETH1_FOLLOW_DISTANCE <= period_start) and
|
|
|
|
(blk.timestamp + SECONDS_PER_ETH1_BLOCK.uint64 * preset.ETH1_FOLLOW_DISTANCE * 2 >= period_start)
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2020-06-19 17:42:28 +00:00
|
|
|
func asEth2Digest*(x: BlockHash): Eth2Digest =
|
2020-03-24 11:13:07 +00:00
|
|
|
Eth2Digest(data: array[32, byte](x))
|
|
|
|
|
|
|
|
template asBlockHash(x: Eth2Digest): BlockHash =
|
|
|
|
BlockHash(x.data)
|
|
|
|
|
2020-09-06 08:39:25 +00:00
|
|
|
func shortLog*(b: Eth1Block): string =
|
2020-06-27 12:01:19 +00:00
|
|
|
&"{b.number}:{shortLog b.voteData.block_hash}"
|
|
|
|
|
2020-03-24 11:13:07 +00:00
|
|
|
template findBlock*(eth1Chain: Eth1Chain, hash: BlockHash): Eth1Block =
|
|
|
|
eth1Chain.blocksByHash.getOrDefault(hash, nil)
|
|
|
|
|
|
|
|
template findBlock*(eth1Chain: Eth1Chain, eth1Data: Eth1Data): Eth1Block =
|
|
|
|
getOrDefault(eth1Chain.blocksByHash, asBlockHash(eth1Data.block_hash), nil)
|
|
|
|
|
|
|
|
proc findParent*(eth1Chain: Eth1Chain, blk: BlockObject): Eth1Block =
|
|
|
|
result = eth1Chain.findBlock(blk.parentHash)
|
|
|
|
# a distinct type is stipped here:
|
|
|
|
let blockNumber = Eth1BlockNumber(blk.number)
|
|
|
|
if result != nil and result.number != blockNumber - 1:
|
|
|
|
debug "Found inconsistent numbering of Eth1 blocks. Ignoring block.",
|
|
|
|
blockHash = blk.hash.toHex, blockNumber,
|
|
|
|
parentHash = blk.parentHash.toHex, parentNumber = result.number
|
|
|
|
result = nil
|
|
|
|
|
2020-08-06 09:08:54 +00:00
|
|
|
func latestCandidateBlock(eth1Chain: Eth1Chain, preset: RuntimePreset, periodStart: uint64): Eth1Block =
|
2020-03-24 11:13:07 +00:00
|
|
|
for i in countdown(eth1Chain.blocks.len - 1, 0):
|
|
|
|
let blk = eth1Chain.blocks[i]
|
2020-08-06 09:08:54 +00:00
|
|
|
if is_candidate_block(preset, blk, periodStart):
|
2020-03-24 11:13:07 +00:00
|
|
|
return blk
|
|
|
|
|
2020-06-27 12:01:19 +00:00
|
|
|
func popBlock(eth1Chain: var Eth1Chain) =
|
|
|
|
let removed = eth1Chain.blocks.popLast
|
|
|
|
eth1Chain.blocksByHash.del removed.voteData.block_hash.asBlockHash
|
|
|
|
|
2020-03-24 11:13:07 +00:00
|
|
|
func trimHeight(eth1Chain: var Eth1Chain, blockNumber: Eth1BlockNumber) =
|
|
|
|
## Removes all blocks above certain `blockNumber`
|
2020-06-27 12:01:19 +00:00
|
|
|
while eth1Chain.blocks.len > 0:
|
|
|
|
if eth1Chain.blocks.peekLast.number > blockNumber:
|
|
|
|
eth1Chain.popBlock()
|
|
|
|
else:
|
|
|
|
break
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2020-10-26 08:55:10 +00:00
|
|
|
func addBlock*(eth1Chain: var Eth1Chain, newBlock: Eth1Block) =
|
|
|
|
eth1Chain.blocks.addLast newBlock
|
|
|
|
eth1Chain.blocksByHash[newBlock.voteData.block_hash.asBlockHash] = newBlock
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2020-10-12 01:07:20 +00:00
|
|
|
proc allDepositsUpTo*(m: MainchainMonitor, totalDeposits: uint64): seq[Deposit] =
|
|
|
|
for i in 0'u64 ..< totalDeposits:
|
|
|
|
result.add Deposit(data: m.db.deposits.get(i))
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2020-06-19 17:42:28 +00:00
|
|
|
func clear*(eth1Chain: var Eth1Chain) =
|
|
|
|
eth1Chain = default(Eth1Chain)
|
|
|
|
|
2020-03-24 11:13:07 +00:00
|
|
|
template hash*(x: Eth1Block): Hash =
|
|
|
|
hash(x.voteData.block_hash.data)
|
|
|
|
|
2020-10-14 14:04:08 +00:00
|
|
|
proc close*(p: Web3DataProviderRef): Future[void] {.async, locks: 0.} =
|
2020-06-27 12:01:19 +00:00
|
|
|
if p.blockHeadersSubscription != nil:
|
|
|
|
await p.blockHeadersSubscription.unsubscribe()
|
|
|
|
|
|
|
|
await p.web3.close()
|
|
|
|
|
2020-10-14 14:04:08 +00:00
|
|
|
proc getBlockByHash*(p: Web3DataProviderRef, hash: BlockHash): Future[BlockObject] =
|
2020-06-27 12:01:19 +00:00
|
|
|
return p.web3.provider.eth_getBlockByHash(hash, false)
|
|
|
|
|
2020-10-14 14:04:08 +00:00
|
|
|
proc getBlockByNumber*(p: Web3DataProviderRef, number: Eth1BlockNumber): Future[BlockObject] =
|
2020-06-27 12:01:19 +00:00
|
|
|
return p.web3.provider.eth_getBlockByNumber(&"0x{number:X}", false)
|
|
|
|
|
2020-10-14 14:04:08 +00:00
|
|
|
proc getBlockNumber(p: Web3DataProviderRef, hash: BlockHash): Future[Eth1BlockNumber] {.async.} =
|
2020-06-27 12:01:19 +00:00
|
|
|
try:
|
|
|
|
let blk = awaitWithTimeout(p.getBlockByHash(hash), web3Timeouts):
|
|
|
|
return 0
|
|
|
|
return Eth1BlockNumber(blk.number)
|
|
|
|
except CatchableError as exc:
|
2020-10-01 18:56:42 +00:00
|
|
|
debug "Failed to get Eth1 block number from hash",
|
2020-10-12 01:07:20 +00:00
|
|
|
hash = $hash, err = exc.msg
|
2020-08-06 18:47:39 +00:00
|
|
|
raise exc
|
2020-06-27 12:01:19 +00:00
|
|
|
|
|
|
|
template readJsonField(j: JsonNode,
|
|
|
|
fieldName: string,
|
|
|
|
ValueType: type): untyped =
|
|
|
|
var res: ValueType
|
|
|
|
fromJson(j[fieldName], fieldName, res)
|
|
|
|
res
|
|
|
|
|
|
|
|
proc readJsonDeposits(depositsList: JsonNode): seq[Eth1Block] =
|
|
|
|
if depositsList.kind != JArray:
|
|
|
|
raise newException(CatchableError,
|
|
|
|
"Web3 provider didn't return a list of deposit events")
|
|
|
|
|
|
|
|
var lastEth1Block: Eth1Block
|
|
|
|
|
|
|
|
for logEvent in depositsList:
|
|
|
|
let
|
|
|
|
blockNumber = Eth1BlockNumber readJsonField(logEvent, "blockNumber", Quantity)
|
|
|
|
blockHash = readJsonField(logEvent, "blockHash", BlockHash)
|
|
|
|
logData = strip0xPrefix(logEvent["data"].getStr)
|
|
|
|
|
|
|
|
if lastEth1Block == nil or lastEth1Block.number != blockNumber:
|
|
|
|
lastEth1Block = Eth1Block(
|
|
|
|
number: blockNumber,
|
|
|
|
voteData: Eth1Data(block_hash: blockHash.asEth2Digest))
|
|
|
|
|
|
|
|
result.add lastEth1Block
|
|
|
|
|
|
|
|
var
|
|
|
|
pubkey: Bytes48
|
|
|
|
withdrawalCredentials: Bytes32
|
|
|
|
amount: Bytes8
|
|
|
|
signature: Bytes96
|
|
|
|
index: Bytes8
|
|
|
|
|
|
|
|
var offset = 0
|
|
|
|
offset += decode(logData, offset, pubkey)
|
|
|
|
offset += decode(logData, offset, withdrawalCredentials)
|
|
|
|
offset += decode(logData, offset, amount)
|
|
|
|
offset += decode(logData, offset, signature)
|
|
|
|
offset += decode(logData, offset, index)
|
|
|
|
|
|
|
|
lastEth1Block.deposits.add Deposit(
|
|
|
|
data: DepositData(
|
|
|
|
pubkey: ValidatorPubKey.init(array[48, byte](pubkey)),
|
|
|
|
withdrawal_credentials: Eth2Digest(data: array[32, byte](withdrawalCredentials)),
|
2020-07-27 10:59:57 +00:00
|
|
|
amount: bytes_to_uint64(array[8, byte](amount)),
|
2020-06-27 12:01:19 +00:00
|
|
|
signature: ValidatorSig.init(array[96, byte](signature))))
|
|
|
|
|
2020-10-14 14:04:08 +00:00
|
|
|
proc fetchDepositData*(p: Web3DataProviderRef,
|
|
|
|
fromBlock, toBlock: Eth1BlockNumber): Future[seq[Eth1Block]]
|
2020-10-26 08:55:10 +00:00
|
|
|
{.async, locks: 0.} =
|
2020-10-21 13:25:53 +00:00
|
|
|
var currentBlock = fromBlock
|
|
|
|
while currentBlock <= toBlock:
|
|
|
|
var blocksPerRequest = 128'u64
|
|
|
|
while true:
|
|
|
|
let requestToBlock = min(toBlock, currentBlock + blocksPerRequest - 1)
|
|
|
|
|
|
|
|
debug "Obtaining deposit log events",
|
|
|
|
fromBlock = currentBlock,
|
|
|
|
toBlock = requestToBlock
|
|
|
|
|
|
|
|
let depositLogs = try:
|
|
|
|
await p.ns.getJsonLogs(
|
|
|
|
DepositEvent,
|
|
|
|
fromBlock = some blockId(currentBlock),
|
|
|
|
toBlock = some blockId(requestToBlock))
|
|
|
|
except CatchableError as err:
|
|
|
|
blocksPerRequest = blocksPerRequest div 2
|
|
|
|
if blocksPerRequest > 0:
|
|
|
|
continue
|
|
|
|
raise err
|
|
|
|
|
|
|
|
currentBlock = requestToBlock + 1
|
|
|
|
result.add readJsonDeposits(depositLogs)
|
|
|
|
break # breaks the inner "retry" loop and continues
|
|
|
|
# to the next range of blocks to request
|
2020-06-27 12:01:19 +00:00
|
|
|
|
2020-10-14 14:04:08 +00:00
|
|
|
proc onBlockHeaders*(p: Web3DataProviderRef,
|
|
|
|
blockHeaderHandler: BlockHeaderHandler,
|
2020-10-26 08:55:10 +00:00
|
|
|
errorHandler: SubscriptionErrorHandler): Future[void]
|
|
|
|
{.async, gcsafe.} =
|
2020-06-27 12:01:19 +00:00
|
|
|
if p.blockHeadersSubscription != nil:
|
|
|
|
await p.blockHeadersSubscription.unsubscribe()
|
|
|
|
|
2020-10-12 01:07:20 +00:00
|
|
|
info "Waiting for new Eth1 block headers"
|
2020-06-27 12:01:19 +00:00
|
|
|
|
|
|
|
p.blockHeadersSubscription = await p.web3.subscribeForBlockHeaders(
|
2020-09-24 13:07:48 +00:00
|
|
|
blockHeaderHandler, errorHandler)
|
2020-06-27 12:01:19 +00:00
|
|
|
|
2020-10-15 17:30:33 +00:00
|
|
|
# https://github.com/ethereum/eth2.0-specs/blob/v1.0.0-rc.0/specs/phase0/validator.md#get_eth1_data
|
2020-10-12 01:07:20 +00:00
|
|
|
proc getBlockProposalData*(m: MainchainMonitor,
|
2020-10-26 08:55:10 +00:00
|
|
|
state: BeaconState,
|
|
|
|
finalizedEth1Data: Eth1Data): (Eth1Data, seq[Deposit]) =
|
|
|
|
# TODO To make block proposal cheaper, we can perform this action more regularly
|
|
|
|
# (e.g. in BeaconNode.onSlot). But keep in mind that this action needs to be
|
|
|
|
# performed only when there are validators attached to the node.
|
|
|
|
m.db.finalizedEth2DepositsMerkleizer.advanceTo(
|
|
|
|
m.db, finalizedEth1Data.deposit_count)
|
|
|
|
|
|
|
|
doAssert(m.db.finalizedEth2DepositsMerkleizer.getFinalHash ==
|
|
|
|
finalizedEth1Data.deposit_root)
|
|
|
|
|
2020-03-24 11:13:07 +00:00
|
|
|
let periodStart = voting_period_start_time(state)
|
|
|
|
|
|
|
|
var otherVotesCountTable = initCountTable[Eth1Block]()
|
|
|
|
for vote in state.eth1_data_votes:
|
2020-10-12 01:07:20 +00:00
|
|
|
let eth1Block = m.eth1Chain.findBlock(vote)
|
2020-10-15 17:30:33 +00:00
|
|
|
if eth1Block != nil and
|
|
|
|
is_candidate_block(m.preset, eth1Block, periodStart) and
|
|
|
|
eth1Block.voteData.deposit_count > state.eth1_data.deposit_count:
|
2020-03-24 11:13:07 +00:00
|
|
|
otherVotesCountTable.inc eth1Block
|
|
|
|
|
2020-10-15 17:30:33 +00:00
|
|
|
var pendingDeposits = state.eth1_data.deposit_count - state.eth1_deposit_index
|
2020-03-24 11:13:07 +00:00
|
|
|
if otherVotesCountTable.len > 0:
|
2020-10-12 01:07:20 +00:00
|
|
|
let (winningBlock, votes) = otherVotesCountTable.largest
|
|
|
|
result[0] = winningBlock.voteData
|
|
|
|
if uint64((votes + 1) * 2) > SLOTS_PER_ETH1_VOTING_PERIOD:
|
2020-10-15 17:30:33 +00:00
|
|
|
pendingDeposits = winningBlock.voteData.deposit_count -
|
|
|
|
state.eth1_deposit_index
|
2020-03-24 11:13:07 +00:00
|
|
|
else:
|
2020-10-12 01:07:20 +00:00
|
|
|
let latestBlock = m.eth1Chain.latestCandidateBlock(m.preset, periodStart)
|
|
|
|
if latestBlock == nil:
|
2020-10-15 17:30:33 +00:00
|
|
|
result[0] = state.eth1_data
|
2020-10-12 01:07:20 +00:00
|
|
|
else:
|
|
|
|
result[0] = latestBlock.voteData
|
2020-10-15 17:30:33 +00:00
|
|
|
|
|
|
|
if pendingDeposits > 0:
|
2020-10-26 08:55:10 +00:00
|
|
|
let totalDepositsInNewBlock = min(MAX_DEPOSITS, pendingDeposits)
|
2020-10-15 17:30:33 +00:00
|
|
|
|
2020-10-26 08:55:10 +00:00
|
|
|
var
|
|
|
|
deposits = newSeq[Deposit](pendingDeposits)
|
|
|
|
depositRoots = newSeq[Eth2Digest](pendingDeposits)
|
|
|
|
depositsMerkleizerClone = clone m.db.finalizedEth2DepositsMerkleizer
|
2020-10-15 17:30:33 +00:00
|
|
|
|
2020-10-26 08:55:10 +00:00
|
|
|
depositsMerkleizerClone.advanceTo(m.db, state.eth1_deposit_index)
|
|
|
|
|
|
|
|
for i in 0 ..< totalDepositsInNewBlock:
|
|
|
|
deposits[i].data = m.db.deposits.get(state.eth1_deposit_index + i)
|
|
|
|
depositRoots[i] = hash_tree_root(deposits[i].data)
|
|
|
|
|
|
|
|
let proofs = depositsMerkleizerClone.addChunksAndGenMerkleProofs(depositRoots)
|
2020-10-15 17:30:33 +00:00
|
|
|
|
2020-10-26 08:55:10 +00:00
|
|
|
for i in 0 ..< totalDepositsInNewBlock:
|
|
|
|
deposits[i].proof[0..31] = proofs.getProof(i.int)
|
|
|
|
deposits[i].proof[32].data[0..7] =
|
|
|
|
toBytesLE uint64(state.eth1_deposit_index + i + 1)
|
|
|
|
|
|
|
|
swap(result[1], deposits)
|
2020-03-24 11:13:07 +00:00
|
|
|
|
|
|
|
proc init*(T: type MainchainMonitor,
|
2020-10-12 01:07:20 +00:00
|
|
|
db: BeaconChainDB,
|
2020-07-07 23:02:14 +00:00
|
|
|
preset: RuntimePreset,
|
2020-10-14 14:04:08 +00:00
|
|
|
web3Url: string,
|
2020-07-02 15:14:11 +00:00
|
|
|
depositContractAddress: Eth1Address,
|
2020-10-14 14:04:08 +00:00
|
|
|
depositContractDeployedAt: string): Future[Result[T, string]] {.async.} =
|
|
|
|
let web3 = try: await newWeb3(web3Url)
|
|
|
|
except CatchableError as err:
|
|
|
|
return err "Failed to setup web3 connection"
|
|
|
|
let
|
|
|
|
ns = web3.contractSender(DepositContract, depositContractAddress)
|
|
|
|
dataProvider = Web3DataProviderRef(url: web3Url, web3: web3, ns: ns)
|
|
|
|
|
|
|
|
let
|
|
|
|
previouslyPersistedTo = db.getEth1PersistedTo()
|
|
|
|
knownStart = previouslyPersistedTo.get:
|
|
|
|
# `previouslyPersistedTo` wall null, we start from scratch
|
|
|
|
let deployedAtHash = if depositContractDeployedAt.startsWith "0x":
|
|
|
|
try: BlockHash.fromHex depositContractDeployedAt
|
|
|
|
except ValueError:
|
|
|
|
return err "Invalid hex value specified for deposit-contract-block"
|
|
|
|
else:
|
|
|
|
let blockNum = try: parseBiggestUInt depositContractDeployedAt
|
|
|
|
except ValueError:
|
|
|
|
return err "Invalid nummeric value for deposit-contract-block"
|
|
|
|
try:
|
|
|
|
let blk = await dataProvider.getBlockByNumber(blockNum)
|
|
|
|
blk.hash
|
|
|
|
except CatchableError:
|
|
|
|
return err("Failed to obtain block hash for block number " & $blockNum)
|
|
|
|
Eth1Data(block_hash: deployedAtHash.asEth2Digest, deposit_count: 0)
|
|
|
|
|
|
|
|
return ok T(
|
|
|
|
db: db,
|
2020-10-12 01:07:20 +00:00
|
|
|
preset: preset,
|
2020-10-14 14:04:08 +00:00
|
|
|
dataProvider: dataProvider,
|
2020-07-07 23:02:14 +00:00
|
|
|
depositQueue: newAsyncQueue[Eth1BlockHeader](),
|
2020-10-14 14:04:08 +00:00
|
|
|
eth1Chain: Eth1Chain(knownStart: knownStart))
|
2019-09-09 15:59:02 +00:00
|
|
|
|
2020-10-26 08:55:10 +00:00
|
|
|
proc fetchBlockDetails(p: Web3DataProviderRef, blk: Eth1Block) {.async.} =
|
|
|
|
let
|
|
|
|
web3Block = p.getBlockByNumber(blk.number)
|
|
|
|
depositRoot = p.ns.get_deposit_root.call(blockNumber = blk.number)
|
|
|
|
rawCount = p.ns.get_deposit_count.call(blockNumber = blk.number)
|
|
|
|
|
|
|
|
var error: ref CatchableError = nil
|
|
|
|
|
|
|
|
try: blk.voteData.deposit_root = asEth2Digest(await depositRoot)
|
|
|
|
except CatchableError as err: error = err
|
|
|
|
|
|
|
|
try: blk.voteData.deposit_count = bytes_to_uint64(array[8, byte](await rawCount))
|
|
|
|
except CatchableError as err: error = err
|
|
|
|
|
|
|
|
if error != nil:
|
|
|
|
debug "Deposit contract data not available",
|
|
|
|
blockNumber = blk.number,
|
|
|
|
err = error.msg
|
|
|
|
|
|
|
|
blk.timestamp = Eth1BlockTimestamp (await web3Block).timestamp
|
|
|
|
|
|
|
|
proc persistFinalizedBlocks(m: MainchainMonitor, timeNow: float):
|
|
|
|
Future[tuple[genesisBlock: Eth1Block, previousBlock: Eth1Block]] {.async.} =
|
|
|
|
|
2020-10-12 01:07:20 +00:00
|
|
|
let followDistanceInSeconds = uint64(SECONDS_PER_ETH1_BLOCK) *
|
|
|
|
m.preset.ETH1_FOLLOW_DISTANCE
|
|
|
|
var prevBlock: Eth1Block
|
|
|
|
|
|
|
|
# TODO: The DB operations should be executed as a transaction here
|
|
|
|
block: # TODO Begin Transaction
|
2020-10-14 14:04:08 +00:00
|
|
|
while m.eth1Chain.blocks.len > 0:
|
2020-10-26 08:55:10 +00:00
|
|
|
# TODO keep a separate set of candidate blocks
|
|
|
|
var blk = m.eth1Chain.blocks.peekFirst
|
|
|
|
if blk.timestamp != 0:
|
|
|
|
await fetchBlockDetails(m.dataProvider, blk)
|
|
|
|
|
2020-10-12 01:07:20 +00:00
|
|
|
if float(blk.timestamp + followDistanceInSeconds) > timeNow:
|
|
|
|
break
|
|
|
|
|
|
|
|
for deposit in blk.deposits:
|
2020-10-15 11:49:02 +00:00
|
|
|
m.db.processDeposit(deposit.data)
|
2020-10-12 01:07:20 +00:00
|
|
|
|
2020-10-26 08:55:10 +00:00
|
|
|
# TODO compare our results against the web3 provider
|
|
|
|
blk.voteData.deposit_count = m.db.finalizedEth1DepositsMerkleizer.totalChunks
|
|
|
|
blk.voteData.deposit_root = m.db.finalizedEth1DepositsMerkleizer.getFinalHash
|
|
|
|
|
|
|
|
# TODO
|
|
|
|
# Try to confirm history by obtaining deposit_root from a more
|
|
|
|
# recent block
|
|
|
|
|
2020-10-14 14:04:08 +00:00
|
|
|
# TODO The len property is currently stored in memory which
|
|
|
|
# makes it unsafe in the face of failed transactions
|
2020-10-15 11:49:02 +00:00
|
|
|
let activeValidatorsCount = m.db.immutableValidatorData.lenu64
|
|
|
|
blk.knownValidatorsCount = some activeValidatorsCount
|
2020-10-12 01:07:20 +00:00
|
|
|
|
|
|
|
discard m.eth1Chain.blocks.popFirst()
|
|
|
|
m.eth1Chain.blocksByHash.del blk.voteData.block_hash.asBlockHash
|
|
|
|
|
|
|
|
let blockGenesisTime = genesis_time_from_eth1_timestamp(m.preset,
|
|
|
|
blk.timestamp)
|
|
|
|
if blockGenesisTime >= m.preset.MIN_GENESIS_TIME and
|
2020-10-15 11:49:02 +00:00
|
|
|
activeValidatorsCount >= m.preset.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT:
|
2020-10-12 01:07:20 +00:00
|
|
|
result = (blk, prevBlock)
|
|
|
|
|
|
|
|
prevBlock = blk
|
|
|
|
|
|
|
|
if prevBlock != nil:
|
|
|
|
# TODO commit transaction
|
2020-10-14 14:04:08 +00:00
|
|
|
m.db.putEth1PersistedTo prevBlock.voteData
|
|
|
|
m.eth1Chain.knownStart = prevBlock.voteData
|
|
|
|
notice "Eth1 sync progress",
|
|
|
|
blockNumber = prevBlock.number,
|
|
|
|
depositsProcessed = prevBlock.voteData.deposit_count
|
2020-10-12 01:07:20 +00:00
|
|
|
|
|
|
|
# TODO Commit
|
|
|
|
|
|
|
|
proc createGenesisState(m: MainchainMonitor, eth1Block: Eth1Block): BeaconStateRef =
|
|
|
|
notice "Generating genesis state",
|
|
|
|
blockNum = eth1Block.number,
|
|
|
|
blockHash = eth1Block.voteData.block_hash,
|
|
|
|
totalDeposits = eth1Block.voteData.deposit_count,
|
|
|
|
activeValidators = eth1Block.knownValidatorsCount.get
|
|
|
|
|
|
|
|
var deposits = m.allDepositsUpTo(eth1Block.voteData.deposit_count)
|
2020-06-27 12:01:19 +00:00
|
|
|
attachMerkleProofs deposits
|
2020-10-12 01:07:20 +00:00
|
|
|
|
|
|
|
initialize_beacon_state_from_eth1(m.preset,
|
|
|
|
eth1Block.voteData.block_hash,
|
|
|
|
eth1Block.timestamp.uint64,
|
|
|
|
deposits, {})
|
2020-06-28 20:17:47 +00:00
|
|
|
|
|
|
|
proc signalGenesis(m: MainchainMonitor, genesisState: BeaconStateRef) =
|
|
|
|
m.genesisState = genesisState
|
|
|
|
|
|
|
|
if not m.genesisStateFut.isNil:
|
|
|
|
m.genesisStateFut.complete()
|
|
|
|
m.genesisStateFut = nil
|
2020-06-27 12:01:19 +00:00
|
|
|
|
2020-06-28 20:17:47 +00:00
|
|
|
proc findGenesisBlockInRange(m: MainchainMonitor,
|
|
|
|
startBlock, endBlock: Eth1Block): Future[Eth1Block]
|
|
|
|
{.async.} =
|
|
|
|
var
|
|
|
|
startBlock = startBlock
|
|
|
|
endBlock = endBlock
|
|
|
|
depositData = startBlock.voteData
|
|
|
|
|
|
|
|
while startBlock.number + 1 < endBlock.number:
|
|
|
|
let
|
2020-07-07 23:02:14 +00:00
|
|
|
MIN_GENESIS_TIME = m.preset.MIN_GENESIS_TIME
|
|
|
|
startBlockTime = genesis_time_from_eth1_timestamp(m.preset, startBlock.timestamp)
|
2020-06-28 20:17:47 +00:00
|
|
|
secondsPerBlock = float(endBlock.timestamp - startBlock.timestamp) /
|
|
|
|
float(endBlock.number - startBlock.number)
|
|
|
|
blocksToJump = max(float(MIN_GENESIS_TIME - startBlockTime) / secondsPerBlock, 1.0)
|
2020-08-02 21:19:25 +00:00
|
|
|
candidateNumber = min(endBlock.number - 1, startBlock.number + 1) # blocksToJump.uint64)
|
2020-10-14 14:04:08 +00:00
|
|
|
candidateBlock = await m.dataProvider.getBlockByNumber(candidateNumber)
|
2020-06-28 20:17:47 +00:00
|
|
|
|
|
|
|
var candidateAsEth1Block = Eth1Block(number: candidateBlock.number.uint64,
|
|
|
|
timestamp: candidateBlock.timestamp.uint64,
|
|
|
|
voteData: depositData)
|
|
|
|
candidateAsEth1Block.voteData.block_hash = candidateBlock.hash.asEth2Digest
|
|
|
|
|
2020-07-07 23:02:14 +00:00
|
|
|
let candidateGenesisTime = genesis_time_from_eth1_timestamp(
|
|
|
|
m.preset, candidateBlock.timestamp.uint64)
|
|
|
|
|
2020-10-01 18:56:42 +00:00
|
|
|
notice "Probing possible genesis block",
|
2020-06-28 20:17:47 +00:00
|
|
|
`block` = candidateBlock.number.uint64,
|
2020-07-07 23:02:14 +00:00
|
|
|
candidateGenesisTime
|
2020-06-28 20:17:47 +00:00
|
|
|
|
2020-07-07 23:02:14 +00:00
|
|
|
if candidateGenesisTime < MIN_GENESIS_TIME:
|
2020-06-28 20:17:47 +00:00
|
|
|
startBlock = candidateAsEth1Block
|
|
|
|
else:
|
|
|
|
endBlock = candidateAsEth1Block
|
|
|
|
|
|
|
|
return endBlock
|
2020-06-27 12:01:19 +00:00
|
|
|
|
2020-09-28 15:19:57 +00:00
|
|
|
proc safeCancel(fut: var Future[void]) =
|
|
|
|
if not fut.isNil and not fut.finished:
|
|
|
|
fut.cancel()
|
|
|
|
fut = nil
|
|
|
|
|
|
|
|
proc stop*(m: MainchainMonitor) =
|
|
|
|
safeCancel m.runFut
|
|
|
|
safeCancel m.genesisMonitoringFut
|
|
|
|
|
|
|
|
template checkIfShouldStopMainchainMonitor(m: MainchainMonitor) =
|
|
|
|
if bnStatus == BeaconNodeStatus.Stopping:
|
|
|
|
if not m.genesisStateFut.isNil:
|
|
|
|
m.genesisStateFut.complete()
|
|
|
|
m.genesisStateFut = nil
|
|
|
|
m.stop
|
|
|
|
return
|
|
|
|
|
2020-06-27 12:01:19 +00:00
|
|
|
proc checkForGenesisLoop(m: MainchainMonitor) {.async.} =
|
2019-09-09 15:59:02 +00:00
|
|
|
while true:
|
2020-09-28 15:19:57 +00:00
|
|
|
m.checkIfShouldStopMainchainMonitor()
|
|
|
|
|
2020-06-27 12:01:19 +00:00
|
|
|
if not m.genesisState.isNil:
|
|
|
|
return
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2020-06-27 12:01:19 +00:00
|
|
|
try:
|
2020-10-12 01:07:20 +00:00
|
|
|
# TODO: check for a stale monitor
|
|
|
|
let
|
|
|
|
now = epochTime()
|
2020-10-26 08:55:10 +00:00
|
|
|
(genesisCandidate, genesisParent) = await m.persistFinalizedBlocks(now)
|
2020-10-12 01:07:20 +00:00
|
|
|
|
|
|
|
if genesisCandidate != nil:
|
|
|
|
# We have a candidate state on our hands, but our current Eth1Chain
|
|
|
|
# may consist only of blocks that have deposits attached to them
|
|
|
|
# while the real genesis may have happened in a block without any
|
|
|
|
# deposits (triggered by MIN_GENESIS_TIME).
|
|
|
|
#
|
|
|
|
# This can happen when the beacon node is launched after the genesis
|
|
|
|
# event. We take a short cut when constructing the initial Eth1Chain
|
|
|
|
# by downloading only deposit log entries. Thus, we'll see all the
|
|
|
|
# blocks with deposits, but not the regular blocks in between.
|
|
|
|
#
|
|
|
|
# We'll handle this special case below by examing whether we are in
|
|
|
|
# this potential scenario and we'll use a fast guessing algorith to
|
|
|
|
# discover the ETh1 block with minimal valid genesis time.
|
|
|
|
if genesisParent != nil:
|
|
|
|
if genesisParent.knownValidatorsCount.get >= m.preset.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT and
|
|
|
|
genesisParent.number - genesisParent.number > 1:
|
|
|
|
let genesisBlock = await m.findGenesisBlockInRange(genesisParent, genesisCandidate)
|
|
|
|
if genesisBlock.number != genesisCandidate.number:
|
|
|
|
m.signalGenesis m.createGenesisState(genesisBlock)
|
|
|
|
return
|
|
|
|
|
|
|
|
let candidateState = m.createGenesisState(genesisCandidate)
|
|
|
|
m.signalGenesis candidateState
|
|
|
|
return
|
|
|
|
|
2020-06-27 12:01:19 +00:00
|
|
|
except CatchableError as err:
|
|
|
|
debug "Unexpected error in checkForGenesisLoop", err = err.msg
|
2019-09-09 15:59:02 +00:00
|
|
|
|
2020-06-27 12:01:19 +00:00
|
|
|
await sleepAsync(1.seconds)
|
2019-09-09 15:59:02 +00:00
|
|
|
|
2020-06-28 20:17:47 +00:00
|
|
|
proc waitGenesis*(m: MainchainMonitor): Future[BeaconStateRef] {.async.} =
|
2019-09-09 15:59:02 +00:00
|
|
|
if m.genesisState.isNil:
|
|
|
|
if m.genesisStateFut.isNil:
|
2020-06-28 20:17:47 +00:00
|
|
|
m.genesisStateFut = newFuture[void]("waitGenesis")
|
2020-06-27 12:01:19 +00:00
|
|
|
|
|
|
|
m.genesisMonitoringFut = m.checkForGenesisLoop()
|
2019-09-09 15:59:02 +00:00
|
|
|
await m.genesisStateFut
|
|
|
|
m.genesisStateFut = nil
|
|
|
|
|
2020-09-28 15:19:57 +00:00
|
|
|
if bnStatus == BeaconNodeStatus.Stopping:
|
|
|
|
return new BeaconStateRef # cannot return nil...
|
|
|
|
|
2020-04-23 18:58:54 +00:00
|
|
|
if m.genesisState != nil:
|
2020-04-22 23:35:55 +00:00
|
|
|
return m.genesisState
|
2020-04-23 18:58:54 +00:00
|
|
|
else:
|
2020-05-19 18:57:35 +00:00
|
|
|
result = new BeaconStateRef # make the compiler happy
|
2020-04-23 18:58:54 +00:00
|
|
|
raiseAssert "Unreachable code"
|
2019-09-09 15:59:02 +00:00
|
|
|
|
2020-06-27 12:01:19 +00:00
|
|
|
func totalNonFinalizedBlocks(eth1Chain: Eth1Chain): Natural =
|
|
|
|
# TODO: implement this precisely
|
|
|
|
eth1Chain.blocks.len
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2020-06-27 12:01:19 +00:00
|
|
|
func latestEth1Data(eth1Chain: Eth1Chain): Eth1Data =
|
|
|
|
if eth1Chain.blocks.len > 0:
|
|
|
|
eth1Chain.blocks[^1].voteData
|
|
|
|
else:
|
|
|
|
eth1Chain.knownStart
|
2020-06-25 23:33:06 +00:00
|
|
|
|
2020-06-27 12:01:19 +00:00
|
|
|
func knownInvalidDepositsCount(eth1Chain: Eth1Chain): uint64 =
|
|
|
|
for i in countdown(eth1Chain.blocks.len - 1, 0):
|
|
|
|
let blk = eth1Chain.blocks[i]
|
2020-10-12 01:07:20 +00:00
|
|
|
if blk.knownValidatorsCount.isSome:
|
|
|
|
return blk.voteData.deposit_count - blk.knownValidatorsCount.get
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2020-06-27 12:01:19 +00:00
|
|
|
return 0
|
2020-02-07 07:13:38 +00:00
|
|
|
|
2020-06-27 12:01:19 +00:00
|
|
|
func maxValidDeposits(eth1Chain: Eth1Chain): uint64 =
|
|
|
|
if eth1Chain.blocks.len > 0:
|
|
|
|
let lastBlock = eth1Chain.blocks[^1]
|
2020-10-12 01:07:20 +00:00
|
|
|
lastBlock.knownValidatorsCount.get(
|
2020-06-27 12:01:19 +00:00
|
|
|
lastBlock.voteData.deposit_count - eth1Chain.knownInvalidDepositsCount)
|
|
|
|
else:
|
|
|
|
0
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2020-06-27 12:01:19 +00:00
|
|
|
proc processDeposits(m: MainchainMonitor,
|
2020-10-14 14:04:08 +00:00
|
|
|
dataProvider: Web3DataProviderRef) {.async.} =
|
2020-06-27 12:01:19 +00:00
|
|
|
# ATTENTION!
|
|
|
|
# Please note that this code is using a queue to guarantee the
|
|
|
|
# strict serial order of processing of deposits. If we had the
|
|
|
|
# same code embedded in the deposit contracts events handler,
|
2020-10-12 01:07:20 +00:00
|
|
|
# it could easily re-order the steps due to the interruptible
|
2020-06-27 12:01:19 +00:00
|
|
|
# interleaved execution of async code.
|
|
|
|
while true:
|
2020-09-28 15:19:57 +00:00
|
|
|
m.checkIfShouldStopMainchainMonitor()
|
|
|
|
|
2020-10-14 14:04:08 +00:00
|
|
|
let now = epochTime()
|
2020-10-26 08:55:10 +00:00
|
|
|
discard await m.persistFinalizedBlocks(now)
|
2020-10-14 14:04:08 +00:00
|
|
|
|
2020-06-27 12:01:19 +00:00
|
|
|
let blk = await m.depositQueue.popFirst()
|
|
|
|
m.eth1Chain.trimHeight(Eth1BlockNumber(blk.number) - 1)
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2020-06-27 12:01:19 +00:00
|
|
|
let latestKnownBlock = if m.eth1Chain.blocks.len > 0:
|
|
|
|
m.eth1Chain.blocks[^1].number
|
|
|
|
elif m.eth1Chain.knownStartBlockNum.isSome:
|
|
|
|
m.eth1Chain.knownStartBlockNum.get
|
|
|
|
else:
|
|
|
|
m.eth1Chain.knownStartBlockNum = some(
|
|
|
|
await dataProvider.getBlockNumber(m.eth1Chain.knownStart.block_hash.asBlockHash))
|
|
|
|
m.eth1Chain.knownStartBlockNum.get
|
|
|
|
|
|
|
|
let eth1Blocks = await dataProvider.fetchDepositData(latestKnownBlock + 1,
|
|
|
|
Eth1BlockNumber blk.number)
|
2020-10-26 08:55:10 +00:00
|
|
|
for i in 0 ..< eth1Blocks.len:
|
|
|
|
m.eth1Chain.addBlock eth1Blocks[i]
|
2020-03-24 11:13:07 +00:00
|
|
|
|
2020-06-27 12:01:19 +00:00
|
|
|
proc isRunning*(m: MainchainMonitor): bool =
|
|
|
|
not m.runFut.isNil
|
2019-11-22 13:16:07 +00:00
|
|
|
|
2020-06-24 13:48:57 +00:00
|
|
|
func `===`(json: JsonNode, boolean: bool): bool =
|
|
|
|
json.kind == JBool and json.bval == boolean
|
|
|
|
|
2019-11-22 13:16:07 +00:00
|
|
|
proc run(m: MainchainMonitor, delayBeforeStart: Duration) {.async.} =
|
|
|
|
if delayBeforeStart != ZeroDuration:
|
|
|
|
await sleepAsync(delayBeforeStart)
|
|
|
|
|
2020-10-14 14:04:08 +00:00
|
|
|
info "Starting Eth1 deposit contract monitoring",
|
|
|
|
contract = $m.depositContractAddress, url = m.web3Url
|
2019-11-22 13:16:07 +00:00
|
|
|
|
2020-10-14 14:04:08 +00:00
|
|
|
await m.dataProvider.onBlockHeaders do (blk: Eth1BlockHeader)
|
2020-08-02 21:19:25 +00:00
|
|
|
{.raises: [Defect], gcsafe.}:
|
2020-10-14 14:04:08 +00:00
|
|
|
try:
|
|
|
|
m.depositQueue.addLastNoWait(blk)
|
|
|
|
except AsyncQueueFullError:
|
|
|
|
raiseAssert "The depositQueue has no size limit"
|
|
|
|
except Exception:
|
|
|
|
# TODO Investigate why this exception is being raised
|
|
|
|
raiseAssert "queue.addLastNoWait should not raise exceptions"
|
|
|
|
do (err: CatchableError):
|
|
|
|
debug "Error while processing Eth1 block headers subscription", err = err.msg
|
2020-06-19 17:42:28 +00:00
|
|
|
|
2020-10-14 14:04:08 +00:00
|
|
|
await m.processDeposits(m.dataProvider)
|
2019-09-09 15:59:02 +00:00
|
|
|
|
2019-11-22 13:16:07 +00:00
|
|
|
proc start(m: MainchainMonitor, delayBeforeStart: Duration) =
|
|
|
|
if m.runFut.isNil:
|
|
|
|
let runFut = m.run(delayBeforeStart)
|
|
|
|
m.runFut = runFut
|
2020-03-24 11:13:07 +00:00
|
|
|
runFut.addCallback do (p: pointer):
|
|
|
|
if runFut.failed:
|
|
|
|
if runFut.error[] of CatchableError:
|
|
|
|
if runFut == m.runFut:
|
2020-11-02 20:16:24 +00:00
|
|
|
error "Eth1 chain monitoring failure, restarting", err = runFut.error.msg
|
2020-06-27 12:01:19 +00:00
|
|
|
m.stop()
|
2020-03-24 11:13:07 +00:00
|
|
|
m.start(5.seconds)
|
|
|
|
else:
|
|
|
|
fatal "Fatal exception reached", err = runFut.error.msg
|
|
|
|
quit 1
|
2019-11-22 13:16:07 +00:00
|
|
|
|
|
|
|
proc start*(m: MainchainMonitor) {.inline.} =
|
|
|
|
m.start(0.seconds)
|
|
|
|
|
2020-07-28 13:36:11 +00:00
|
|
|
proc getEth1BlockHash*(url: string, blockId: RtBlockIdentifier): Future[BlockHash] {.async.} =
|
2019-10-25 14:53:31 +00:00
|
|
|
let web3 = await newWeb3(url)
|
2020-04-22 23:35:55 +00:00
|
|
|
try:
|
2020-07-28 13:36:11 +00:00
|
|
|
let blk = await web3.provider.eth_getBlockByNumber(blockId, false)
|
|
|
|
return blk.hash
|
2020-04-22 23:35:55 +00:00
|
|
|
finally:
|
|
|
|
await web3.close()
|
|
|
|
|