# Nimbus # Copyright (c) 2023-2024 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or # http://www.apache.org/licenses/LICENSE-2.0) # * MIT license ([LICENSE-MIT](LICENSE-MIT) or # http://opensource.org/licenses/MIT) # at your option. This file may not be copied, modified, or distributed except # according to those terms. ## Rewrite of `core_apps.nim` using the new `CoreDb` API. The original ## `core_apps.nim` was renamed `core_apps_legacy.nim`. {.push raises: [].} import std/[algorithm, sequtils], chronicles, eth/[common, rlp], stew/byteutils, "../.."/[errors, constants], ".."/[aristo, storage_types], ./backend/aristo_db, "."/base logScope: topics = "core_db-apps" type TransactionKey = tuple blockNumber: BlockNumber index: uint # ------------------------------------------------------------------------------ # Forward declarations # ------------------------------------------------------------------------------ proc getBlockHeader*( db: CoreDbRef; n: BlockNumber; output: var BlockHeader; ): bool {.gcsafe.} proc getBlockHeader*( db: CoreDbRef, blockHash: Hash256; ): BlockHeader {.gcsafe, raises: [BlockNotFound].} proc getBlockHash*( db: CoreDbRef; n: BlockNumber; output: var Hash256; ): bool {.gcsafe.} proc addBlockNumberToHashLookup*( db: CoreDbRef; blockNumber: BlockNumber; blockHash: Hash256; ) {.gcsafe.} proc getBlockHeader*( db: CoreDbRef; blockHash: Hash256; output: var BlockHeader; ): bool {.gcsafe.} proc getCanonicalHeaderHash*(db: CoreDbRef): Opt[Hash256] {.gcsafe.} # ------------------------------------------------------------------------------ # Private helpers # ------------------------------------------------------------------------------ template logTxt(info: static[string]): static[string] = "Core apps " & info template discardRlpException(info: static[string]; code: untyped) = try: code except RlpError as e: warn logTxt info, error=($e.name), msg=e.msg # ------------------------------------------------------------------------------ # Private iterators # ------------------------------------------------------------------------------ iterator findNewAncestors( db: CoreDbRef; header: BlockHeader; ): BlockHeader = ## Returns the chain leading up from the given header until the first ## ancestor it has in common with our canonical chain. var h = header var orig: BlockHeader while true: if db.getBlockHeader(h.number, orig) and orig.rlpHash == h.rlpHash: break yield h if h.parentHash == GENESIS_PARENT_HASH: break else: if not db.getBlockHeader(h.parentHash, h): warn "Could not find parent while iterating", hash = h.parentHash break # ------------------------------------------------------------------------------ # Public iterators # ------------------------------------------------------------------------------ iterator getBlockTransactionData*( db: CoreDbRef; transactionRoot: Hash256; ): Blob = const info = "getBlockTransactionData()" block body: if transactionRoot == EMPTY_ROOT_HASH: break body let transactionDb = db.ctx.getColumn CtTxs state = transactionDb.state(updateOk=true).valueOr: raiseAssert info & ": " & $$error if state != transactionRoot: warn logTxt info, transactionRoot, state, error="state mismatch" break body var transactionIdx = 0'u64 while true: let transactionKey = rlp.encode(transactionIdx) let data = transactionDb.fetch(transactionKey).valueOr: if error.error != MptNotFound: warn logTxt info, transactionRoot, transactionKey, action="fetch()", error=($$error) break body yield data inc transactionIdx iterator getBlockTransactions*( db: CoreDbRef; header: BlockHeader; ): Transaction = for encodedTx in db.getBlockTransactionData(header.txRoot): try: yield rlp.decode(encodedTx, Transaction) except RlpError as exc: warn "Cannot decode database transaction", data = toHex(encodedTx), error = exc.msg iterator getBlockTransactionHashes*( db: CoreDbRef; blockHeader: BlockHeader; ): Hash256 = ## Returns an iterable of the transaction hashes from th block specified ## by the given block header. for encodedTx in db.getBlockTransactionData(blockHeader.txRoot): yield keccakHash(encodedTx) iterator getWithdrawalsData*( db: CoreDbRef; withdrawalsRoot: Hash256; ): Blob = const info = "getWithdrawalsData()" block body: if withdrawalsRoot == EMPTY_ROOT_HASH: break body let wddb = db.ctx.getColumn CtWithdrawals state = wddb.state(updateOk=true).valueOr: raiseAssert info & ": " & $$error if state != withdrawalsRoot: warn logTxt info, withdrawalsRoot, state, error="state mismatch" break body var idx = 0 while true: let wdKey = rlp.encode(idx.uint) let data = wddb.fetch(wdKey).valueOr: if error.error != MptNotFound: warn logTxt "getWithdrawalsData()", withdrawalsRoot, wdKey, action="fetch()", error=($$error) break body yield data inc idx iterator getReceipts*( db: CoreDbRef; receiptsRoot: Hash256; ): Receipt {.gcsafe, raises: [RlpError].} = const info = "getReceipts()" block body: if receiptsRoot == EMPTY_ROOT_HASH: break body let receiptDb = db.ctx.getColumn CtReceipts state = receiptDb.state(updateOk=true).valueOr: raiseAssert info & ": " & $$error if state != receiptsRoot: warn logTxt info, receiptsRoot, state, error="state mismatch" break body var receiptIdx = 0 while true: let receiptKey = rlp.encode(receiptIdx.uint) let receiptData = receiptDb.fetch(receiptKey).valueOr: if error.error != MptNotFound: warn logTxt "getWithdrawalsData()", receiptsRoot, receiptKey, action="hasKey()", error=($$error) break body yield rlp.decode(receiptData, Receipt) inc receiptIdx # ------------------------------------------------------------------------------ # Private helpers # ------------------------------------------------------------------------------ proc removeTransactionFromCanonicalChain( db: CoreDbRef; transactionHash: Hash256; ) = ## Removes the transaction specified by the given hash from the canonical ## chain. db.newKvt.del(transactionHashToBlockKey(transactionHash).toOpenArray).isOkOr: warn logTxt "removeTransactionFromCanonicalChain()", transactionHash, action="del()", error=($$error) proc setAsCanonicalChainHead( db: CoreDbRef; headerHash: Hash256; header: BlockHeader; ) = ## Sets the header as the canonical chain HEAD. # TODO This code handles reorgs - this should be moved elsewhere because we'll # be handling reorgs mainly in-memory if header.number == 0 or db.getCanonicalHeaderHash().valueOr(Hash256()) != header.parentHash: var newCanonicalHeaders = sequtils.toSeq(db.findNewAncestors(header)) reverse(newCanonicalHeaders) for h in newCanonicalHeaders: var oldHash: Hash256 if not db.getBlockHash(h.number, oldHash): break try: let oldHeader = db.getBlockHeader(oldHash) for txHash in db.getBlockTransactionHashes(oldHeader): db.removeTransactionFromCanonicalChain(txHash) # TODO re-add txn to internal pending pool (only if local sender) except BlockNotFound: warn "Could not load old header", oldHash for h in newCanonicalHeaders: # TODO don't recompute block hash db.addBlockNumberToHashLookup(h.number, h.blockHash) let canonicalHeadHash = canonicalHeadHashKey() db.newKvt.put(canonicalHeadHash.toOpenArray, rlp.encode(headerHash)).isOkOr: warn logTxt "setAsCanonicalChainHead()", canonicalHeadHash, action="put()", error=($$error) proc markCanonicalChain( db: CoreDbRef; header: BlockHeader; headerHash: Hash256; ): bool {.gcsafe, raises: [RlpError].} = ## mark this chain as canonical by adding block number to hash lookup ## down to forking point var currHash = headerHash currHeader = header # mark current header as canonical let kvt = db.newKvt() key = blockNumberToHashKey(currHeader.number) kvt.put(key.toOpenArray, rlp.encode(currHash)).isOkOr: warn logTxt "markCanonicalChain()", key, action="put()", error=($$error) return false # it is a genesis block, done if currHeader.parentHash == Hash256(): return true # mark ancestor blocks as canonical too currHash = currHeader.parentHash if not db.getBlockHeader(currHeader.parentHash, currHeader): return false while currHash != Hash256(): let key = blockNumberToHashKey(currHeader.number) let data = kvt.getOrEmpty(key.toOpenArray).valueOr: warn logTxt "markCanonicalChain()", key, action="get()", error=($$error) return false if data.len == 0: # not marked, mark it kvt.put(key.toOpenArray, rlp.encode(currHash)).isOkOr: warn logTxt "markCanonicalChain()", key, action="put()", error=($$error) elif rlp.decode(data, Hash256) != currHash: # replace prev chain kvt.put(key.toOpenArray, rlp.encode(currHash)).isOkOr: warn logTxt "markCanonicalChain()", key, action="put()", error=($$error) else: # forking point, done break if currHeader.parentHash == Hash256(): break currHash = currHeader.parentHash if not db.getBlockHeader(currHeader.parentHash, currHeader): return false return true # ------------------------------------------------------------------------------ # Public functions # ------------------------------------------------------------------------------ proc exists*(db: CoreDbRef, hash: Hash256): bool = db.newKvt().hasKey(hash.data).valueOr: warn logTxt "exisis()", hash, action="hasKey()", error=($$error) return false proc getSavedStateBlockNumber*( db: CoreDbRef; relax = false; ): BlockNumber = ## Returns the block number registered when the database was last time ## updated, or `BlockNumber(0)` if there was no updata found. ## ## This function verifies the state consistency of the database and throws ## an assert exception if that fails. So the function will only apply to a ## finalised (aka hashified) database state. For an an opportunistic use, ## the `relax` argument can be set `true` so this function also returns ## zero if the state consistency check fails. ## const info = "getSavedStateBlockNumber(): " var header: BlockHeader let st = db.ctx.getColumn(CtGeneric).backend.toAristoSavedStateBlockNumber() if db.getBlockHeader(st.blockNumber, header): let state = db.ctx.getAccounts.state.valueOr: raiseAssert info & $$error if state == header.stateRoot: return st.blockNumber if not relax: raiseAssert info & ": state mismatch at " & "#" & $st.blockNumber proc getBlockHeader*( db: CoreDbRef; blockHash: Hash256; output: var BlockHeader; ): bool = const info = "getBlockHeader()" let data = db.newKvt().get(genericHashKey(blockHash).toOpenArray).valueOr: if error.error != KvtNotFound: warn logTxt info, blockHash, action="get()", error=($$error) return false discardRlpException info: output = rlp.decode(data, BlockHeader) return true proc getBlockHeader*( db: CoreDbRef, blockHash: Hash256; ): BlockHeader = ## Returns the requested block header as specified by block hash. ## ## Raises BlockNotFound if it is not present in the db. if not db.getBlockHeader(blockHash, result): raise newException( BlockNotFound, "No block with hash " & blockHash.data.toHex) proc getHash( db: CoreDbRef; key: DbKey; ): Opt[Hash256] = let data = db.newKvt().get(key.toOpenArray).valueOr: if error.error != KvtNotFound: warn logTxt "getHash()", key, action="get()", error=($$error) return Opt.none(Hash256) try: Opt.some(rlp.decode(data, Hash256)) except RlpError as exc: warn logTxt "getHash()", key, action="rlp.decode()", error=exc.msg Opt.none(Hash256) proc getCanonicalHeaderHash*(db: CoreDbRef): Opt[Hash256] = db.getHash(canonicalHeadHashKey()) proc getCanonicalHead*( db: CoreDbRef; output: var BlockHeader; ): bool = let headHash = db.getCanonicalHeaderHash().valueOr: return false discardRlpException "getCanonicalHead()": if db.getBlockHeader(headHash, output): return true proc getCanonicalHead*( db: CoreDbRef; ): BlockHeader {.gcsafe, raises: [EVMError].} = if not db.getCanonicalHead result: raise newException( CanonicalHeadNotFound, "No canonical head set for this chain") proc getBlockHash*( db: CoreDbRef; n: BlockNumber; output: var Hash256; ): bool = ## Return the block hash for the given block number. output = db.getHash(blockNumberToHashKey(n)).valueOr: return false true proc getBlockHash*( db: CoreDbRef; n: BlockNumber; ): Hash256 {.gcsafe, raises: [BlockNotFound].} = ## Return the block hash for the given block number. if not db.getBlockHash(n, result): raise newException(BlockNotFound, "No block hash for number " & $n) proc getHeadBlockHash*(db: CoreDbRef): Hash256 = db.getHash(canonicalHeadHashKey()).valueOr(Hash256()) proc getBlockHeader*( db: CoreDbRef; n: BlockNumber; output: var BlockHeader; ): bool = ## Returns the block header with the given number in the canonical chain. var blockHash: Hash256 if db.getBlockHash(n, blockHash): result = db.getBlockHeader(blockHash, output) proc getBlockHeaderWithHash*( db: CoreDbRef; n: BlockNumber; ): Opt[(BlockHeader, Hash256)] = ## Returns the block header and its hash, with the given number in the ## canonical chain. Hash is returned to avoid recomputing it var hash: Hash256 if db.getBlockHash(n, hash): # Note: this will throw if header is not present. var header: BlockHeader if db.getBlockHeader(hash, header): return Opt.some((header, hash)) else: # this should not happen, but if it happen lets fail laudly as this means # something is super wrong raiseAssert("Corrupted database. Mapping number->hash present, without header in database") else: return Opt.none((BlockHeader, Hash256)) proc getBlockHeader*( db: CoreDbRef; n: BlockNumber; ): BlockHeader {.raises: [BlockNotFound].} = ## Returns the block header with the given number in the canonical chain. ## Raises BlockNotFound error if the block is not in the DB. db.getBlockHeader(db.getBlockHash(n)) proc getScore*( db: CoreDbRef; blockHash: Hash256; ): Opt[UInt256] = let data = db.newKvt() .get(blockHashToScoreKey(blockHash).toOpenArray).valueOr: if error.error != KvtNotFound: warn logTxt "getScore()", blockHash, action="get()", error=($$error) return Opt.none(UInt256) try: Opt.some(rlp.decode(data, UInt256)) except RlpError as exc: warn logTxt "getScore()", data = data.toHex(), error=exc.msg Opt.none(UInt256) proc setScore*(db: CoreDbRef; blockHash: Hash256, score: UInt256) = ## for testing purpose let scoreKey = blockHashToScoreKey blockHash db.newKvt.put(scoreKey.toOpenArray, rlp.encode(score)).isOkOr: warn logTxt "setScore()", scoreKey, action="put()", error=($$error) return proc getTd*(db: CoreDbRef; blockHash: Hash256, td: var UInt256): bool = td = db.getScore(blockHash).valueOr: return false true proc headTotalDifficulty*( db: CoreDbRef; ): UInt256 = let blockHash = db.getCanonicalHeaderHash().valueOr: return 0.u256 db.getScore(blockHash).valueOr(0.u256) proc getAncestorsHashes*( db: CoreDbRef; limit: BlockNumber; header: BlockHeader; ): seq[Hash256] {.gcsafe, raises: [BlockNotFound].} = var ancestorCount = min(header.number, limit) var h = header result = newSeq[Hash256](ancestorCount) while ancestorCount > 0: h = db.getBlockHeader(h.parentHash) result[ancestorCount - 1] = h.rlpHash dec ancestorCount proc addBlockNumberToHashLookup*( db: CoreDbRef; blockNumber: BlockNumber, blockHash: Hash256) = let blockNumberKey = blockNumberToHashKey(blockNumber) db.newKvt.put(blockNumberKey.toOpenArray, rlp.encode(blockHash)).isOkOr: warn logTxt "addBlockNumberToHashLookup()", blockNumberKey, action="put()", error=($$error) proc persistTransactions*( db: CoreDbRef; blockNumber: BlockNumber; transactions: openArray[Transaction]; ) = const info = "persistTransactions()" if transactions.len == 0: return let mpt = db.ctx.getColumn(CtTxs, clearData=true) kvt = db.newKvt() for idx, tx in transactions: let encodedKey = rlp.encode(idx.uint) encodedTx = rlp.encode(tx) txHash = keccakHash(encodedTx) blockKey = transactionHashToBlockKey(txHash) txKey: TransactionKey = (blockNumber, idx.uint) mpt.merge(encodedKey, encodedTx).isOkOr: warn logTxt info, idx, action="merge()", error=($$error) return kvt.put(blockKey.toOpenArray, rlp.encode(txKey)).isOkOr: trace logTxt info, blockKey, action="put()", error=($$error) return proc forgetHistory*( db: CoreDbRef; blockNum: BlockNumber; ): bool = ## Remove all data related to the block number argument `num`. This function ## returns `true`, if some history was available and deleted. var blockHash: Hash256 if db.getBlockHash(blockNum, blockHash): let kvt = db.newKvt() # delete blockNum->blockHash discard kvt.del(blockNumberToHashKey(blockNum).toOpenArray) result = true var header: BlockHeader if db.getBlockHeader(blockHash, header): # delete blockHash->header, stateRoot->blockNum discard kvt.del(genericHashKey(blockHash).toOpenArray) proc getTransaction*( db: CoreDbRef; txRoot: Hash256; txIndex: uint64; res: var Transaction; ): bool = const info = "getTransaction()" let clearOk = txRoot == EMPTY_ROOT_HASH mpt = db.ctx.getColumn(CtTxs, clearData=clearOk) if not clearOk: let state = mpt.state(updateOk=true).valueOr: raiseAssert info & ": " & $$error if state != txRoot: warn logTxt info, txRoot, state, error="state mismatch" return false let txData = mpt.fetch(rlp.encode(txIndex)).valueOr: if error.error != MptNotFound: warn logTxt info, txIndex, error=($$error) return false try: res = rlp.decode(txData, Transaction) except RlpError as e: warn logTxt info, txRoot, action="rlp.decode()", name=($e.name), msg=e.msg return false true proc getTransactionCount*( db: CoreDbRef; txRoot: Hash256; ): int = const info = "getTransactionCount()" let clearOk = txRoot == EMPTY_ROOT_HASH mpt = db.ctx.getColumn(CtTxs, clearData=clearOk) if not clearOk: let state = mpt.state(updateOk=true).valueOr: raiseAssert info & ": " & $$error if state != txRoot: warn logTxt info, txRoot, state, error="state mismatch" return 0 var txCount = 0 while true: let hasPath = mpt.hasPath(rlp.encode(txCount.uint)).valueOr: warn logTxt info, txCount, action="hasPath()", error=($$error) return 0 if hasPath: inc txCount else: return txCount doAssert(false, "unreachable") proc getUnclesCount*( db: CoreDbRef; ommersHash: Hash256; ): int {.gcsafe, raises: [RlpError].} = const info = "getUnclesCount()" if ommersHash != EMPTY_UNCLE_HASH: let encodedUncles = block: let key = genericHashKey(ommersHash) db.newKvt().get(key.toOpenArray).valueOr: if error.error == KvtNotFound: warn logTxt info, ommersHash, action="get()", `error`=($$error) return 0 return rlpFromBytes(encodedUncles).listLen proc getUncles*( db: CoreDbRef; ommersHash: Hash256; ): seq[BlockHeader] {.gcsafe, raises: [RlpError].} = const info = "getUncles()" if ommersHash != EMPTY_UNCLE_HASH: let encodedUncles = block: let key = genericHashKey(ommersHash) db.newKvt().get(key.toOpenArray).valueOr: if error.error == KvtNotFound: warn logTxt info, ommersHash, action="get()", `error`=($$error) return @[] return rlp.decode(encodedUncles, seq[BlockHeader]) proc persistWithdrawals*( db: CoreDbRef; withdrawals: openArray[Withdrawal]; ) = const info = "persistWithdrawals()" if withdrawals.len == 0: return let mpt = db.ctx.getColumn(CtWithdrawals, clearData=true) for idx, wd in withdrawals: mpt.merge(rlp.encode(idx.uint), rlp.encode(wd)).isOkOr: warn logTxt info, idx, error=($$error) return proc getWithdrawals*( db: CoreDbRef; withdrawalsRoot: Hash256; ): seq[Withdrawal] {.gcsafe, raises: [RlpError].} = for encodedWd in db.getWithdrawalsData(withdrawalsRoot): result.add(rlp.decode(encodedWd, Withdrawal)) proc getTransactions*( db: CoreDbRef; txRoot: Hash256; output: var seq[Transaction]) {.gcsafe, raises: [RlpError].} = for encodedTx in db.getBlockTransactionData(txRoot): output.add(rlp.decode(encodedTx, Transaction)) proc getTransactions*( db: CoreDbRef; txRoot: Hash256; ): seq[Transaction] {.gcsafe, raises: [RlpError].} = db.getTransactions(txRoot, result) proc getBlockBody*( db: CoreDbRef; header: BlockHeader; output: var BlockBody; ): bool {.gcsafe, raises: [RlpError].} = output.transactions = db.getTransactions(header.txRoot) output.uncles = db.getUncles(header.ommersHash) if header.withdrawalsRoot.isSome: output.withdrawals = Opt.some(db.getWithdrawals(header.withdrawalsRoot.get)) true proc getBlockBody*( db: CoreDbRef; blockHash: Hash256; output: var BlockBody; ): bool {.gcsafe, raises: [RlpError].} = var header: BlockHeader if db.getBlockHeader(blockHash, header): return db.getBlockBody(header, output) proc getBlockBody*( db: CoreDbRef; hash: Hash256; ): BlockBody {.gcsafe, raises: [RlpError,ValueError].} = if not db.getBlockBody(hash, result): raise newException(ValueError, "Error when retrieving block body") proc getEthBlock*( db: CoreDbRef; hash: Hash256; ): EthBlock {.gcsafe, raises: [BlockNotFound, RlpError,ValueError].} = var header = db.getBlockHeader(hash) blockBody = db.getBlockBody(hash) EthBlock.init(move(header), move(blockBody)) proc getEthBlock*( db: CoreDbRef; blockNumber: BlockNumber; ): EthBlock {.gcsafe, raises: [BlockNotFound, RlpError,ValueError].} = var header = db.getBlockHeader(blockNumber) headerHash = header.blockHash blockBody = db.getBlockBody(headerHash) EthBlock.init(move(header), move(blockBody)) proc getUncleHashes*( db: CoreDbRef; blockHashes: openArray[Hash256]; ): seq[Hash256] {.gcsafe, raises: [RlpError,ValueError].} = for blockHash in blockHashes: result &= db.getBlockBody(blockHash).uncles.mapIt(it.rlpHash) proc getUncleHashes*( db: CoreDbRef; header: BlockHeader; ): seq[Hash256] {.gcsafe, raises: [RlpError].} = if header.ommersHash != EMPTY_UNCLE_HASH: let key = genericHashKey(header.ommersHash) encodedUncles = db.newKvt().get(key.toOpenArray).valueOr: if error.error == KvtNotFound: warn logTxt "getUncleHashes()", ommersHash=header.ommersHash, action="get()", `error`=($$error) return @[] return rlp.decode(encodedUncles, seq[BlockHeader]).mapIt(it.rlpHash) proc getTransactionKey*( db: CoreDbRef; transactionHash: Hash256; ): tuple[blockNumber: BlockNumber, index: uint64] {.gcsafe, raises: [RlpError].} = let txKey = transactionHashToBlockKey(transactionHash) tx = db.newKvt().get(txKey.toOpenArray).valueOr: if error.error == KvtNotFound: warn logTxt "getTransactionKey()", transactionHash, action="get()", `error`=($$error) return (0.BlockNumber, 0) let key = rlp.decode(tx, TransactionKey) (key.blockNumber, key.index.uint64) proc headerExists*(db: CoreDbRef; blockHash: Hash256): bool = ## Returns True if the header with the given block hash is in our DB. db.newKvt().hasKey(genericHashKey(blockHash).toOpenArray).valueOr: warn logTxt "headerExists()", blockHash, action="get()", `error`=($$error) return false proc setHead*( db: CoreDbRef; blockHash: Hash256; ): bool {.gcsafe, raises: [RlpError].} = var header: BlockHeader if not db.getBlockHeader(blockHash, header): return false if not db.markCanonicalChain(header, blockHash): return false let canonicalHeadHash = canonicalHeadHashKey() db.newKvt.put(canonicalHeadHash.toOpenArray, rlp.encode(blockHash)).isOkOr: warn logTxt "setHead()", canonicalHeadHash, action="put()", error=($$error) return true proc setHead*( db: CoreDbRef; header: BlockHeader; writeHeader = false; ): bool {.gcsafe, raises: [RlpError].} = var headerHash = rlpHash(header) let kvt = db.newKvt() if writeHeader: kvt.put(genericHashKey(headerHash).toOpenArray, rlp.encode(header)).isOkOr: warn logTxt "setHead()", headerHash, action="put()", error=($$error) return false if not db.markCanonicalChain(header, headerHash): return false let canonicalHeadHash = canonicalHeadHashKey() kvt.put(canonicalHeadHash.toOpenArray, rlp.encode(headerHash)).isOkOr: warn logTxt "setHead()", canonicalHeadHash, action="put()", error=($$error) return false true proc persistReceipts*( db: CoreDbRef; receipts: openArray[Receipt]; ) = const info = "persistReceipts()" if receipts.len == 0: return let mpt = db.ctx.getColumn(CtReceipts, clearData=true) for idx, rec in receipts: mpt.merge(rlp.encode(idx.uint), rlp.encode(rec)).isOkOr: warn logTxt info, idx, action="merge()", error=($$error) proc getReceipts*( db: CoreDbRef; receiptsRoot: Hash256; ): seq[Receipt] {.gcsafe, raises: [RlpError].} = var receipts = newSeq[Receipt]() for r in db.getReceipts(receiptsRoot): receipts.add(r) return receipts proc persistScore*( db: CoreDbRef; blockHash: Hash256; score: UInt256 ): bool = let kvt = db.newKvt() scoreKey = blockHashToScoreKey(blockHash) kvt.put(scoreKey.toOpenArray, rlp.encode(score)).isOkOr: warn logTxt "persistHeader()", scoreKey, action="put()", `error`=($$error) return true proc persistHeader*( db: CoreDbRef; blockHash: Hash256; header: BlockHeader; startOfHistory = GENESIS_PARENT_HASH; ): bool = let kvt = db.newKvt() isStartOfHistory = header.parentHash == startOfHistory if not isStartOfHistory and not db.headerExists(header.parentHash): warn logTxt "persistHeaderWithoutSetHead()", blockHash, action="headerExists(parent)" return false kvt.put(genericHashKey(blockHash).toOpenArray, rlp.encode(header)).isOkOr: warn logTxt "persistHeaderWithoutSetHead()", blockHash, action="put()", `error`=($$error) return false let parentScore = if isStartOfHistory: 0.u256 else: db.getScore(header.parentHash).valueOr: # TODO it's slightly wrong to fail here and leave the block in the db, # but this code is going away soon enough return false score = parentScore + header.difficulty # After EIP-3675, difficulty is set to 0 but we still save the score for # each block to simplify totalDifficulty reporting # TODO get rid of this and store a single value if not db.persistScore(blockHash, score): return false db.addBlockNumberToHashLookup(header.number, blockHash) true proc persistHeader*( db: CoreDbRef; blockHash: Hash256; header: BlockHeader; forceCanonical: bool; startOfHistory = GENESIS_PARENT_HASH; ): bool = if not db.persistHeader(blockHash, header, startOfHistory): return false if not forceCanonical and header.parentHash != startOfHistory: let canonicalHash = db.getCanonicalHeaderHash().valueOr: return false canonScore = db.getScore(canonicalHash).valueOr: return false # TODO no need to load score from database _really_, but this code is # hopefully going away soon score = db.getScore(blockHash).valueOr: return false if score <= canonScore: return true db.setAsCanonicalChainHead(blockHash, header) true proc persistHeader*( db: CoreDbRef; header: BlockHeader; forceCanonical: bool; startOfHistory = GENESIS_PARENT_HASH; ): bool = let blockHash = header.blockHash db.persistHeader(blockHash, header, forceCanonical, startOfHistory) proc persistUncles*(db: CoreDbRef, uncles: openArray[BlockHeader]): Hash256 = ## Persists the list of uncles to the database. ## Returns the uncles hash. let enc = rlp.encode(uncles) result = keccakHash(enc) db.newKvt.put(genericHashKey(result).toOpenArray, enc).isOkOr: warn logTxt "persistUncles()", unclesHash=result, action="put()", `error`=($$error) return EMPTY_ROOT_HASH proc safeHeaderHash*(db: CoreDbRef): Hash256 = db.getHash(safeHashKey()).valueOr(Hash256()) proc safeHeaderHash*(db: CoreDbRef, headerHash: Hash256) = let safeHashKey = safeHashKey() db.newKvt.put(safeHashKey.toOpenArray, rlp.encode(headerHash)).isOkOr: warn logTxt "safeHeaderHash()", safeHashKey, action="put()", `error`=($$error) return proc finalizedHeaderHash*( db: CoreDbRef; ): Hash256 = db.getHash(finalizedHashKey()).valueOr(Hash256()) proc finalizedHeaderHash*(db: CoreDbRef, headerHash: Hash256) = let finalizedHashKey = finalizedHashKey() db.newKvt.put(finalizedHashKey.toOpenArray, rlp.encode(headerHash)).isOkOr: warn logTxt "finalizedHeaderHash()", finalizedHashKey, action="put()", `error`=($$error) return proc safeHeader*( db: CoreDbRef; ): BlockHeader {.gcsafe, raises: [BlockNotFound].} = db.getBlockHeader(db.safeHeaderHash) proc finalizedHeader*( db: CoreDbRef; ): BlockHeader {.gcsafe, raises: [BlockNotFound].} = db.getBlockHeader(db.finalizedHeaderHash) proc haveBlockAndState*(db: CoreDbRef, headerHash: Hash256): bool = var header: BlockHeader if not db.getBlockHeader(headerHash, header): return false # see if stateRoot exists db.exists(header.stateRoot) # ------------------------------------------------------------------------------ # End # ------------------------------------------------------------------------------