nimbus-eth1/nimbus/db/db_chain.nim

414 lines
15 KiB
Nim
Raw Normal View History

# Nimbus
# Copyright (c) 2018 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.
import
std/[sequtils, algorithm],
stew/[byteutils], eth/trie/[hexary, db],
eth/[common, rlp, p2p], chronicles,
".."/[errors, constants, utils, chain_config],
Redesign of BaseVMState descriptor (#923) * Redesign of BaseVMState descriptor why: BaseVMState provides an environment for executing transactions. The current descriptor also provides data that cannot generally be known within the execution environment, e.g. the total gasUsed which is available not before after all transactions have finished. Also, the BaseVMState constructor has been replaced by a constructor that does not need pre-initialised input of the account database. also: Previous constructor and some fields are provided with a deprecated annotation (producing a lot of noise.) * Replace legacy directives in production sources * Replace legacy directives in unit test sources * fix CI (missing premix update) * Remove legacy directives * chase CI problem * rebased * Re-introduce 'AccountsCache' constructor optimisation for 'BaseVmState' re-initialisation why: Constructing a new 'AccountsCache' descriptor can be avoided sometimes when the current state root is properly positioned already. Such a feature existed already as the update function 'initStateDB()' for the 'BaseChanDB' where the accounts cache was linked into this desctiptor. The function 'initStateDB()' was removed and re-implemented into the 'BaseVmState' constructor without optimisation. The old version was of restricted use as a wrong accounts cache state would unconditionally throw an exception rather than conceptually ask for a remedy. The optimised 'BaseVmState' re-initialisation has been implemented for the 'persistBlocks()' function. also: moved some test helpers to 'test/replay' folder * Remove unused & undocumented fields from Chain descriptor why: Reduces attack surface in general & improves reading the code.
2022-01-18 16:19:32 +00:00
"."/storage_types
type
2018-08-29 08:49:01 +00:00
BaseChainDB* = ref object
2018-12-31 03:27:02 +00:00
db* : TrieDatabaseRef
2018-11-30 10:07:20 +00:00
pruneTrie*: bool
networkId*: NetworkId
config* : ChainConfig
genesis* : Genesis
totalDifficulty*: DifficultyInt
# startingBlock, currentBlock, and highestBlock
# are progress indicator
startingBlock*: BlockNumber
currentBlock*: BlockNumber
highestBlock*: BlockNumber
2018-06-20 12:33:57 +00:00
TransactionKey = tuple
blockNumber: BlockNumber
index: int
proc getTotalDifficulty*(self: BaseChainDB): UInt256 =
# this is actually a combination of `getHash` and `getScore`
const key = canonicalHeadHashKey()
let data = self.db.get(key.toOpenArray)
if data.len == 0:
return 0.u256
let blockHash = rlp.decode(data, Hash256)
rlp.decode(self.db.get(blockHashToScoreKey(blockHash).toOpenArray), Uint256)
proc newBaseChainDB*(
db: TrieDatabaseRef,
pruneTrie: bool = true,
id: NetworkId = MainNet,
params = networkParams(MainNet)): BaseChainDB =
new(result)
result.db = db
2018-11-30 10:07:20 +00:00
result.pruneTrie = pruneTrie
result.networkId = id
result.config = params.config
result.genesis = params.genesis
result.totalDifficulty = result.getTotalDifficulty()
proc `$`*(db: BaseChainDB): string =
result = "BaseChainDB"
2022-02-15 03:24:38 +00:00
proc ttd*(db: BaseChainDB): DifficultyInt =
if db.config.terminalTotalDifficulty.isSome:
db.config.terminalTotalDifficulty.get()
else:
high(DifficultyInt)
proc networkParams*(db: BaseChainDB): NetworkParams =
NetworkParams(config: db.config, genesis: db.genesis)
2019-09-09 05:27:17 +00:00
proc exists*(self: BaseChainDB, hash: Hash256): bool =
self.db.contains(hash.data)
proc getBlockHeader*(self: BaseChainDB; blockHash: Hash256, output: var BlockHeader): bool =
let data = self.db.get(genericHashKey(blockHash).toOpenArray)
if data.len != 0:
try:
output = rlp.decode(data, BlockHeader)
true
except RlpError:
false
else:
false
proc getBlockHeader*(self: BaseChainDB, 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 self.getBlockHeader(blockHash, result):
2018-05-30 16:11:15 +00:00
raise newException(BlockNotFound, "No block with hash " & blockHash.data.toHex)
proc getHash(self: BaseChainDB, key: DbKey, output: var Hash256): bool {.inline.} =
let data = self.db.get(key.toOpenArray)
if data.len != 0:
output = rlp.decode(data, Hash256)
result = true
2018-06-20 12:33:57 +00:00
proc getCanonicalHead*(self: BaseChainDB): BlockHeader =
var headHash: Hash256
if not self.getHash(canonicalHeadHashKey(), headHash) or
not self.getBlockHeader(headHash, result):
2018-06-20 12:33:57 +00:00
raise newException(CanonicalHeadNotFound,
"No canonical head set for this chain")
proc populateProgress*(self: BaseChainDB) =
try:
self.startingBlock = self.getCanonicalHead().blockNumber
except CanonicalHeadNotFound:
self.startingBlock = toBlockNumber(0)
self.currentBlock = self.startingBlock
self.highestBlock = self.startingBlock
proc getBlockHash*(self: BaseChainDB, n: BlockNumber, output: var Hash256): bool {.inline.} =
## Return the block hash for the given block number.
self.getHash(blockNumberToHashKey(n), output)
2018-05-30 16:11:15 +00:00
proc getBlockHash*(self: BaseChainDB, n: BlockNumber): Hash256 {.inline.} =
## Return the block hash for the given block number.
if not self.getHash(blockNumberToHashKey(n), result):
raise newException(BlockNotFound, "No block hash for number " & $n)
proc getCurrentBlockHash*(self: BaseChainDB): Hash256 =
if not self.getHash(blockNumberToHashKey(self.currentBlock), result):
result = Hash256()
proc getBlockHeader*(self: BaseChainDB; n: BlockNumber, output: var BlockHeader): bool =
## Returns the block header with the given number in the canonical chain.
var blockHash: Hash256
if self.getBlockHash(n, blockHash):
result = self.getBlockHeader(blockHash, output)
proc getBlockHeader*(self: BaseChainDB; n: BlockNumber): BlockHeader =
## Returns the block header with the given number in the canonical chain.
## Raises BlockNotFound error if the block is not in the DB.
self.getBlockHeader(self.getBlockHash(n))
proc getScore*(self: BaseChainDB; blockHash: Hash256): Uint256 =
rlp.decode(self.db.get(blockHashToScoreKey(blockHash).toOpenArray), Uint256)
proc getTd*(self: BaseChainDB; blockHash: Hash256, td: var Uint256): bool =
let bytes = self.db.get(blockHashToScoreKey(blockHash).toOpenArray)
if bytes.len == 0: return false
try:
td = rlp.decode(bytes, Uint256)
except RlpError:
return false
return true
2019-09-09 05:27:17 +00:00
proc getAncestorsHashes*(self: BaseChainDB, limit: Uint256, header: BlockHeader): seq[Hash256] =
var ancestorCount = min(header.blockNumber, limit).truncate(int)
var h = header
result = newSeq[Hash256](ancestorCount)
while ancestorCount > 0:
h = self.getBlockHeader(h.parentHash)
result[ancestorCount - 1] = h.hash
dec ancestorCount
2018-06-20 12:33:57 +00:00
iterator findNewAncestors(self: BaseChainDB; header: BlockHeader): BlockHeader =
2018-05-30 16:11:15 +00:00
## 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
2018-05-30 16:11:15 +00:00
while true:
if self.getBlockHeader(h.blockNumber, orig) and orig.hash == h.hash:
break
2018-06-20 12:33:57 +00:00
2018-05-30 16:11:15 +00:00
yield h
2018-06-20 12:33:57 +00:00
2018-05-30 16:11:15 +00:00
if h.parentHash == GENESIS_PARENT_HASH:
break
2018-06-20 12:33:57 +00:00
else:
h = self.getBlockHeader(h.parentHash)
2018-06-20 12:33:57 +00:00
proc addBlockNumberToHashLookup*(self: BaseChainDB; header: BlockHeader) =
self.db.put(blockNumberToHashKey(header.blockNumber).toOpenArray,
rlp.encode(header.hash))
2018-06-20 12:33:57 +00:00
2020-07-30 07:21:11 +00:00
proc persistTransactions*(self: BaseChainDB, blockNumber:
BlockNumber, transactions: openArray[Transaction]): Hash256 =
var trie = initHexaryTrie(self.db)
for idx, tx in transactions:
let
encodedTx = rlp.encode(tx)
txHash = keccakHash(encodedTx)
txKey: TransactionKey = (blockNumber, idx)
trie.put(rlp.encode(idx), encodedTx)
self.db.put(transactionHashToBlockKey(txHash).toOpenArray, rlp.encode(txKey))
2020-07-30 07:21:11 +00:00
trie.rootHash
proc getTransaction*(self: BaseChainDB, txRoot: Hash256, txIndex: int, res: var Transaction): bool =
var db = initHexaryTrie(self.db, txRoot)
let txData = db.get(rlp.encode(txIndex))
if txData.len > 0:
res = rlp.decode(txData, Transaction)
result = true
iterator getBlockTransactionData*(self: BaseChainDB, transactionRoot: Hash256): seq[byte] =
var transactionDb = initHexaryTrie(self.db, transactionRoot)
var transactionIdx = 0
while true:
let transactionKey = rlp.encode(transactionIdx)
if transactionKey in transactionDb:
yield transactionDb.get(transactionKey)
else:
break
inc transactionIdx
2020-07-30 07:21:11 +00:00
iterator getBlockTransactions*(self: BaseChainDB, header: BlockHeader): Transaction =
for encodedTx in self.getBlockTransactionData(header.txRoot):
yield rlp.decode(encodedTx, Transaction)
iterator getBlockTransactionHashes*(self: BaseChainDB, blockHeader: BlockHeader): Hash256 =
2018-06-20 12:33:57 +00:00
## Returns an iterable of the transaction hashes from th block specified
## by the given block header.
for encodedTx in self.getBlockTransactionData(blockHeader.txRoot):
yield keccakHash(encodedTx)
proc getTransactionCount*(chain: BaseChainDB, txRoot: Hash256): int =
var trie = initHexaryTrie(chain.db, txRoot)
var txCount = 0
while true:
let txKey = rlp.encode(txCount)
if txKey notin trie:
break
inc txCount
txCount
proc getUnclesCount*(self: BaseChainDB, ommersHash: Hash256): int =
if ommersHash != EMPTY_UNCLE_HASH:
let encodedUncles = self.db.get(genericHashKey(ommersHash).toOpenArray)
if encodedUncles.len != 0:
let r = rlpFromBytes(encodedUncles)
result = r.listLen
2020-07-30 07:21:11 +00:00
proc getUncles*(self: BaseChainDB, ommersHash: Hash256): seq[BlockHeader] =
if ommersHash != EMPTY_UNCLE_HASH:
let encodedUncles = self.db.get(genericHashKey(ommersHash).toOpenArray)
if encodedUncles.len != 0:
result = rlp.decode(encodedUncles, seq[BlockHeader])
proc getBlockBody*(self: BaseChainDB, blockHash: Hash256, output: var BlockBody): bool =
var header: BlockHeader
if self.getBlockHeader(blockHash, header):
result = true
output.transactions = @[]
output.uncles = @[]
for encodedTx in self.getBlockTransactionData(header.txRoot):
output.transactions.add(rlp.decode(encodedTx, Transaction))
if header.ommersHash != EMPTY_UNCLE_HASH:
let encodedUncles = self.db.get(genericHashKey(header.ommersHash).toOpenArray)
if encodedUncles.len != 0:
output.uncles = rlp.decode(encodedUncles, seq[BlockHeader])
else:
result = false
2018-06-20 12:33:57 +00:00
2018-12-14 07:32:45 +00:00
proc getBlockBody*(self: BaseChainDB, hash: Hash256): BlockBody =
if not self.getBlockBody(hash, result):
raise newException(ValueError, "Error when retrieving block body")
2019-09-09 05:27:17 +00:00
proc getUncleHashes*(self: BaseChainDB, blockHashes: openArray[Hash256]): seq[Hash256] =
for blockHash in blockHashes:
var blockBody = self.getBlockBody(blockHash)
for uncle in blockBody.uncles:
result.add uncle.hash
2020-07-30 07:21:11 +00:00
proc getUncleHashes*(self: BaseChainDB, header: BlockHeader): seq[Hash256] =
if header.ommersHash != EMPTY_UNCLE_HASH:
let encodedUncles = self.db.get(genericHashKey(header.ommersHash).toOpenArray)
if encodedUncles.len != 0:
let uncles = rlp.decode(encodedUncles, seq[BlockHeader])
for x in uncles:
result.add x.hash
2018-08-17 18:52:48 +00:00
proc getTransactionKey*(self: BaseChainDB, transactionHash: Hash256): tuple[blockNumber: BlockNumber, index: int] {.inline.} =
2020-07-30 07:21:11 +00:00
let tx = self.db.get(transactionHashToBlockKey(transactionHash).toOpenArray)
if tx.len > 0:
let key = rlp.decode(tx, TransactionKey)
result = (key.blockNumber, key.index)
else:
result = (0.toBlockNumber, -1)
2018-06-20 12:33:57 +00:00
proc removeTransactionFromCanonicalChain(self: BaseChainDB, transactionHash: Hash256) {.inline.} =
## Removes the transaction specified by the given hash from the canonical chain.
self.db.del(transactionHashToBlockKey(transactionHash).toOpenArray)
2018-06-20 12:33:57 +00:00
proc setAsCanonicalChainHead(self: BaseChainDB; headerHash: Hash256): seq[BlockHeader] =
## Sets the header as the canonical chain HEAD.
let header = self.getBlockHeader(headerHash)
2018-06-20 12:33:57 +00:00
var newCanonicalHeaders = sequtils.toSeq(findNewAncestors(self, header))
reverse(newCanonicalHeaders)
for h in newCanonicalHeaders:
var oldHash: Hash256
if not self.getBlockHash(h.blockNumber, oldHash):
2018-06-20 12:33:57 +00:00
break
let oldHeader = self.getBlockHeader(oldHash)
2018-06-20 12:33:57 +00:00
for txHash in self.getBlockTransactionHashes(oldHeader):
self.removeTransactionFromCanonicalChain(txHash)
# TODO re-add txn to internal pending pool (only if local sender)
for h in newCanonicalHeaders:
self.addBlockNumberToHashLookup(h)
self.db.put(canonicalHeadHashKey().toOpenArray, rlp.encode(headerHash))
2018-06-20 12:33:57 +00:00
return newCanonicalHeaders
2018-05-30 16:11:15 +00:00
proc headerExists*(self: BaseChainDB; blockHash: Hash256): bool =
## Returns True if the header with the given block hash is in our DB.
self.db.contains(genericHashKey(blockHash).toOpenArray)
proc setHead*(self: BaseChainDB, blockHash: Hash256): bool =
if self.headerExists(blockHash):
self.db.put(canonicalHeadHashKey().toOpenArray, rlp.encode(blockHash))
return true
else:
return false
2019-01-01 03:55:40 +00:00
proc setHead*(self: BaseChainDB, header: BlockHeader, writeHeader = false) =
var headerHash = rlpHash(header)
if writeHeader:
self.db.put(genericHashKey(headerHash).toOpenArray, rlp.encode(header))
self.addBlockNumberToHashLookup(header)
self.db.put(canonicalHeadHashKey().toOpenArray, rlp.encode(headerHash))
proc persistReceipts*(self: BaseChainDB, receipts: openArray[Receipt]): Hash256 =
2018-12-10 12:04:34 +00:00
var trie = initHexaryTrie(self.db)
for idx, rec in receipts:
trie.put(rlp.encode(idx), rlp.encode(rec))
trie.rootHash
2018-12-10 12:04:34 +00:00
iterator getReceipts*(self: BaseChainDB; receiptRoot: Hash256): Receipt =
var receiptDb = initHexaryTrie(self.db, receiptRoot)
var receiptIdx = 0
while true:
let receiptKey = rlp.encode(receiptIdx)
if receiptKey in receiptDb:
let receiptData = receiptDb.get(receiptKey)
2018-12-10 12:04:34 +00:00
yield rlp.decode(receiptData, Receipt)
else:
break
inc receiptIdx
proc readTerminalHash*(self: BaseChainDB; h: var Hash256): bool =
let bytes = self.db.get(terminalHashKey().toOpenArray)
if bytes.len == 0:
return false
try:
h = rlp.decode(bytes, Hash256)
except RlpError:
return false
true
proc writeTerminalHash*(self: BaseChainDB; h: Hash256) =
self.db.put(terminalHashKey().toOpenArray, rlp.encode(h))
proc currentTerminalHeader*(self: BaseChainDB; header: var BlockHeader): bool =
var terminalHash: Hash256
if not self.readTerminalHash(terminalHash):
return false
if not self.getBlockHeader(terminalHash, header):
return false
true
2018-06-20 12:33:57 +00:00
proc persistHeaderToDb*(self: BaseChainDB; header: BlockHeader): seq[BlockHeader] =
let isGenesis = header.parentHash == GENESIS_PARENT_HASH
let headerHash = header.blockHash
2018-06-20 12:33:57 +00:00
if not isGenesis and not self.headerExists(header.parentHash):
raise newException(ParentNotFound, "Cannot persist block header " &
$headerHash & " with unknown parent " & $header.parentHash)
self.db.put(genericHashKey(headerHash).toOpenArray, rlp.encode(header))
2018-06-20 12:33:57 +00:00
let score = if isGenesis: header.difficulty
else: self.getScore(header.parentHash) + header.difficulty
self.db.put(blockHashToScoreKey(headerHash).toOpenArray, rlp.encode(score))
2018-08-01 12:50:44 +00:00
self.addBlockNumberToHashLookup(header)
var headScore: Uint256
2018-06-20 12:33:57 +00:00
try:
headScore = self.getScore(self.getCanonicalHead().hash)
except CanonicalHeadNotFound:
return self.setAsCanonicalChainHead(headerHash)
2018-06-20 12:33:57 +00:00
let ttd = self.ttd()
if headScore < ttd and score >= ttd:
self.writeTerminalHash(headerHash)
if score > headScore:
self.totalDifficulty = score
result = self.setAsCanonicalChainHead(headerHash)
2018-06-20 12:33:57 +00:00
proc persistHeaderToDbWithoutSetHead*(self: BaseChainDB; header: BlockHeader) =
let isGenesis = header.parentHash == GENESIS_PARENT_HASH
let headerHash = header.blockHash
let score = if isGenesis: header.difficulty
else: self.getScore(header.parentHash) + header.difficulty
self.db.put(blockHashToScoreKey(headerHash).toOpenArray, rlp.encode(score))
self.addBlockNumberToHashLookup(header)
self.db.put(genericHashKey(headerHash).toOpenArray, rlp.encode(header))
2018-06-20 12:33:57 +00:00
proc persistUncles*(self: BaseChainDB, uncles: openarray[BlockHeader]): Hash256 =
## Persists the list of uncles to the database.
## Returns the uncles hash.
let enc = rlp.encode(uncles)
2019-03-07 15:53:09 +00:00
result = keccakHash(enc)
self.db.put(genericHashKey(result).toOpenArray, enc)