Fluffy State Bridge - State Gossip via Portal JSON-RPC (#2535)
* Create block offers queue and collect account preimages. * Implement iterators to return account and storage proofs and bytecode from updatedCaches. * Implement building offers from proofs. * Refactor BlockDataRef type to only include required fields. * Store block data in database. * Improve state diff types. * Implement start state backfill from specific block. * Record last persisted block number in database. * Persist account preimages in db. * Apply state updates for DAO hard fork. * Implement state gossip of block offers via portal JSON RPC.
This commit is contained in:
parent
72c3ab8ced
commit
947f629903
|
@ -62,6 +62,8 @@ type
|
||||||
of contractCode:
|
of contractCode:
|
||||||
contractCodeKey*: ContractCodeKey
|
contractCodeKey*: ContractCodeKey
|
||||||
|
|
||||||
|
ContentKeyType* = AccountTrieNodeKey | ContractTrieNodeKey | ContractCodeKey
|
||||||
|
|
||||||
func init*(T: type AccountTrieNodeKey, path: Nibbles, nodeHash: NodeHash): T =
|
func init*(T: type AccountTrieNodeKey, path: Nibbles, nodeHash: NodeHash): T =
|
||||||
AccountTrieNodeKey(path: path, nodeHash: nodeHash)
|
AccountTrieNodeKey(path: path, nodeHash: nodeHash)
|
||||||
|
|
||||||
|
|
|
@ -15,9 +15,9 @@ import results, eth/common/eth_types, ssz_serialization, ../../../common/common_
|
||||||
export ssz_serialization, common_types, hash, results
|
export ssz_serialization, common_types, hash, results
|
||||||
|
|
||||||
const
|
const
|
||||||
MAX_TRIE_NODE_LEN = 1024
|
MAX_TRIE_NODE_LEN* = 1024
|
||||||
MAX_TRIE_PROOF_LEN = 65
|
MAX_TRIE_PROOF_LEN* = 65
|
||||||
MAX_BYTECODE_LEN = 32768
|
MAX_BYTECODE_LEN* = 32768
|
||||||
|
|
||||||
type
|
type
|
||||||
TrieNode* = List[byte, MAX_TRIE_NODE_LEN]
|
TrieNode* = List[byte, MAX_TRIE_NODE_LEN]
|
||||||
|
|
|
@ -102,7 +102,7 @@ func unpackNibbles*(packed: Nibbles): UnpackedNibbles =
|
||||||
output.add(first)
|
output.add(first)
|
||||||
output.add(second)
|
output.add(second)
|
||||||
|
|
||||||
move(output)
|
ensureMove(output)
|
||||||
|
|
||||||
func len(packed: Nibbles): int =
|
func len(packed: Nibbles): int =
|
||||||
let lenExclPrefix = (packed.len() - 1) * 2
|
let lenExclPrefix = (packed.len() - 1) * 2
|
||||||
|
@ -115,4 +115,4 @@ func len(packed: Nibbles): int =
|
||||||
func dropN*(unpacked: UnpackedNibbles, num: int): UnpackedNibbles =
|
func dropN*(unpacked: UnpackedNibbles, num: int): UnpackedNibbles =
|
||||||
var nibbles = unpacked
|
var nibbles = unpacked
|
||||||
nibbles.setLen(nibbles.len() - num)
|
nibbles.setLen(nibbles.len() - num)
|
||||||
move(nibbles)
|
ensureMove(nibbles)
|
||||||
|
|
|
@ -29,17 +29,12 @@ type AccountTrieOfferWithKey* =
|
||||||
type ContractTrieOfferWithKey* =
|
type ContractTrieOfferWithKey* =
|
||||||
tuple[key: ContractTrieNodeKey, offer: ContractTrieNodeOffer]
|
tuple[key: ContractTrieNodeKey, offer: ContractTrieNodeOffer]
|
||||||
|
|
||||||
|
type ContractCodeOfferWithKey* = tuple[key: ContractCodeKey, offer: ContractCodeOffer]
|
||||||
|
|
||||||
func withPath(proof: TrieProof, path: Nibbles): ProofWithPath =
|
func withPath(proof: TrieProof, path: Nibbles): ProofWithPath =
|
||||||
(path: path, proof: proof)
|
(path: path, proof: proof)
|
||||||
|
|
||||||
func withKey*(
|
func withKey*(offer: ContentOfferType, key: ContentKeyType): auto =
|
||||||
offer: AccountTrieNodeOffer, key: AccountTrieNodeKey
|
|
||||||
): AccountTrieOfferWithKey =
|
|
||||||
(key: key, offer: offer)
|
|
||||||
|
|
||||||
func withKey*(
|
|
||||||
offer: ContractTrieNodeOffer, key: ContractTrieNodeKey
|
|
||||||
): ContractTrieOfferWithKey =
|
|
||||||
(key: key, offer: offer)
|
(key: key, offer: offer)
|
||||||
|
|
||||||
func getParent(p: ProofWithPath): ProofWithPath =
|
func getParent(p: ProofWithPath): ProofWithPath =
|
||||||
|
|
|
@ -70,6 +70,20 @@ func toSlot*(storageProof: TrieProof): Result[UInt256, string] {.inline.} =
|
||||||
|
|
||||||
rlpDecodeContractTrieNode(storageProof[^1])
|
rlpDecodeContractTrieNode(storageProof[^1])
|
||||||
|
|
||||||
|
func removeLeafKeyEndNibbles*(
|
||||||
|
nibbles: Nibbles, leafNode: TrieNode
|
||||||
|
): Nibbles {.raises: RlpError.} =
|
||||||
|
let nodeRlp = rlpFromBytes(leafNode.asSeq())
|
||||||
|
doAssert(nodeRlp.listLen() == 2)
|
||||||
|
let (_, isLeaf, prefix) = decodePrefix(nodeRlp.listElem(0))
|
||||||
|
doAssert(isLeaf)
|
||||||
|
|
||||||
|
let leafPrefix = prefix.unpackNibbles()
|
||||||
|
var unpackedNibbles = nibbles.unpackNibbles()
|
||||||
|
doAssert(unpackedNibbles[^leafPrefix.len() .. ^1] == leafPrefix)
|
||||||
|
|
||||||
|
unpackedNibbles.dropN(leafPrefix.len()).packNibbles()
|
||||||
|
|
||||||
func toPath*(hash: KeccakHash): Nibbles {.inline.} =
|
func toPath*(hash: KeccakHash): Nibbles {.inline.} =
|
||||||
Nibbles.init(hash.data, isEven = true)
|
Nibbles.init(hash.data, isEven = true)
|
||||||
|
|
||||||
|
|
|
@ -61,20 +61,6 @@ type
|
||||||
func asNibbles*(key: openArray[byte], isEven = true): Nibbles =
|
func asNibbles*(key: openArray[byte], isEven = true): Nibbles =
|
||||||
Nibbles.init(key, isEven)
|
Nibbles.init(key, isEven)
|
||||||
|
|
||||||
func removeLeafKeyEndNibbles*(
|
|
||||||
nibbles: Nibbles, leafNode: TrieNode
|
|
||||||
): Nibbles {.raises: [RlpError].} =
|
|
||||||
let nodeRlp = rlpFromBytes(leafNode.asSeq())
|
|
||||||
doAssert(nodeRlp.listLen() == 2)
|
|
||||||
let (_, isLeaf, prefix) = decodePrefix(nodeRlp.listElem(0))
|
|
||||||
doAssert(isLeaf)
|
|
||||||
|
|
||||||
let leafPrefix = prefix.unpackNibbles()
|
|
||||||
var unpackedNibbles = nibbles.unpackNibbles()
|
|
||||||
doAssert(unpackedNibbles[^leafPrefix.len() .. ^1] == leafPrefix)
|
|
||||||
|
|
||||||
unpackedNibbles.dropN(leafPrefix.len()).packNibbles()
|
|
||||||
|
|
||||||
func asTrieProof*(branch: openArray[seq[byte]]): TrieProof =
|
func asTrieProof*(branch: openArray[seq[byte]]): TrieProof =
|
||||||
TrieProof.init(branch.map(node => TrieNode.init(node)))
|
TrieProof.init(branch.map(node => TrieNode.init(node)))
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ import
|
||||||
results,
|
results,
|
||||||
eth/[common, trie, trie/trie_defs],
|
eth/[common, trie, trie/trie_defs],
|
||||||
../../../nimbus/common/chain_config,
|
../../../nimbus/common/chain_config,
|
||||||
../../network/state/[state_content, state_validation],
|
../../network/state/[state_content, state_validation, state_utils],
|
||||||
./state_test_helpers
|
./state_test_helpers
|
||||||
|
|
||||||
template checkValidProofsForExistingLeafs(
|
template checkValidProofsForExistingLeafs(
|
||||||
|
|
|
@ -141,10 +141,10 @@ type
|
||||||
name: "state-dir"
|
name: "state-dir"
|
||||||
.}: InputDir
|
.}: InputDir
|
||||||
|
|
||||||
# TODO: support starting from a specific block. Currently this is not possible using the existing HexaryTrie library.
|
startBlockNumber* {.
|
||||||
# startBlockNumber* {.
|
desc: "The block number to start from", defaultValue: 1, name: "start-block"
|
||||||
# desc: "The block number to start from", defaultValue: 1, name: "start-block"
|
.}: uint64
|
||||||
# .}: uint64
|
|
||||||
verifyState* {.
|
verifyState* {.
|
||||||
desc: "Verify the fetched state before gossiping it into the network",
|
desc: "Verify the fetched state before gossiping it into the network",
|
||||||
defaultValue: true,
|
defaultValue: true,
|
||||||
|
|
|
@ -12,92 +12,144 @@ import
|
||||||
chronicles,
|
chronicles,
|
||||||
chronos,
|
chronos,
|
||||||
stint,
|
stint,
|
||||||
|
stew/byteutils,
|
||||||
web3/[eth_api, eth_api_types],
|
web3/[eth_api, eth_api_types],
|
||||||
results,
|
results,
|
||||||
eth/common/[eth_types, eth_types_rlp],
|
eth/common/[eth_types, eth_types_rlp],
|
||||||
../../../nimbus/common/chain_config,
|
../../../nimbus/common/chain_config,
|
||||||
|
../../common/common_utils,
|
||||||
../../rpc/rpc_calls/rpc_trace_calls,
|
../../rpc/rpc_calls/rpc_trace_calls,
|
||||||
./state_bridge/[database, state_diff, world_state_helper],
|
../../rpc/portal_rpc_client,
|
||||||
|
../../network/state/[state_content, state_gossip],
|
||||||
|
./state_bridge/[database, state_diff, world_state_helper, offers_builder],
|
||||||
./[portal_bridge_conf, portal_bridge_common]
|
./[portal_bridge_conf, portal_bridge_common]
|
||||||
|
|
||||||
type BlockData = object
|
type BlockData = object
|
||||||
blockNumber: uint64
|
blockNumber: uint64
|
||||||
blockObject: BlockObject
|
blockHash: KeccakHash
|
||||||
stateDiffs: seq[StateDiffRef]
|
miner: EthAddress
|
||||||
uncleBlocks: seq[BlockObject]
|
uncles: seq[tuple[miner: EthAddress, blockNumber: uint64]]
|
||||||
|
parentStateRoot: KeccakHash
|
||||||
|
stateRoot: KeccakHash
|
||||||
|
stateDiffs: seq[TransactionDiff]
|
||||||
|
|
||||||
|
type BlockOffersRef = ref object
|
||||||
|
blockNumber: uint64
|
||||||
|
accountTrieOffers: seq[AccountTrieOfferWithKey]
|
||||||
|
contractTrieOffers: seq[ContractTrieOfferWithKey]
|
||||||
|
contractCodeOffers: seq[ContractCodeOfferWithKey]
|
||||||
|
|
||||||
|
proc getBlockData(db: DatabaseRef, blockNumber: uint64): Opt[BlockData] =
|
||||||
|
let blockDataBytes = db.get(rlp.encode(blockNumber))
|
||||||
|
if blockDataBytes.len() == 0:
|
||||||
|
return Opt.none(BlockData)
|
||||||
|
|
||||||
|
try:
|
||||||
|
Opt.some(rlp.decode(blockDataBytes, BlockData))
|
||||||
|
except RlpError as e:
|
||||||
|
raiseAssert(e.msg) # Should never happen
|
||||||
|
|
||||||
|
proc putBlockData(
|
||||||
|
db: DatabaseRef, blockNumber: uint64, blockData: BlockData
|
||||||
|
) {.inline.} =
|
||||||
|
db.put(rlp.encode(blockNumber), rlp.encode(blockData))
|
||||||
|
|
||||||
|
proc getLastPersistedBlockNumber(db: DatabaseRef): Opt[uint64] =
|
||||||
|
let blockNumberBytes = db.get(rlp.encode("lastPersistedBlockNumber"))
|
||||||
|
if blockNumberBytes.len() == 0:
|
||||||
|
return Opt.none(uint64)
|
||||||
|
|
||||||
|
try:
|
||||||
|
Opt.some(rlp.decode(blockNumberBytes, uint64))
|
||||||
|
except RlpError as e:
|
||||||
|
raiseAssert(e.msg) # Should never happen
|
||||||
|
|
||||||
|
proc putLastPersistedBlockNumber(db: DatabaseRef, blockNumber: uint64) {.inline.} =
|
||||||
|
db.put(rlp.encode("lastPersistedBlockNumber"), rlp.encode(blockNumber))
|
||||||
|
|
||||||
proc runBackfillCollectBlockDataLoop(
|
proc runBackfillCollectBlockDataLoop(
|
||||||
|
db: DatabaseRef,
|
||||||
blockDataQueue: AsyncQueue[BlockData],
|
blockDataQueue: AsyncQueue[BlockData],
|
||||||
web3Client: RpcClient,
|
web3Client: RpcClient,
|
||||||
startBlockNumber: uint64,
|
startBlockNumber: uint64,
|
||||||
) {.async: (raises: [CancelledError]).} =
|
) {.async: (raises: [CancelledError]).} =
|
||||||
debug "Starting state backfill collect block data loop"
|
info "Starting state backfill collect block data loop"
|
||||||
|
|
||||||
var currentBlockNumber = startBlockNumber
|
let parentBlock = (
|
||||||
|
await web3Client.getBlockByNumber(blockId(startBlockNumber - 1.uint64), false)
|
||||||
|
).valueOr:
|
||||||
|
raiseAssert("Failed to get parent block")
|
||||||
|
|
||||||
|
var
|
||||||
|
parentStateRoot = parentBlock.stateRoot
|
||||||
|
currentBlockNumber = startBlockNumber
|
||||||
|
|
||||||
while true:
|
while true:
|
||||||
if currentBlockNumber mod 10000 == 0:
|
if currentBlockNumber mod 10000 == 0:
|
||||||
info "Collecting block data for block number: ", blockNumber = currentBlockNumber
|
info "Collecting block data for block number: ", blockNumber = currentBlockNumber
|
||||||
|
|
||||||
|
let blockData = db.getBlockData(currentBlockNumber).valueOr:
|
||||||
|
# block data doesn't exist in db so we fetch it via RPC
|
||||||
let
|
let
|
||||||
blockId = blockId(currentBlockNumber)
|
blockId = blockId(currentBlockNumber)
|
||||||
blockRequest = web3Client.getBlockByNumber(blockId, false)
|
blockObject = (await web3Client.getBlockByNumber(blockId, false)).valueOr:
|
||||||
stateDiffsRequest = web3Client.getStateDiffsByBlockNumber(blockId)
|
|
||||||
|
|
||||||
blockObject = (await blockRequest).valueOr:
|
|
||||||
error "Failed to get block", error
|
error "Failed to get block", error
|
||||||
await sleepAsync(1.seconds)
|
await sleepAsync(1.seconds)
|
||||||
continue
|
continue
|
||||||
|
stateDiffs = (await web3Client.getStateDiffsByBlockNumber(blockId)).valueOr:
|
||||||
var uncleBlockRequests: seq[Future[Result[BlockObject, string]]]
|
|
||||||
for i in 0 .. blockObject.uncles.high:
|
|
||||||
uncleBlockRequests.add(
|
|
||||||
web3Client.getUncleByBlockNumberAndIndex(blockId, i.Quantity)
|
|
||||||
)
|
|
||||||
|
|
||||||
let stateDiffs = (await stateDiffsRequest).valueOr:
|
|
||||||
error "Failed to get state diffs", error
|
error "Failed to get state diffs", error
|
||||||
await sleepAsync(1.seconds)
|
await sleepAsync(1.seconds)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
var uncleBlocks: seq[BlockObject]
|
var uncleBlocks: seq[BlockObject]
|
||||||
for uncleBlockRequest in uncleBlockRequests:
|
for i in 0 .. blockObject.uncles.high:
|
||||||
try:
|
let uncleBlock = (
|
||||||
let uncleBlock = (await uncleBlockRequest).valueOr:
|
await web3Client.getUncleByBlockNumberAndIndex(blockId, i.Quantity)
|
||||||
error "Failed to get uncle blocks", error
|
).valueOr:
|
||||||
|
error "Failed to get uncle block", error
|
||||||
await sleepAsync(1.seconds)
|
await sleepAsync(1.seconds)
|
||||||
break
|
|
||||||
uncleBlocks.add(uncleBlock)
|
|
||||||
except CatchableError as e:
|
|
||||||
error "Failed to get uncleBlockRequest", error = e.msg
|
|
||||||
break
|
|
||||||
|
|
||||||
if uncleBlocks.len() < uncleBlockRequests.len():
|
|
||||||
continue
|
continue
|
||||||
|
uncleBlocks.add(uncleBlock)
|
||||||
|
|
||||||
let blockData = BlockData(
|
let blockData = BlockData(
|
||||||
blockNumber: currentBlockNumber,
|
blockNumber: currentBlockNumber,
|
||||||
blockObject: blockObject,
|
blockHash: KeccakHash.fromBytes(blockObject.hash.bytes()),
|
||||||
|
miner: blockObject.miner.EthAddress,
|
||||||
|
uncles: uncleBlocks.mapIt((it.miner.EthAddress, it.number.uint64)),
|
||||||
|
parentStateRoot: KeccakHash.fromBytes(parentStateRoot.bytes()),
|
||||||
|
stateRoot: KeccakHash.fromBytes(blockObject.stateRoot.bytes()),
|
||||||
stateDiffs: stateDiffs,
|
stateDiffs: stateDiffs,
|
||||||
uncleBlocks: uncleBlocks,
|
|
||||||
)
|
)
|
||||||
await blockDataQueue.addLast(blockData)
|
db.putBlockData(currentBlockNumber, blockData)
|
||||||
|
|
||||||
|
parentStateRoot = blockObject.stateRoot
|
||||||
|
blockData
|
||||||
|
|
||||||
|
await blockDataQueue.addLast(blockData)
|
||||||
inc currentBlockNumber
|
inc currentBlockNumber
|
||||||
|
|
||||||
proc runBackfillBuildStateLoop(
|
proc runBackfillBuildBlockOffersLoop(
|
||||||
blockDataQueue: AsyncQueue[BlockData], stateDir: string
|
db: DatabaseRef,
|
||||||
|
blockDataQueue: AsyncQueue[BlockData],
|
||||||
|
blockOffersQueue: AsyncQueue[BlockOffersRef],
|
||||||
) {.async: (raises: [CancelledError]).} =
|
) {.async: (raises: [CancelledError]).} =
|
||||||
debug "Starting state backfill build state loop"
|
info "Starting state backfill build block offers loop"
|
||||||
|
|
||||||
let db = DatabaseRef.init(stateDir).get()
|
# wait for the first block data to be put on the queue
|
||||||
defer:
|
# so that we can access the first block once available
|
||||||
db.close()
|
while blockDataQueue.empty():
|
||||||
|
await sleepAsync(100.milliseconds)
|
||||||
|
# peek but don't remove it so that it can be processed later
|
||||||
|
let firstBlock = blockDataQueue[0]
|
||||||
|
|
||||||
let worldState = db.withTransaction:
|
# Only apply genesis accounts if starting from block 1
|
||||||
let
|
if firstBlock.blockNumber == 1:
|
||||||
|
info "Building state for genesis"
|
||||||
|
|
||||||
|
db.withTransaction:
|
||||||
# Requires an active transaction because it writes an emptyRlp node
|
# Requires an active transaction because it writes an emptyRlp node
|
||||||
# to the accounts HexaryTrie on initialization
|
# to the accounts HexaryTrie on initialization
|
||||||
|
let
|
||||||
ws = WorldStateRef.init(db)
|
ws = WorldStateRef.init(db)
|
||||||
genesisAccounts =
|
genesisAccounts =
|
||||||
try:
|
try:
|
||||||
|
@ -105,7 +157,24 @@ proc runBackfillBuildStateLoop(
|
||||||
except CatchableError as e:
|
except CatchableError as e:
|
||||||
raiseAssert(e.msg) # Should never happen
|
raiseAssert(e.msg) # Should never happen
|
||||||
ws.applyGenesisAccounts(genesisAccounts)
|
ws.applyGenesisAccounts(genesisAccounts)
|
||||||
ws
|
|
||||||
|
let genesisBlockHash = KeccakHash.fromHex(
|
||||||
|
"d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"
|
||||||
|
)
|
||||||
|
var builder = OffersBuilderRef.init(ws, genesisBlockHash)
|
||||||
|
builder.buildBlockOffers()
|
||||||
|
|
||||||
|
await blockOffersQueue.addLast(
|
||||||
|
BlockOffersRef(
|
||||||
|
blockNumber: 0.uint64,
|
||||||
|
accountTrieOffers: builder.getAccountTrieOffers(),
|
||||||
|
contractTrieOffers: builder.getContractTrieOffers(),
|
||||||
|
contractCodeOffers: builder.getContractCodeOffers(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Load the world state using the parent state root
|
||||||
|
let worldState = WorldStateRef.init(db, firstBlock.parentStateRoot)
|
||||||
|
|
||||||
while true:
|
while true:
|
||||||
let blockData = await blockDataQueue.popFirst()
|
let blockData = await blockDataQueue.popFirst()
|
||||||
|
@ -113,33 +182,112 @@ proc runBackfillBuildStateLoop(
|
||||||
if blockData.blockNumber mod 10000 == 0:
|
if blockData.blockNumber mod 10000 == 0:
|
||||||
info "Building state for block number: ", blockNumber = blockData.blockNumber
|
info "Building state for block number: ", blockNumber = blockData.blockNumber
|
||||||
|
|
||||||
|
# For now all WorldStateRef functions need to be inside a transaction
|
||||||
|
# because the DatabaseRef backends currently only supports reading and
|
||||||
|
# writing to/from a single active transaction.
|
||||||
db.withTransaction:
|
db.withTransaction:
|
||||||
for stateDiff in blockData.stateDiffs:
|
for stateDiff in blockData.stateDiffs:
|
||||||
worldState.applyStateDiff(stateDiff)
|
worldState.applyStateDiff(stateDiff)
|
||||||
let
|
|
||||||
minerData =
|
|
||||||
(EthAddress(blockData.blockObject.miner), blockData.blockObject.number.uint64)
|
|
||||||
uncleMinersData =
|
|
||||||
blockData.uncleBlocks.mapIt((EthAddress(it.miner), it.number.uint64))
|
|
||||||
worldState.applyBlockRewards(minerData, uncleMinersData)
|
|
||||||
|
|
||||||
doAssert(blockData.blockObject.stateRoot.bytes() == worldState.stateRoot.data)
|
worldState.applyBlockRewards(
|
||||||
|
(blockData.miner, blockData.blockNumber), blockData.uncles
|
||||||
|
)
|
||||||
|
|
||||||
|
if blockData.blockNumber == 1_920_000:
|
||||||
|
info "Applying state updates for DAO hard fork"
|
||||||
|
worldState.applyDAOHardFork()
|
||||||
|
|
||||||
|
doAssert(
|
||||||
|
worldState.stateRoot == blockData.stateRoot,
|
||||||
|
"State root mismatch at block number: " & $blockData.blockNumber,
|
||||||
|
)
|
||||||
trace "State diffs successfully applied to block number:",
|
trace "State diffs successfully applied to block number:",
|
||||||
blockNumber = blockData.blockNumber
|
blockNumber = blockData.blockNumber
|
||||||
|
|
||||||
proc runBackfillMetricsLoop(
|
# worldState.verifyProofs(blockData.parentStateRoot, blockData.stateRoot)
|
||||||
blockDataQueue: AsyncQueue[BlockData]
|
|
||||||
|
var builder = OffersBuilderRef.init(worldState, blockData.blockHash)
|
||||||
|
builder.buildBlockOffers()
|
||||||
|
|
||||||
|
await blockOffersQueue.addLast(
|
||||||
|
BlockOffersRef(
|
||||||
|
blockNumber: blockData.blockNumber,
|
||||||
|
accountTrieOffers: builder.getAccountTrieOffers(),
|
||||||
|
contractTrieOffers: builder.getContractTrieOffers(),
|
||||||
|
contractCodeOffers: builder.getContractCodeOffers(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# After commit of the above db transaction which stores the updated account state
|
||||||
|
# then we store the last persisted block number in the database so that we can use it
|
||||||
|
# to enable restarting from this block if needed
|
||||||
|
db.putLastPersistedBlockNumber(blockData.blockNumber)
|
||||||
|
|
||||||
|
proc gossipOffer(
|
||||||
|
portalClient: RpcClient,
|
||||||
|
offerWithKey:
|
||||||
|
AccountTrieOfferWithKey | ContractTrieOfferWithKey | ContractCodeOfferWithKey,
|
||||||
) {.async: (raises: [CancelledError]).} =
|
) {.async: (raises: [CancelledError]).} =
|
||||||
debug "Starting state backfill metrics loop"
|
let
|
||||||
|
keyBytes = offerWithKey.key.toContentKey().encode().asSeq()
|
||||||
|
offerBytes = offerWithKey.offer.encode()
|
||||||
|
try:
|
||||||
|
let numPeers =
|
||||||
|
await portalClient.portal_stateGossip(keyBytes.to0xHex(), offerBytes.to0xHex())
|
||||||
|
debug "Gossiping offer to peers: ", offerKey = keyBytes.to0xHex(), numPeers
|
||||||
|
except CatchableError as e:
|
||||||
|
raiseAssert(e.msg) # Should never happen
|
||||||
|
|
||||||
|
proc recursiveGossipOffer(
|
||||||
|
portalClient: RpcClient,
|
||||||
|
offerWithKey: AccountTrieOfferWithKey | ContractTrieOfferWithKey,
|
||||||
|
) {.async: (raises: [CancelledError]).} =
|
||||||
|
await portalClient.gossipOffer(offerWithKey)
|
||||||
|
|
||||||
|
# root node, recursive gossip is finished
|
||||||
|
if offerWithKey.key.path.unpackNibbles().len() == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
# continue the recursive gossip by sharing the parent offer with peers
|
||||||
|
await portalClient.recursiveGossipOffer(offerWithKey.getParent())
|
||||||
|
|
||||||
|
proc runBackfillGossipBlockOffersLoop(
|
||||||
|
blockOffersQueue: AsyncQueue[BlockOffersRef], portalClient: RpcClient
|
||||||
|
) {.async: (raises: [CancelledError]).} =
|
||||||
|
info "Starting state backfill gossip block offers loop"
|
||||||
|
|
||||||
|
while true:
|
||||||
|
let blockOffers = await blockOffersQueue.popFirst()
|
||||||
|
|
||||||
|
for offerWithKey in blockOffers.accountTrieOffers:
|
||||||
|
await portalClient.recursiveGossipOffer(offerWithKey)
|
||||||
|
|
||||||
|
for offerWithKey in blockOffers.contractTrieOffers:
|
||||||
|
await portalClient.recursiveGossipOffer(offerWithKey)
|
||||||
|
|
||||||
|
for offerWithKey in blockOffers.contractCodeOffers:
|
||||||
|
await portalClient.gossipOffer(offerWithKey)
|
||||||
|
|
||||||
|
proc runBackfillMetricsLoop(
|
||||||
|
blockDataQueue: AsyncQueue[BlockData], blockOffersQueue: AsyncQueue[BlockOffersRef]
|
||||||
|
) {.async: (raises: [CancelledError]).} =
|
||||||
|
info "Starting state backfill metrics loop"
|
||||||
|
|
||||||
while true:
|
while true:
|
||||||
await sleepAsync(10.seconds)
|
await sleepAsync(10.seconds)
|
||||||
info "Block data queue length: ", queueLen = blockDataQueue.len()
|
info "Block data queue length: ", blockDataQueueLen = blockDataQueue.len()
|
||||||
|
info "Block offers queue length: ", blockOffersQueueLen = blockOffersQueue.len()
|
||||||
|
|
||||||
proc runState*(config: PortalBridgeConf) =
|
proc runState*(config: PortalBridgeConf) =
|
||||||
let
|
let
|
||||||
#portalClient = newRpcClientConnect(config.portalRpcUrl)
|
portalClient = newRpcClientConnect(config.portalRpcUrl)
|
||||||
web3Client = newRpcClientConnect(config.web3UrlState)
|
web3Client = newRpcClientConnect(config.web3UrlState)
|
||||||
|
db = DatabaseRef.init(config.stateDir.string).get()
|
||||||
|
defer:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
if web3Client of RpcHttpClient:
|
||||||
|
warn "Using a WebSocket connection to the JSON-RPC API is recommended to improve performance"
|
||||||
|
|
||||||
# TODO:
|
# TODO:
|
||||||
# Here we'd want to implement initially a loop that backfills the state
|
# Here we'd want to implement initially a loop that backfills the state
|
||||||
|
@ -154,20 +302,34 @@ proc runState*(config: PortalBridgeConf) =
|
||||||
# inside the bridge, and getting the blocks from era1 files.
|
# inside the bridge, and getting the blocks from era1 files.
|
||||||
|
|
||||||
if config.backfillState:
|
if config.backfillState:
|
||||||
const startBlockNumber = 1
|
let maybeLastPersistedBlock = db.getLastPersistedBlockNumber()
|
||||||
# This will become a parameter in the config once we can support it
|
if maybeLastPersistedBlock.isSome():
|
||||||
info "Starting state backfill from block number: ", startBlockNumber
|
info "Last persisted block found in the database: ",
|
||||||
|
lastPersistedBlock = maybeLastPersistedBlock.get()
|
||||||
|
if config.startBlockNumber < 1 or
|
||||||
|
config.startBlockNumber > maybeLastPersistedBlock.get():
|
||||||
|
warn "Start block must be set to a value between 1 and the last persisted block"
|
||||||
|
quit QuitFailure
|
||||||
|
else:
|
||||||
|
info "No last persisted block found in the database"
|
||||||
|
if config.startBlockNumber != 1:
|
||||||
|
warn "Start block must be set to 1"
|
||||||
|
quit QuitFailure
|
||||||
|
|
||||||
|
info "Starting state backfill from block number: ",
|
||||||
|
startBlockNumber = config.startBlockNumber
|
||||||
|
|
||||||
const bufferSize = 1000 # Should we make this configurable?
|
const bufferSize = 1000 # Should we make this configurable?
|
||||||
let blockDataQueue = newAsyncQueue[BlockData](bufferSize)
|
let
|
||||||
|
blockDataQueue = newAsyncQueue[BlockData](bufferSize)
|
||||||
|
blockOffersQueue = newAsyncQueue[BlockOffersRef](bufferSize)
|
||||||
|
|
||||||
asyncSpawn runBackfillCollectBlockDataLoop(
|
asyncSpawn runBackfillCollectBlockDataLoop(
|
||||||
blockDataQueue, web3Client, startBlockNumber
|
db, blockDataQueue, web3Client, config.startBlockNumber
|
||||||
)
|
)
|
||||||
|
asyncSpawn runBackfillBuildBlockOffersLoop(db, blockDataQueue, blockOffersQueue)
|
||||||
asyncSpawn runBackfillBuildStateLoop(blockDataQueue, config.stateDir.string)
|
asyncSpawn runBackfillGossipBlockOffersLoop(blockOffersQueue, portalClient)
|
||||||
|
asyncSpawn runBackfillMetricsLoop(blockDataQueue, blockOffersQueue)
|
||||||
asyncSpawn runBackfillMetricsLoop(blockDataQueue)
|
|
||||||
|
|
||||||
while true:
|
while true:
|
||||||
poll()
|
poll()
|
||||||
|
|
|
@ -14,27 +14,36 @@ export results, db
|
||||||
const COL_FAMILY_NAME_ACCOUNTS = "A"
|
const COL_FAMILY_NAME_ACCOUNTS = "A"
|
||||||
const COL_FAMILY_NAME_STORAGE = "S"
|
const COL_FAMILY_NAME_STORAGE = "S"
|
||||||
const COL_FAMILY_NAME_BYTECODE = "B"
|
const COL_FAMILY_NAME_BYTECODE = "B"
|
||||||
|
const COL_FAMILY_NAME_PREIMAGES = "P"
|
||||||
|
|
||||||
const COL_FAMILY_NAMES =
|
const COL_FAMILY_NAMES = [
|
||||||
[COL_FAMILY_NAME_ACCOUNTS, COL_FAMILY_NAME_STORAGE, COL_FAMILY_NAME_BYTECODE]
|
COL_FAMILY_NAME_ACCOUNTS, COL_FAMILY_NAME_STORAGE, COL_FAMILY_NAME_BYTECODE,
|
||||||
|
COL_FAMILY_NAME_PREIMAGES,
|
||||||
|
]
|
||||||
|
|
||||||
type
|
type
|
||||||
AccountsBackendRef = ref object of RootObj
|
AccountsBackendRef = ref object of RootObj
|
||||||
cfHandle: ColFamilyHandleRef
|
cfHandle: ColFamilyHandleRef
|
||||||
tx: TransactionRef
|
tx: TransactionRef
|
||||||
updatedCache: TableRef[seq[byte], seq[byte]]
|
updatedCache: TrieDatabaseRef
|
||||||
|
|
||||||
StorageBackendRef = ref object of RootObj
|
StorageBackendRef = ref object of RootObj
|
||||||
cfHandle: ColFamilyHandleRef
|
cfHandle: ColFamilyHandleRef
|
||||||
tx: TransactionRef
|
tx: TransactionRef
|
||||||
updatedCache: TableRef[seq[byte], seq[byte]]
|
updatedCache: TrieDatabaseRef
|
||||||
|
|
||||||
BytecodeBackendRef = ref object of RootObj
|
BytecodeBackendRef = ref object of RootObj
|
||||||
cfHandle: ColFamilyHandleRef
|
cfHandle: ColFamilyHandleRef
|
||||||
tx: TransactionRef
|
tx: TransactionRef
|
||||||
updatedCache: TableRef[seq[byte], seq[byte]]
|
updatedCache: TrieDatabaseRef
|
||||||
|
|
||||||
DatabaseBackendRef = AccountsBackendRef | StorageBackendRef | BytecodeBackendRef
|
PreimagesBackendRef = ref object of RootObj
|
||||||
|
cfHandle: ColFamilyHandleRef
|
||||||
|
tx: TransactionRef
|
||||||
|
updatedCache: TrieDatabaseRef
|
||||||
|
|
||||||
|
DatabaseBackendRef =
|
||||||
|
AccountsBackendRef | StorageBackendRef | BytecodeBackendRef | PreimagesBackendRef
|
||||||
|
|
||||||
DatabaseRef* = ref object
|
DatabaseRef* = ref object
|
||||||
rocksDb: OptimisticTxDbRef
|
rocksDb: OptimisticTxDbRef
|
||||||
|
@ -42,6 +51,7 @@ type
|
||||||
accountsBackend: AccountsBackendRef
|
accountsBackend: AccountsBackendRef
|
||||||
storageBackend: StorageBackendRef
|
storageBackend: StorageBackendRef
|
||||||
bytecodeBackend: BytecodeBackendRef
|
bytecodeBackend: BytecodeBackendRef
|
||||||
|
preimagesBackend: PreimagesBackendRef
|
||||||
|
|
||||||
proc init*(T: type DatabaseRef, baseDir: string): Result[T, string] =
|
proc init*(T: type DatabaseRef, baseDir: string): Result[T, string] =
|
||||||
let dbPath = baseDir / "db"
|
let dbPath = baseDir / "db"
|
||||||
|
@ -69,6 +79,9 @@ proc init*(T: type DatabaseRef, baseDir: string): Result[T, string] =
|
||||||
bytecodeBackend = BytecodeBackendRef(
|
bytecodeBackend = BytecodeBackendRef(
|
||||||
cfHandle: db.getColFamilyHandle(COL_FAMILY_NAME_BYTECODE).get()
|
cfHandle: db.getColFamilyHandle(COL_FAMILY_NAME_BYTECODE).get()
|
||||||
)
|
)
|
||||||
|
preimagesBackend = PreimagesBackendRef(
|
||||||
|
cfHandle: db.getColFamilyHandle(COL_FAMILY_NAME_PREIMAGES).get()
|
||||||
|
)
|
||||||
|
|
||||||
ok(
|
ok(
|
||||||
T(
|
T(
|
||||||
|
@ -77,6 +90,7 @@ proc init*(T: type DatabaseRef, baseDir: string): Result[T, string] =
|
||||||
accountsBackend: accountsBackend,
|
accountsBackend: accountsBackend,
|
||||||
storageBackend: storageBackend,
|
storageBackend: storageBackend,
|
||||||
bytecodeBackend: bytecodeBackend,
|
bytecodeBackend: bytecodeBackend,
|
||||||
|
preimagesBackend: preimagesBackend,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -92,7 +106,8 @@ proc put(
|
||||||
dbBackend: DatabaseBackendRef, key, val: openArray[byte]
|
dbBackend: DatabaseBackendRef, key, val: openArray[byte]
|
||||||
) {.gcsafe, raises: [].} =
|
) {.gcsafe, raises: [].} =
|
||||||
doAssert dbBackend.tx.put(key, val, dbBackend.cfHandle).isOk()
|
doAssert dbBackend.tx.put(key, val, dbBackend.cfHandle).isOk()
|
||||||
dbBackend.updatedCache[@key] = @val
|
if not dbBackend.updatedCache.isNil():
|
||||||
|
dbBackend.updatedCache.put(key, val)
|
||||||
|
|
||||||
proc get(
|
proc get(
|
||||||
dbBackend: DatabaseBackendRef, key: openArray[byte]
|
dbBackend: DatabaseBackendRef, key: openArray[byte]
|
||||||
|
@ -111,15 +126,47 @@ proc del(
|
||||||
else:
|
else:
|
||||||
false
|
false
|
||||||
|
|
||||||
proc getAccountsBackend*(db: DatabaseRef): TrieDatabaseRef =
|
proc getAccountsBackend*(db: DatabaseRef): TrieDatabaseRef {.inline.} =
|
||||||
trieDB(db.accountsBackend)
|
trieDB(db.accountsBackend)
|
||||||
|
|
||||||
proc getStorageBackend*(db: DatabaseRef): TrieDatabaseRef =
|
proc getStorageBackend*(db: DatabaseRef): TrieDatabaseRef {.inline.} =
|
||||||
trieDB(db.storageBackend)
|
trieDB(db.storageBackend)
|
||||||
|
|
||||||
proc getBytecodeBackend*(db: DatabaseRef): TrieDatabaseRef =
|
proc getBytecodeBackend*(db: DatabaseRef): TrieDatabaseRef {.inline.} =
|
||||||
trieDB(db.bytecodeBackend)
|
trieDB(db.bytecodeBackend)
|
||||||
|
|
||||||
|
proc getPreimagesBackend*(db: DatabaseRef): TrieDatabaseRef {.inline.} =
|
||||||
|
trieDB(db.preimagesBackend)
|
||||||
|
|
||||||
|
proc getAccountsUpdatedCache*(db: DatabaseRef): TrieDatabaseRef {.inline.} =
|
||||||
|
db.accountsBackend.updatedCache
|
||||||
|
|
||||||
|
proc getStorageUpdatedCache*(db: DatabaseRef): TrieDatabaseRef {.inline.} =
|
||||||
|
db.storageBackend.updatedCache
|
||||||
|
|
||||||
|
proc getBytecodeUpdatedCache*(db: DatabaseRef): TrieDatabaseRef {.inline.} =
|
||||||
|
db.bytecodeBackend.updatedCache
|
||||||
|
|
||||||
|
proc put*(db: DatabaseRef, key, val: openArray[byte]) =
|
||||||
|
let tx = db.rocksDb.beginTransaction()
|
||||||
|
defer:
|
||||||
|
tx.close()
|
||||||
|
|
||||||
|
# using default column family
|
||||||
|
doAssert tx.put(key, val).isOk()
|
||||||
|
doAssert tx.commit().isOk()
|
||||||
|
|
||||||
|
proc get*(db: DatabaseRef, key: openArray[byte]): seq[byte] =
|
||||||
|
let tx = db.rocksDb.beginTransaction()
|
||||||
|
defer:
|
||||||
|
tx.close()
|
||||||
|
|
||||||
|
# using default column family
|
||||||
|
if tx.get(key, onData).get():
|
||||||
|
tx.get(key).get()
|
||||||
|
else:
|
||||||
|
@[]
|
||||||
|
|
||||||
proc beginTransaction*(db: DatabaseRef): Result[void, string] =
|
proc beginTransaction*(db: DatabaseRef): Result[void, string] =
|
||||||
if not db.pendingTransaction.isNil():
|
if not db.pendingTransaction.isNil():
|
||||||
return err("DatabaseRef: Pending transaction already in progress")
|
return err("DatabaseRef: Pending transaction already in progress")
|
||||||
|
@ -129,10 +176,12 @@ proc beginTransaction*(db: DatabaseRef): Result[void, string] =
|
||||||
db.accountsBackend.tx = tx
|
db.accountsBackend.tx = tx
|
||||||
db.storageBackend.tx = tx
|
db.storageBackend.tx = tx
|
||||||
db.bytecodeBackend.tx = tx
|
db.bytecodeBackend.tx = tx
|
||||||
|
db.preimagesBackend.tx = tx
|
||||||
|
|
||||||
db.accountsBackend.updatedCache = newTable[seq[byte], seq[byte]]()
|
db.accountsBackend.updatedCache = newMemoryDB()
|
||||||
db.storageBackend.updatedCache = newTable[seq[byte], seq[byte]]()
|
db.storageBackend.updatedCache = newMemoryDB()
|
||||||
db.bytecodeBackend.updatedCache = newTable[seq[byte], seq[byte]]()
|
db.bytecodeBackend.updatedCache = newMemoryDB()
|
||||||
|
db.preimagesBackend.updatedCache = nil # not used
|
||||||
|
|
||||||
ok()
|
ok()
|
||||||
|
|
||||||
|
@ -165,15 +214,6 @@ template withTransaction*(db: DatabaseRef, body: untyped): auto =
|
||||||
finally:
|
finally:
|
||||||
db.commitTransaction().expect("Transaction should be commited")
|
db.commitTransaction().expect("Transaction should be commited")
|
||||||
|
|
||||||
template accountsBackendUpdatedCache*(db: DatabaseRef): TableRef[seq[byte], seq[byte]] =
|
|
||||||
db.accountsBackend.updatedCache
|
|
||||||
|
|
||||||
template storageBackendUpdatedCache*(db: DatabaseRef): TableRef[seq[byte], seq[byte]] =
|
|
||||||
db.storageBackend.updatedCache
|
|
||||||
|
|
||||||
template bytecodeBackendUpdatedCache*(db: DatabaseRef): TableRef[seq[byte], seq[byte]] =
|
|
||||||
db.bytecodeBackend.updatedCache
|
|
||||||
|
|
||||||
proc close*(db: DatabaseRef) =
|
proc close*(db: DatabaseRef) =
|
||||||
if not db.pendingTransaction.isNil():
|
if not db.pendingTransaction.isNil():
|
||||||
discard db.rollbackTransaction()
|
discard db.rollbackTransaction()
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
# Fluffy
|
||||||
|
# Copyright (c) 2024 Status Research & Development GmbH
|
||||||
|
# Licensed and distributed under either of
|
||||||
|
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
|
||||||
|
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
||||||
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||||
|
|
||||||
|
{.push raises: [].}
|
||||||
|
|
||||||
|
import
|
||||||
|
std/[sequtils, sugar],
|
||||||
|
eth/common,
|
||||||
|
../../../network/state/[state_content, state_utils, state_gossip],
|
||||||
|
./world_state
|
||||||
|
|
||||||
|
type OffersBuilderRef* = ref object
|
||||||
|
worldState: WorldStateRef
|
||||||
|
blockHash: BlockHash
|
||||||
|
accountTrieOffers: seq[AccountTrieOfferWithKey]
|
||||||
|
contractTrieOffers: seq[ContractTrieOfferWithKey]
|
||||||
|
contractCodeOffers: seq[ContractCodeOfferWithKey]
|
||||||
|
|
||||||
|
proc init*(
|
||||||
|
T: type OffersBuilderRef, worldState: WorldStateRef, blockHash: BlockHash
|
||||||
|
): T =
|
||||||
|
T(worldState: worldState, blockHash: blockHash)
|
||||||
|
|
||||||
|
proc toTrieProof(proof: seq[seq[byte]]): TrieProof =
|
||||||
|
TrieProof.init(proof.map((node) => TrieNode.init(node)))
|
||||||
|
|
||||||
|
proc buildAccountTrieNodeOffer(
|
||||||
|
builder: var OffersBuilderRef, address: EthAddress, proof: TrieProof
|
||||||
|
) =
|
||||||
|
try:
|
||||||
|
let
|
||||||
|
path = removeLeafKeyEndNibbles(
|
||||||
|
Nibbles.init(worldState.toAccountKey(address).data, isEven = true), proof[^1]
|
||||||
|
)
|
||||||
|
offerKey = AccountTrieNodeKey.init(path, keccakHash(proof[^1].asSeq()))
|
||||||
|
offerValue = AccountTrieNodeOffer.init(proof, builder.blockHash)
|
||||||
|
|
||||||
|
builder.accountTrieOffers.add(offerValue.withKey(offerKey))
|
||||||
|
except RlpError as e:
|
||||||
|
raiseAssert(e.msg) # Should never happen
|
||||||
|
|
||||||
|
proc buildContractTrieNodeOffer(
|
||||||
|
builder: var OffersBuilderRef,
|
||||||
|
address: EthAddress,
|
||||||
|
slotHash: SlotKeyHash,
|
||||||
|
storageProof: TrieProof,
|
||||||
|
accountProof: TrieProof,
|
||||||
|
) =
|
||||||
|
let
|
||||||
|
path = Nibbles.init(slotHash.data, isEven = true)
|
||||||
|
offerKey =
|
||||||
|
ContractTrieNodeKey.init(address, path, keccakHash(storageProof[^1].asSeq()))
|
||||||
|
offerValue =
|
||||||
|
ContractTrieNodeOffer.init(storageProof, accountProof, builder.blockHash)
|
||||||
|
|
||||||
|
builder.contractTrieOffers.add(offerValue.withKey(offerKey))
|
||||||
|
|
||||||
|
proc buildContractCodeOffer(
|
||||||
|
builder: var OffersBuilderRef,
|
||||||
|
address: EthAddress,
|
||||||
|
code: seq[byte],
|
||||||
|
accountProof: TrieProof,
|
||||||
|
) =
|
||||||
|
let
|
||||||
|
#bytecode = Bytelist.init(code) # This fails to compile for some reason
|
||||||
|
bytecode = List[byte, MAX_BYTECODE_LEN](code)
|
||||||
|
offerKey = ContractCodeKey.init(address, keccakHash(code))
|
||||||
|
offerValue = ContractCodeOffer.init(bytecode, accountProof, builder.blockHash)
|
||||||
|
|
||||||
|
builder.contractCodeOffers.add(offerValue.withKey(offerKey))
|
||||||
|
|
||||||
|
proc buildBlockOffers*(builder: var OffersBuilderRef) =
|
||||||
|
for address, proof in builder.worldState.updatedAccountProofs():
|
||||||
|
let accountProof = toTrieProof(proof)
|
||||||
|
builder.buildAccountTrieNodeOffer(address, accountProof)
|
||||||
|
|
||||||
|
for slotHash, sProof in builder.worldState.updatedStorageProofs(address):
|
||||||
|
let storageProof = toTrieProof(sProof)
|
||||||
|
builder.buildContractTrieNodeOffer(address, slotHash, storageProof, accountProof)
|
||||||
|
|
||||||
|
let code = builder.worldState.getUpdatedBytecode(address)
|
||||||
|
if code.len() > 0:
|
||||||
|
builder.buildContractCodeOffer(address, code, accountProof)
|
||||||
|
|
||||||
|
proc getAccountTrieOffers*(builder: OffersBuilderRef): seq[AccountTrieOfferWithKey] =
|
||||||
|
builder.accountTrieOffers
|
||||||
|
|
||||||
|
proc getContractTrieOffers*(builder: OffersBuilderRef): seq[ContractTrieOfferWithKey] =
|
||||||
|
builder.contractTrieOffers
|
||||||
|
|
||||||
|
proc getContractCodeOffers*(builder: OffersBuilderRef): seq[ContractCodeOfferWithKey] =
|
||||||
|
builder.contractCodeOffers
|
|
@ -30,11 +30,16 @@ type
|
||||||
before*: StateValue
|
before*: StateValue
|
||||||
after*: StateValue
|
after*: StateValue
|
||||||
|
|
||||||
StateDiffRef* = ref object
|
SlotDiff* = tuple[slotKey: UInt256, slotValueDiff: StateValueDiff[UInt256]]
|
||||||
balances*: Table[EthAddress, StateValueDiff[UInt256]]
|
|
||||||
nonces*: Table[EthAddress, StateValueDiff[AccountNonce]]
|
AccountDiff* = object
|
||||||
storage*: Table[EthAddress, Table[UInt256, StateValueDiff[UInt256]]]
|
address*: EthAddress
|
||||||
code*: Table[EthAddress, StateValueDiff[Code]]
|
balanceDiff*: StateValueDiff[UInt256]
|
||||||
|
nonceDiff*: StateValueDiff[AccountNonce]
|
||||||
|
storageDiff*: seq[SlotDiff]
|
||||||
|
codeDiff*: StateValueDiff[Code]
|
||||||
|
|
||||||
|
TransactionDiff* = seq[AccountDiff]
|
||||||
|
|
||||||
proc toStateValue(T: type UInt256, hex: string): T {.raises: [ValueError].} =
|
proc toStateValue(T: type UInt256, hex: string): T {.raises: [ValueError].} =
|
||||||
UInt256.fromHex(hex)
|
UInt256.fromHex(hex)
|
||||||
|
@ -68,45 +73,49 @@ proc toStateValueDiff(
|
||||||
else:
|
else:
|
||||||
doAssert false # unreachable
|
doAssert false # unreachable
|
||||||
|
|
||||||
proc toStateDiff(stateDiffJson: JsonNode): StateDiffRef {.raises: [ValueError].} =
|
proc toTransactionDiff(
|
||||||
let stateDiff = StateDiffRef()
|
stateDiffJson: JsonNode
|
||||||
|
): TransactionDiff {.raises: [ValueError].} =
|
||||||
|
var txDiff = newSeqOfCap[AccountDiff](stateDiffJson.len())
|
||||||
|
|
||||||
for addrJson, accJson in stateDiffJson.pairs:
|
for addrJson, accJson in stateDiffJson:
|
||||||
let address = EthAddress.fromHex(addrJson)
|
let storageDiffJson = accJson["storage"]
|
||||||
|
var storageDiff = newSeqOfCap[SlotDiff](storageDiffJson.len())
|
||||||
|
|
||||||
stateDiff.balances[address] = toStateValueDiff(accJson["balance"], UInt256)
|
for slotKeyJson, slotValueJson in storageDiffJson:
|
||||||
stateDiff.nonces[address] = toStateValueDiff(accJson["nonce"], AccountNonce)
|
storageDiff.add(
|
||||||
stateDiff.code[address] = toStateValueDiff(accJson["code"], Code)
|
(UInt256.fromHex(slotKeyJson), toStateValueDiff(slotValueJson, UInt256))
|
||||||
|
)
|
||||||
|
|
||||||
let storageDiff = accJson["storage"]
|
let accountDiff = AccountDiff(
|
||||||
var accountStorage: Table[UInt256, StateValueDiff[UInt256]]
|
address: EthAddress.fromHex(addrJson),
|
||||||
|
balanceDiff: toStateValueDiff(accJson["balance"], UInt256),
|
||||||
|
nonceDiff: toStateValueDiff(accJson["nonce"], AccountNonce),
|
||||||
|
storageDiff: storageDiff,
|
||||||
|
codeDiff: toStateValueDiff(accJson["code"], Code),
|
||||||
|
)
|
||||||
|
txDiff.add(accountDiff)
|
||||||
|
|
||||||
for slotKeyJson, slotValueJson in storageDiff.pairs:
|
txDiff
|
||||||
let slotKey = UInt256.fromHex(slotKeyJson)
|
|
||||||
accountStorage[slotKey] = toStateValueDiff(slotValueJson, UInt256)
|
|
||||||
|
|
||||||
stateDiff.storage[address] = ensureMove(accountStorage)
|
proc toTransactionDiffs(
|
||||||
|
|
||||||
stateDiff
|
|
||||||
|
|
||||||
proc toStateDiffs(
|
|
||||||
blockTraceJson: JsonNode
|
blockTraceJson: JsonNode
|
||||||
): seq[StateDiffRef] {.raises: [ValueError].} =
|
): seq[TransactionDiff] {.raises: [ValueError].} =
|
||||||
var stateDiffs = newSeqOfCap[StateDiffRef](blockTraceJson.len())
|
var txDiffs = newSeqOfCap[TransactionDiff](blockTraceJson.len())
|
||||||
for blockTrace in blockTraceJson:
|
for blockTrace in blockTraceJson:
|
||||||
stateDiffs.add(blockTrace["stateDiff"].toStateDiff())
|
txDiffs.add(blockTrace["stateDiff"].toTransactionDiff())
|
||||||
|
|
||||||
stateDiffs
|
txDiffs
|
||||||
|
|
||||||
proc getStateDiffsByBlockNumber*(
|
proc getStateDiffsByBlockNumber*(
|
||||||
client: RpcClient, blockId: BlockIdentifier
|
client: RpcClient, blockId: BlockIdentifier
|
||||||
): Future[Result[seq[StateDiffRef], string]] {.async: (raises: []).} =
|
): Future[Result[seq[TransactionDiff], string]] {.async: (raises: []).} =
|
||||||
const traceOpts = @["stateDiff"]
|
const traceOpts = @["stateDiff"]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
let blockTraceJson = await client.trace_replayBlockTransactions(blockId, traceOpts)
|
let blockTraceJson = await client.trace_replayBlockTransactions(blockId, traceOpts)
|
||||||
if blockTraceJson.isNil:
|
if blockTraceJson.isNil:
|
||||||
return err("EL failed to provide requested state diff")
|
return err("EL failed to provide requested state diff")
|
||||||
ok(blockTraceJson.toStateDiffs())
|
ok(blockTraceJson.toTransactionDiffs())
|
||||||
except CatchableError as e:
|
except CatchableError as e:
|
||||||
return err("EL JSON-RPC trace_replayBlockTransactions failed: " & e.msg)
|
return err("EL JSON-RPC trace_replayBlockTransactions failed: " & e.msg)
|
||||||
|
|
|
@ -9,9 +9,9 @@
|
||||||
|
|
||||||
import
|
import
|
||||||
stint,
|
stint,
|
||||||
eth/[common, trie, trie/db],
|
eth/[common, trie, trie/db, trie/trie_defs],
|
||||||
eth/common/[eth_types, eth_types_rlp],
|
eth/common/[eth_types, eth_types_rlp],
|
||||||
../../../common/common_types,
|
../../../common/[common_types, common_utils],
|
||||||
./database
|
./database
|
||||||
|
|
||||||
# Account State definition
|
# Account State definition
|
||||||
|
@ -22,22 +22,30 @@ type AccountState* = ref object
|
||||||
code: seq[byte]
|
code: seq[byte]
|
||||||
codeUpdated: bool
|
codeUpdated: bool
|
||||||
|
|
||||||
proc init(T: type AccountState, account = newAccount()): T =
|
proc init(T: type AccountState, account = newAccount()): T {.inline.} =
|
||||||
T(account: account, codeUpdated: false)
|
T(account: account, codeUpdated: false)
|
||||||
|
|
||||||
proc setBalance*(accState: var AccountState, balance: UInt256) =
|
proc getBalance*(accState: AccountState): UInt256 {.inline.} =
|
||||||
|
accState.account.balance
|
||||||
|
|
||||||
|
proc getNonce*(accState: AccountState): AccountNonce {.inline.} =
|
||||||
|
accState.account.nonce
|
||||||
|
|
||||||
|
proc setBalance*(accState: var AccountState, balance: UInt256) {.inline.} =
|
||||||
accState.account.balance = balance
|
accState.account.balance = balance
|
||||||
|
|
||||||
proc addBalance*(accState: var AccountState, balance: UInt256) =
|
proc addBalance*(accState: var AccountState, balance: UInt256) {.inline.} =
|
||||||
accState.account.balance += balance
|
accState.account.balance += balance
|
||||||
|
|
||||||
proc setNonce*(accState: var AccountState, nonce: AccountNonce) =
|
proc setNonce*(accState: var AccountState, nonce: AccountNonce) {.inline.} =
|
||||||
accState.account.nonce = nonce
|
accState.account.nonce = nonce
|
||||||
|
|
||||||
proc setStorage*(accState: var AccountState, slotKey: UInt256, slotValue: UInt256) =
|
proc setStorage*(
|
||||||
|
accState: var AccountState, slotKey: UInt256, slotValue: UInt256
|
||||||
|
) {.inline.} =
|
||||||
accState.storageUpdates[slotKey] = slotValue
|
accState.storageUpdates[slotKey] = slotValue
|
||||||
|
|
||||||
proc deleteStorage*(accState: var AccountState, slotKey: UInt256) =
|
proc deleteStorage*(accState: var AccountState, slotKey: UInt256) {.inline.} =
|
||||||
# setting to zero has the effect of deleting the slot
|
# setting to zero has the effect of deleting the slot
|
||||||
accState.setStorage(slotKey, 0.u256)
|
accState.setStorage(slotKey, 0.u256)
|
||||||
|
|
||||||
|
@ -48,32 +56,59 @@ proc setCode*(accState: var AccountState, code: seq[byte]) =
|
||||||
# World State definition
|
# World State definition
|
||||||
|
|
||||||
type
|
type
|
||||||
AccountHash = KeccakHash
|
AddressHash* = KeccakHash
|
||||||
SlotKeyHash = KeccakHash
|
SlotKeyHash* = KeccakHash
|
||||||
|
|
||||||
WorldStateRef* = ref object
|
WorldStateRef* = ref object
|
||||||
|
db: DatabaseRef
|
||||||
accountsTrie: HexaryTrie
|
accountsTrie: HexaryTrie
|
||||||
storageTries: TableRef[AccountHash, HexaryTrie]
|
storageTries: TableRef[AddressHash, HexaryTrie]
|
||||||
storageDb: TrieDatabaseRef
|
storageDb: TrieDatabaseRef
|
||||||
bytecodeDb: TrieDatabaseRef # maps AccountHash -> seq[byte]
|
bytecodeDb: TrieDatabaseRef # maps AddressHash -> seq[byte]
|
||||||
|
preimagesDb: TrieDatabaseRef # maps AddressHash -> EthAddress
|
||||||
|
|
||||||
proc init*(T: type WorldStateRef, db: DatabaseRef): T =
|
proc init*(
|
||||||
|
T: type WorldStateRef, db: DatabaseRef, accountsTrieRoot: KeccakHash = emptyRlpHash
|
||||||
|
): T =
|
||||||
WorldStateRef(
|
WorldStateRef(
|
||||||
accountsTrie: initHexaryTrie(db.getAccountsBackend(), isPruning = false),
|
db: db,
|
||||||
storageTries: newTable[AccountHash, HexaryTrie](),
|
accountsTrie:
|
||||||
|
if accountsTrieRoot == emptyRlpHash:
|
||||||
|
initHexaryTrie(db.getAccountsBackend(), isPruning = false)
|
||||||
|
else:
|
||||||
|
initHexaryTrie(db.getAccountsBackend(), accountsTrieRoot, isPruning = false),
|
||||||
|
storageTries: newTable[AddressHash, HexaryTrie](),
|
||||||
storageDb: db.getStorageBackend(),
|
storageDb: db.getStorageBackend(),
|
||||||
bytecodeDb: db.getBytecodeBackend(),
|
bytecodeDb: db.getBytecodeBackend(),
|
||||||
|
preimagesDb: db.getPreimagesBackend(),
|
||||||
)
|
)
|
||||||
|
|
||||||
template stateRoot*(state: WorldStateRef): KeccakHash =
|
proc stateRoot*(state: WorldStateRef): KeccakHash {.inline.} =
|
||||||
state.accountsTrie.rootHash()
|
state.accountsTrie.rootHash()
|
||||||
|
|
||||||
template toAccountKey(address: EthAddress): AccountHash =
|
proc toAccountKey*(address: EthAddress): AddressHash {.inline.} =
|
||||||
keccakHash(address)
|
keccakHash(address)
|
||||||
|
|
||||||
template toStorageKey(slotKey: UInt256): SlotKeyHash =
|
proc toStorageKey*(slotKey: UInt256): SlotKeyHash {.inline.} =
|
||||||
keccakHash(toBytesBE(slotKey))
|
keccakHash(toBytesBE(slotKey))
|
||||||
|
|
||||||
|
proc getAccountPreimage(state: WorldStateRef, accountKey: AddressHash): EthAddress =
|
||||||
|
doAssert(
|
||||||
|
state.preimagesDb.contains(rlp.encode(accountKey)),
|
||||||
|
"No account preimage with address hash: " & $accountKey,
|
||||||
|
)
|
||||||
|
let addressBytes = state.preimagesDb.get(rlp.encode(accountKey))
|
||||||
|
|
||||||
|
try:
|
||||||
|
rlp.decode(addressBytes, EthAddress)
|
||||||
|
except RlpError as e:
|
||||||
|
raiseAssert(e.msg) # Should never happen
|
||||||
|
|
||||||
|
proc setAccountPreimage(
|
||||||
|
state: WorldStateRef, accountKey: AddressHash, address: EthAddress
|
||||||
|
) =
|
||||||
|
state.preimagesDb.put(rlp.encode(accountKey), rlp.encode(address))
|
||||||
|
|
||||||
proc getAccount*(state: WorldStateRef, address: EthAddress): AccountState =
|
proc getAccount*(state: WorldStateRef, address: EthAddress): AccountState =
|
||||||
let accountKey = toAccountKey(address)
|
let accountKey = toAccountKey(address)
|
||||||
|
|
||||||
|
@ -88,11 +123,17 @@ proc getAccount*(state: WorldStateRef, address: EthAddress): AccountState =
|
||||||
|
|
||||||
proc setAccount*(state: WorldStateRef, address: EthAddress, accState: AccountState) =
|
proc setAccount*(state: WorldStateRef, address: EthAddress, accState: AccountState) =
|
||||||
let accountKey = toAccountKey(address)
|
let accountKey = toAccountKey(address)
|
||||||
|
state.setAccountPreimage(accountKey, address)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not state.storageTries.contains(accountKey):
|
if not state.storageTries.contains(accountKey):
|
||||||
|
if accState.account.storageRoot == EMPTY_ROOT_HASH:
|
||||||
state.storageTries[accountKey] =
|
state.storageTries[accountKey] =
|
||||||
initHexaryTrie(state.storageDb, isPruning = false)
|
initHexaryTrie(state.storageDb, isPruning = false)
|
||||||
|
else:
|
||||||
|
state.storageTries[accountKey] = initHexaryTrie(
|
||||||
|
state.storageDb, accState.account.storageRoot, isPruning = false
|
||||||
|
)
|
||||||
|
|
||||||
var storageTrie = state.storageTries.getOrDefault(accountKey)
|
var storageTrie = state.storageTries.getOrDefault(accountKey)
|
||||||
for k, v in accState.storageUpdates:
|
for k, v in accState.storageUpdates:
|
||||||
|
@ -123,3 +164,41 @@ proc deleteAccount*(state: WorldStateRef, address: EthAddress) =
|
||||||
state.bytecodeDb.del(accountKey.data)
|
state.bytecodeDb.del(accountKey.data)
|
||||||
except RlpError as e:
|
except RlpError as e:
|
||||||
raiseAssert(e.msg) # should never happen unless the database is corrupted
|
raiseAssert(e.msg) # should never happen unless the database is corrupted
|
||||||
|
|
||||||
|
# Returns the account proofs for all the updated accounts from the last transaction
|
||||||
|
iterator updatedAccountProofs*(state: WorldStateRef): (EthAddress, seq[seq[byte]]) =
|
||||||
|
let trie = initHexaryTrie(
|
||||||
|
state.db.getAccountsUpdatedCache(), state.stateRoot(), isPruning = false
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
for key in trie.keys():
|
||||||
|
if key.len() == 0:
|
||||||
|
continue # skip the empty node created on initialization
|
||||||
|
let address = state.getAccountPreimage(KeccakHash.fromBytes(key))
|
||||||
|
yield (address, trie.getBranch(key))
|
||||||
|
except RlpError as e:
|
||||||
|
raiseAssert(e.msg) # should never happen unless the database is corrupted
|
||||||
|
|
||||||
|
# Returns the storage proofs for the updated slots for the given account from the last transaction
|
||||||
|
iterator updatedStorageProofs*(
|
||||||
|
state: WorldStateRef, address: EthAddress
|
||||||
|
): (SlotKeyHash, seq[seq[byte]]) =
|
||||||
|
let accState = state.getAccount(address)
|
||||||
|
|
||||||
|
let trie = initHexaryTrie(
|
||||||
|
state.db.getStorageUpdatedCache(), accState.account.storageRoot, isPruning = false
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
for key in trie.keys():
|
||||||
|
if key.len() == 0:
|
||||||
|
continue # skip the empty node created on initialization
|
||||||
|
yield (KeccakHash.fromBytes(key), trie.getBranch(key))
|
||||||
|
except RlpError as e:
|
||||||
|
raiseAssert(e.msg) # should never happen unless the database is corrupted
|
||||||
|
|
||||||
|
proc getUpdatedBytecode*(
|
||||||
|
state: WorldStateRef, address: EthAddress
|
||||||
|
): seq[byte] {.inline.} =
|
||||||
|
state.db.getBytecodeUpdatedCache().get(toAccountKey(address).data)
|
||||||
|
|
|
@ -11,6 +11,7 @@ import
|
||||||
chronicles,
|
chronicles,
|
||||||
stint,
|
stint,
|
||||||
results,
|
results,
|
||||||
|
stew/byteutils,
|
||||||
eth/common/[eth_types, eth_types_rlp],
|
eth/common/[eth_types, eth_types_rlp],
|
||||||
../../../../nimbus/common/chain_config,
|
../../../../nimbus/common/chain_config,
|
||||||
./[state_diff, world_state]
|
./[state_diff, world_state]
|
||||||
|
@ -31,12 +32,14 @@ proc applyGenesisAccounts*(worldState: WorldStateRef, alloc: GenesisAlloc) =
|
||||||
|
|
||||||
worldState.setAccount(address, accState)
|
worldState.setAccount(address, accState)
|
||||||
|
|
||||||
proc applyStateDiff*(worldState: WorldStateRef, stateDiff: StateDiffRef) =
|
proc applyStateDiff*(worldState: WorldStateRef, txDiff: TransactionDiff) =
|
||||||
for address, balanceDiff in stateDiff.balances:
|
for accountDiff in txDiff:
|
||||||
let
|
let
|
||||||
nonceDiff = stateDiff.nonces.getOrDefault(address)
|
address = accountDiff.address
|
||||||
codeDiff = stateDiff.code.getOrDefault(address)
|
balanceDiff = accountDiff.balanceDiff
|
||||||
storageDiff = stateDiff.storage.getOrDefault(address)
|
nonceDiff = accountDiff.nonceDiff
|
||||||
|
codeDiff = accountDiff.codeDiff
|
||||||
|
storageDiff = accountDiff.storageDiff
|
||||||
|
|
||||||
var
|
var
|
||||||
deleteAccount = false
|
deleteAccount = false
|
||||||
|
@ -59,13 +62,13 @@ proc applyStateDiff*(worldState: WorldStateRef, stateDiff: StateDiffRef) =
|
||||||
elif codeDiff.kind == delete:
|
elif codeDiff.kind == delete:
|
||||||
doAssert deleteAccount == true
|
doAssert deleteAccount == true
|
||||||
|
|
||||||
for slotKey, slotDiff in storageDiff:
|
for (slotKey, slotValueDiff) in storageDiff:
|
||||||
if slotDiff.kind == create or slotDiff.kind == update:
|
if slotValueDiff.kind == create or slotValueDiff.kind == update:
|
||||||
if slotDiff.after == 0:
|
if slotValueDiff.after == 0:
|
||||||
accState.deleteStorage(slotKey)
|
accState.deleteStorage(slotKey)
|
||||||
else:
|
else:
|
||||||
accState.setStorage(slotKey, slotDiff.after)
|
accState.setStorage(slotKey, slotValueDiff.after)
|
||||||
elif slotDiff.kind == delete:
|
elif slotValueDiff.kind == delete:
|
||||||
accState.deleteStorage(slotKey)
|
accState.deleteStorage(slotKey)
|
||||||
|
|
||||||
if deleteAccount:
|
if deleteAccount:
|
||||||
|
@ -75,8 +78,8 @@ proc applyStateDiff*(worldState: WorldStateRef, stateDiff: StateDiffRef) =
|
||||||
|
|
||||||
proc applyBlockRewards*(
|
proc applyBlockRewards*(
|
||||||
worldState: WorldStateRef,
|
worldState: WorldStateRef,
|
||||||
minerData: tuple[miner: EthAddress, number: uint64],
|
minerData: tuple[miner: EthAddress, blockNumber: uint64],
|
||||||
uncleMinersData: openArray[tuple[miner: EthAddress, number: uint64]],
|
uncleMinersData: openArray[tuple[miner: EthAddress, blockNumber: uint64]],
|
||||||
) =
|
) =
|
||||||
const baseReward = u256(5) * pow(u256(10), 18)
|
const baseReward = u256(5) * pow(u256(10), 18)
|
||||||
|
|
||||||
|
@ -95,7 +98,145 @@ proc applyBlockRewards*(
|
||||||
let
|
let
|
||||||
uncleMinerAddress = EthAddress(uncleMinerData.miner)
|
uncleMinerAddress = EthAddress(uncleMinerData.miner)
|
||||||
uncleReward =
|
uncleReward =
|
||||||
(u256(8 + uncleMinerData.number - minerData.number) * baseReward) shr 3
|
(u256(8 + uncleMinerData.blockNumber - minerData.blockNumber) * baseReward) shr 3
|
||||||
var accState = worldState.getAccount(uncleMinerAddress)
|
var accState = worldState.getAccount(uncleMinerAddress)
|
||||||
accState.addBalance(uncleReward)
|
accState.addBalance(uncleReward)
|
||||||
worldState.setAccount(uncleMinerAddress, accState)
|
worldState.setAccount(uncleMinerAddress, accState)
|
||||||
|
|
||||||
|
const
|
||||||
|
DAORefundContract: EthAddress =
|
||||||
|
hexToByteArray[20]("0xbf4ed7b27f1d666546e30d74d50d173d20bca754")
|
||||||
|
|
||||||
|
DAODrainList = [
|
||||||
|
hexToByteArray[20]("0xd4fe7bc31cedb7bfb8a345f31e668033056b2728"),
|
||||||
|
hexToByteArray[20]("0xb3fb0e5aba0e20e5c49d252dfd30e102b171a425"),
|
||||||
|
hexToByteArray[20]("0x2c19c7f9ae8b751e37aeb2d93a699722395ae18f"),
|
||||||
|
hexToByteArray[20]("0xecd135fa4f61a655311e86238c92adcd779555d2"),
|
||||||
|
hexToByteArray[20]("0x1975bd06d486162d5dc297798dfc41edd5d160a7"),
|
||||||
|
hexToByteArray[20]("0xa3acf3a1e16b1d7c315e23510fdd7847b48234f6"),
|
||||||
|
hexToByteArray[20]("0x319f70bab6845585f412ec7724b744fec6095c85"),
|
||||||
|
hexToByteArray[20]("0x06706dd3f2c9abf0a21ddcc6941d9b86f0596936"),
|
||||||
|
hexToByteArray[20]("0x5c8536898fbb74fc7445814902fd08422eac56d0"),
|
||||||
|
hexToByteArray[20]("0x6966ab0d485353095148a2155858910e0965b6f9"),
|
||||||
|
hexToByteArray[20]("0x779543a0491a837ca36ce8c635d6154e3c4911a6"),
|
||||||
|
hexToByteArray[20]("0x2a5ed960395e2a49b1c758cef4aa15213cfd874c"),
|
||||||
|
hexToByteArray[20]("0x5c6e67ccd5849c0d29219c4f95f1a7a93b3f5dc5"),
|
||||||
|
hexToByteArray[20]("0x9c50426be05db97f5d64fc54bf89eff947f0a321"),
|
||||||
|
hexToByteArray[20]("0x200450f06520bdd6c527622a273333384d870efb"),
|
||||||
|
hexToByteArray[20]("0xbe8539bfe837b67d1282b2b1d61c3f723966f049"),
|
||||||
|
hexToByteArray[20]("0x6b0c4d41ba9ab8d8cfb5d379c69a612f2ced8ecb"),
|
||||||
|
hexToByteArray[20]("0xf1385fb24aad0cd7432824085e42aff90886fef5"),
|
||||||
|
hexToByteArray[20]("0xd1ac8b1ef1b69ff51d1d401a476e7e612414f091"),
|
||||||
|
hexToByteArray[20]("0x8163e7fb499e90f8544ea62bbf80d21cd26d9efd"),
|
||||||
|
hexToByteArray[20]("0x51e0ddd9998364a2eb38588679f0d2c42653e4a6"),
|
||||||
|
hexToByteArray[20]("0x627a0a960c079c21c34f7612d5d230e01b4ad4c7"),
|
||||||
|
hexToByteArray[20]("0xf0b1aa0eb660754448a7937c022e30aa692fe0c5"),
|
||||||
|
hexToByteArray[20]("0x24c4d950dfd4dd1902bbed3508144a54542bba94"),
|
||||||
|
hexToByteArray[20]("0x9f27daea7aca0aa0446220b98d028715e3bc803d"),
|
||||||
|
hexToByteArray[20]("0xa5dc5acd6a7968a4554d89d65e59b7fd3bff0f90"),
|
||||||
|
hexToByteArray[20]("0xd9aef3a1e38a39c16b31d1ace71bca8ef58d315b"),
|
||||||
|
hexToByteArray[20]("0x63ed5a272de2f6d968408b4acb9024f4cc208ebf"),
|
||||||
|
hexToByteArray[20]("0x6f6704e5a10332af6672e50b3d9754dc460dfa4d"),
|
||||||
|
hexToByteArray[20]("0x77ca7b50b6cd7e2f3fa008e24ab793fd56cb15f6"),
|
||||||
|
hexToByteArray[20]("0x492ea3bb0f3315521c31f273e565b868fc090f17"),
|
||||||
|
hexToByteArray[20]("0x0ff30d6de14a8224aa97b78aea5388d1c51c1f00"),
|
||||||
|
hexToByteArray[20]("0x9ea779f907f0b315b364b0cfc39a0fde5b02a416"),
|
||||||
|
hexToByteArray[20]("0xceaeb481747ca6c540a000c1f3641f8cef161fa7"),
|
||||||
|
hexToByteArray[20]("0xcc34673c6c40e791051898567a1222daf90be287"),
|
||||||
|
hexToByteArray[20]("0x579a80d909f346fbfb1189493f521d7f48d52238"),
|
||||||
|
hexToByteArray[20]("0xe308bd1ac5fda103967359b2712dd89deffb7973"),
|
||||||
|
hexToByteArray[20]("0x4cb31628079fb14e4bc3cd5e30c2f7489b00960c"),
|
||||||
|
hexToByteArray[20]("0xac1ecab32727358dba8962a0f3b261731aad9723"),
|
||||||
|
hexToByteArray[20]("0x4fd6ace747f06ece9c49699c7cabc62d02211f75"),
|
||||||
|
hexToByteArray[20]("0x440c59b325d2997a134c2c7c60a8c61611212bad"),
|
||||||
|
hexToByteArray[20]("0x4486a3d68fac6967006d7a517b889fd3f98c102b"),
|
||||||
|
hexToByteArray[20]("0x9c15b54878ba618f494b38f0ae7443db6af648ba"),
|
||||||
|
hexToByteArray[20]("0x27b137a85656544b1ccb5a0f2e561a5703c6a68f"),
|
||||||
|
hexToByteArray[20]("0x21c7fdb9ed8d291d79ffd82eb2c4356ec0d81241"),
|
||||||
|
hexToByteArray[20]("0x23b75c2f6791eef49c69684db4c6c1f93bf49a50"),
|
||||||
|
hexToByteArray[20]("0x1ca6abd14d30affe533b24d7a21bff4c2d5e1f3b"),
|
||||||
|
hexToByteArray[20]("0xb9637156d330c0d605a791f1c31ba5890582fe1c"),
|
||||||
|
hexToByteArray[20]("0x6131c42fa982e56929107413a9d526fd99405560"),
|
||||||
|
hexToByteArray[20]("0x1591fc0f688c81fbeb17f5426a162a7024d430c2"),
|
||||||
|
hexToByteArray[20]("0x542a9515200d14b68e934e9830d91645a980dd7a"),
|
||||||
|
hexToByteArray[20]("0xc4bbd073882dd2add2424cf47d35213405b01324"),
|
||||||
|
hexToByteArray[20]("0x782495b7b3355efb2833d56ecb34dc22ad7dfcc4"),
|
||||||
|
hexToByteArray[20]("0x58b95c9a9d5d26825e70a82b6adb139d3fd829eb"),
|
||||||
|
hexToByteArray[20]("0x3ba4d81db016dc2890c81f3acec2454bff5aada5"),
|
||||||
|
hexToByteArray[20]("0xb52042c8ca3f8aa246fa79c3feaa3d959347c0ab"),
|
||||||
|
hexToByteArray[20]("0xe4ae1efdfc53b73893af49113d8694a057b9c0d1"),
|
||||||
|
hexToByteArray[20]("0x3c02a7bc0391e86d91b7d144e61c2c01a25a79c5"),
|
||||||
|
hexToByteArray[20]("0x0737a6b837f97f46ebade41b9bc3e1c509c85c53"),
|
||||||
|
hexToByteArray[20]("0x97f43a37f595ab5dd318fb46e7a155eae057317a"),
|
||||||
|
hexToByteArray[20]("0x52c5317c848ba20c7504cb2c8052abd1fde29d03"),
|
||||||
|
hexToByteArray[20]("0x4863226780fe7c0356454236d3b1c8792785748d"),
|
||||||
|
hexToByteArray[20]("0x5d2b2e6fcbe3b11d26b525e085ff818dae332479"),
|
||||||
|
hexToByteArray[20]("0x5f9f3392e9f62f63b8eac0beb55541fc8627f42c"),
|
||||||
|
hexToByteArray[20]("0x057b56736d32b86616a10f619859c6cd6f59092a"),
|
||||||
|
hexToByteArray[20]("0x9aa008f65de0b923a2a4f02012ad034a5e2e2192"),
|
||||||
|
hexToByteArray[20]("0x304a554a310c7e546dfe434669c62820b7d83490"),
|
||||||
|
hexToByteArray[20]("0x914d1b8b43e92723e64fd0a06f5bdb8dd9b10c79"),
|
||||||
|
hexToByteArray[20]("0x4deb0033bb26bc534b197e61d19e0733e5679784"),
|
||||||
|
hexToByteArray[20]("0x07f5c1e1bc2c93e0402f23341973a0e043f7bf8a"),
|
||||||
|
hexToByteArray[20]("0x35a051a0010aba705c9008d7a7eff6fb88f6ea7b"),
|
||||||
|
hexToByteArray[20]("0x4fa802324e929786dbda3b8820dc7834e9134a2a"),
|
||||||
|
hexToByteArray[20]("0x9da397b9e80755301a3b32173283a91c0ef6c87e"),
|
||||||
|
hexToByteArray[20]("0x8d9edb3054ce5c5774a420ac37ebae0ac02343c6"),
|
||||||
|
hexToByteArray[20]("0x0101f3be8ebb4bbd39a2e3b9a3639d4259832fd9"),
|
||||||
|
hexToByteArray[20]("0x5dc28b15dffed94048d73806ce4b7a4612a1d48f"),
|
||||||
|
hexToByteArray[20]("0xbcf899e6c7d9d5a215ab1e3444c86806fa854c76"),
|
||||||
|
hexToByteArray[20]("0x12e626b0eebfe86a56d633b9864e389b45dcb260"),
|
||||||
|
hexToByteArray[20]("0xa2f1ccba9395d7fcb155bba8bc92db9bafaeade7"),
|
||||||
|
hexToByteArray[20]("0xec8e57756626fdc07c63ad2eafbd28d08e7b0ca5"),
|
||||||
|
hexToByteArray[20]("0xd164b088bd9108b60d0ca3751da4bceb207b0782"),
|
||||||
|
hexToByteArray[20]("0x6231b6d0d5e77fe001c2a460bd9584fee60d409b"),
|
||||||
|
hexToByteArray[20]("0x1cba23d343a983e9b5cfd19496b9a9701ada385f"),
|
||||||
|
hexToByteArray[20]("0xa82f360a8d3455c5c41366975bde739c37bfeb8a"),
|
||||||
|
hexToByteArray[20]("0x9fcd2deaff372a39cc679d5c5e4de7bafb0b1339"),
|
||||||
|
hexToByteArray[20]("0x005f5cee7a43331d5a3d3eec71305925a62f34b6"),
|
||||||
|
hexToByteArray[20]("0x0e0da70933f4c7849fc0d203f5d1d43b9ae4532d"),
|
||||||
|
hexToByteArray[20]("0xd131637d5275fd1a68a3200f4ad25c71a2a9522e"),
|
||||||
|
hexToByteArray[20]("0xbc07118b9ac290e4622f5e77a0853539789effbe"),
|
||||||
|
hexToByteArray[20]("0x47e7aa56d6bdf3f36be34619660de61275420af8"),
|
||||||
|
hexToByteArray[20]("0xacd87e28b0c9d1254e868b81cba4cc20d9a32225"),
|
||||||
|
hexToByteArray[20]("0xadf80daec7ba8dcf15392f1ac611fff65d94f880"),
|
||||||
|
hexToByteArray[20]("0x5524c55fb03cf21f549444ccbecb664d0acad706"),
|
||||||
|
hexToByteArray[20]("0x40b803a9abce16f50f36a77ba41180eb90023925"),
|
||||||
|
hexToByteArray[20]("0xfe24cdd8648121a43a7c86d289be4dd2951ed49f"),
|
||||||
|
hexToByteArray[20]("0x17802f43a0137c506ba92291391a8a8f207f487d"),
|
||||||
|
hexToByteArray[20]("0x253488078a4edf4d6f42f113d1e62836a942cf1a"),
|
||||||
|
hexToByteArray[20]("0x86af3e9626fce1957c82e88cbf04ddf3a2ed7915"),
|
||||||
|
hexToByteArray[20]("0xb136707642a4ea12fb4bae820f03d2562ebff487"),
|
||||||
|
hexToByteArray[20]("0xdbe9b615a3ae8709af8b93336ce9b477e4ac0940"),
|
||||||
|
hexToByteArray[20]("0xf14c14075d6c4ed84b86798af0956deef67365b5"),
|
||||||
|
hexToByteArray[20]("0xca544e5c4687d109611d0f8f928b53a25af72448"),
|
||||||
|
hexToByteArray[20]("0xaeeb8ff27288bdabc0fa5ebb731b6f409507516c"),
|
||||||
|
hexToByteArray[20]("0xcbb9d3703e651b0d496cdefb8b92c25aeb2171f7"),
|
||||||
|
hexToByteArray[20]("0x6d87578288b6cb5549d5076a207456a1f6a63dc0"),
|
||||||
|
hexToByteArray[20]("0xb2c6f0dfbb716ac562e2d85d6cb2f8d5ee87603e"),
|
||||||
|
hexToByteArray[20]("0xaccc230e8a6e5be9160b8cdf2864dd2a001c28b6"),
|
||||||
|
hexToByteArray[20]("0x2b3455ec7fedf16e646268bf88846bd7a2319bb2"),
|
||||||
|
hexToByteArray[20]("0x4613f3bca5c44ea06337a9e439fbc6d42e501d0a"),
|
||||||
|
hexToByteArray[20]("0xd343b217de44030afaa275f54d31a9317c7f441e"),
|
||||||
|
hexToByteArray[20]("0x84ef4b2357079cd7a7c69fd7a37cd0609a679106"),
|
||||||
|
hexToByteArray[20]("0xda2fef9e4a3230988ff17df2165440f37e8b1708"),
|
||||||
|
hexToByteArray[20]("0xf4c64518ea10f995918a454158c6b61407ea345c"),
|
||||||
|
hexToByteArray[20]("0x7602b46df5390e432ef1c307d4f2c9ff6d65cc97"),
|
||||||
|
hexToByteArray[20]("0xbb9bc244d798123fde783fcc1c72d3bb8c189413"),
|
||||||
|
hexToByteArray[20]("0x807640a13483f8ac783c557fcdf27be11ea4ac7a"),
|
||||||
|
]
|
||||||
|
|
||||||
|
# ApplyDAOHardFork modifies the state database according to the DAO hard-fork
|
||||||
|
# rules, transferring all balances of a set of DAO accounts to a single refund
|
||||||
|
# contract.
|
||||||
|
proc applyDAOHardFork*(worldState: WorldStateRef) =
|
||||||
|
# Move every DAO account and extra-balance account funds into the refund contract
|
||||||
|
var toAccount = worldState.getAccount(DAORefundContract)
|
||||||
|
|
||||||
|
for address in DAODrainList:
|
||||||
|
var fromAccount = worldState.getAccount(address)
|
||||||
|
toAccount.addBalance(fromAccount.getBalance())
|
||||||
|
fromAccount.setBalance(0.u256)
|
||||||
|
worldState.setAccount(address, fromAccount)
|
||||||
|
|
||||||
|
worldState.setAccount(DAORefundContract, toAccount)
|
||||||
|
|
Loading…
Reference in New Issue