Persist* functions

This commit is contained in:
Yuriy Glukhov 2018-06-20 15:33:57 +03:00 committed by zah
parent 14a1b51981
commit 1c79d1ab3d
3 changed files with 145 additions and 73 deletions

View File

@ -6,15 +6,12 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
stint,
./logging, ./constants,
./utils/header
stint, eth_common,
./logging, ./constants
type
CountableList*[T] = ref object
elements: seq[T] # TODO
Block* = ref object of RootObj
header*: BlockHeader
uncles*: CountableList[BlockHeader]
blockNumber*: UInt256
uncles*: seq[BlockHeader]
proc blockNumber*(b: Block): BlockNumber {.inline.} = b.header.blockNumber

View File

@ -12,28 +12,36 @@ type
genericHash
blockNumberToHash
blockHashToScore
transactionHashToBlock
canonicalHeadHash
DbKey* = object
case kind: DBKeyKind
of genericHash, blockHashToScore:
of genericHash, blockHashToScore, transactionHashToBlock:
h: Hash256
of blockNumberToHash:
u: BlockNumber
of canonicalHeadHash:
discard
MemoryDB* = ref object
kvStore*: Table[DbKey, seq[byte]]
proc genericHashKey*(h: Hash256): DbKey {.inline.} = DbKey(kind: genericHash, h: h)
proc blockHashToScoreKey*(h: Hash256): DbKey {.inline.} = DbKey(kind: blockHashToScore, h: h)
proc transactionHashToBlockKey*(h: Hash256): DbKey {.inline.} = DbKey(kind: transactionHashToBlock, h: h)
proc blockNumberToHashKey*(u: BlockNumber): DbKey {.inline.} = DbKey(kind: blockNumberToHash, u: u)
proc canonicalHeadHashKey*(): DbKey {.inline.} = DbKey(kind: canonicalHeadHash)
proc hash(k: DbKey): Hash =
result = result !& hash(k.kind)
case k.kind
of genericHash, blockHashToScore:
of genericHash, blockHashToScore, transactionHashToBlock:
result = result !& hash(k.h)
of blockNumberToHash:
result = result !& hashData(unsafeAddr k.u, sizeof(k.u))
of canonicalHeadHash:
discard
result = result
proc `==`(a, b: DbKey): bool {.inline.} =

View File

@ -5,8 +5,9 @@
# * 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 stint, tables, rlp, ranges, state_db, backends / memory_backend,
../errors, ../utils/header, ../constants, eth_common, byteutils
import stint, tables, sequtils, algorithm, rlp, ranges, state_db, nimcrypto,
backends / memory_backend,
../errors, ../block_types, ../utils/header, ../constants, eth_common, byteutils
type
BaseChainDB* = ref object
@ -17,6 +18,10 @@ type
blockNumberToHash
blockHashToScore
TransactionKey = tuple
blockNumber: BlockNumber
index: int
proc newBaseChainDB*(db: MemoryDB): BaseChainDB =
new(result)
result.db = db
@ -39,16 +44,19 @@ proc getBlockHeaderByHash*(self: BaseChainDB; blockHash: Hash256): BlockHeader =
let rng = blk.toRange
return decode(rng, BlockHeader)
# proc getCanonicalHead*(self: BaseChainDB): BlockHeader =
# if notself.exists(CANONICALHEADHASHDBKEY):
# raise newException(CanonicalHeadNotFound,
# "No canonical head set for this chain")
# return self.getBlockHeaderByHash(self.db.get(CANONICALHEADHASHDBKEY))
proc getHash(self: BaseChainDB, key: DbKey): Hash256 {.inline.} =
rlp.decode(self.db.get(key).toRange, Hash256)
proc getCanonicalHead*(self: BaseChainDB): BlockHeader =
let k = canonicalHeadHashKey()
if k notin self.db:
raise newException(CanonicalHeadNotFound,
"No canonical head set for this chain")
return self.getBlockHeaderByHash(self.getHash(k))
proc lookupBlockHash*(self: BaseChainDB; n: BlockNumber): Hash256 {.inline.} =
## Return the block hash for the given block number.
let numberToHashKey = blockNumberToHashKey(n)
result = rlp.decode(self.db.get(numberToHashKey).toRange, Hash256)
self.getHash(blockNumberToHashKey(n))
proc getCanonicalBlockHeaderByNumber*(self: BaseChainDB; n: BlockNumber): BlockHeader =
## Returns the block header with the given number in the canonical chain.
@ -60,35 +68,86 @@ proc getCanonicalBlockHeaderByNumber*(self: BaseChainDB; n: BlockNumber): BlockH
proc getScore*(self: BaseChainDB; blockHash: Hash256): int =
rlp.decode(self.db.get(blockHashToScoreKey(blockHash)).toRange, int)
# proc setAsCanonicalChainHead*(self: BaseChainDB; header: BlockHeader): void =
# ## Sets the header as the canonical chain HEAD.
# for h in reversed(self.findCommonAncestor(header)):
# self.addBlockNumberToHashLookup(h)
# try:
# self.getBlockHeaderByHash(header.hash)
# except BlockNotFound:
# raise newException(ValueError, "Cannot use unknown block hash as canonical head: {}".format(
# header.hash))
# self.db.set(CANONICALHEADHASHDBKEY, header.hash)
iterator findCommonAncestor*(self: BaseChainDB; header: BlockHeader): BlockHeader =
iterator findNewAncestors(self: BaseChainDB; 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
while true:
try:
let orig = self.getCanonicalBlockHeaderByNumber(h.blockNumber)
if orig.hash == h.hash:
break
except BlockNotFound:
discard
yield h
if h.parentHash == GENESIS_PARENT_HASH:
break
else:
h = self.getBlockHeaderByHash(h.parentHash)
proc addBlockNumberToHashLookup(self: BaseChainDB; header: BlockHeader) =
self.db.set(blockNumberToHashKey(header.blockNumber), rlp.encode(header.hash).toSeq())
iterator getBlockTransactionHashes(self: BaseChainDB, blockHeader: BlockHeader): Hash256 =
## Returns an iterable of the transaction hashes from th block specified
## by the given block header.
doAssert(false, "TODO: Implement me")
# let all_encoded_transactions = self._get_block_transaction_data(
# blockHeader.transactionRoot,
# )
# for encoded_transaction in all_encoded_transactions:
# yield keccak(encoded_transaction)
proc removeTransactionFromCanonicalChain(self: BaseChainDB, transactionHash: Hash256) {.inline.} =
## Removes the transaction specified by the given hash from the canonical chain.
self.db.delete(transactionHashToBlockKey(transactionHash))
proc setAsCanonicalChainHead(self: BaseChainDB; headerHash: Hash256): seq[BlockHeader] =
## Sets the header as the canonical chain HEAD.
let header = self.getBlockHeaderByHash(headerHash)
var newCanonicalHeaders = sequtils.toSeq(findNewAncestors(self, header))
reverse(newCanonicalHeaders)
for h in newCanonicalHeaders:
var oldHash: Hash256
try:
var orig = self.getCanonicalBlockHeaderByNumber(h.blockNumber)
except KeyError:
discard # TODO: break??
h = self.getBlockHeaderByHash(h.parentHash)
oldHash = self.lookupBlockHash(h.blockNumber)
except BlockNotFound:
break
let oldHeader = self.getBlockHeaderByHash(oldHash)
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.set(canonicalHeadHashKey(), rlp.encode(header.hash).toSeq())
return newCanonicalHeaders
proc headerExists*(self: BaseChainDB; blockHash: Hash256): bool =
## Returns True if the header with the given block hash is in our DB.
self.contains(blockHash)
iterator getBlockTransactionData(self: BaseChainDB, transactionRoot: Hash256): BytesRange =
doAssert(false, "TODO: Implement me")
# var transactionDb = HexaryTrie(self.db, transactionRoot)
# var transactionIdx = 0
# while true:
# var transactionKey = rlp.encode(transactionIdx)
# if transactionKey in transactionDb:
# var transactionData = transactionDb[transactionKey]
# yield transactionDb[transactionKey]
# else:
# break
# inc transactionIdx
# iterator getReceipts*(self: BaseChainDB; header: BlockHeader; receiptClass: typedesc): Receipt =
# var receiptDb = HexaryTrie()
# for receiptIdx in itertools.count():
@ -99,45 +158,55 @@ proc headerExists*(self: BaseChainDB; blockHash: Hash256): bool =
# else:
# break
# iterator getBlockTransactions*(self: BaseChainDB; blockHeader: BlockHeader;
# transactionClass: typedesc): FrontierTransaction =
# var transactionDb = HexaryTrie(self.db)
# for transactionIdx in itertools.count():
# var transactionKey = rlp.encode(transactionIdx)
# if transactionKey in transactionDb:
# var transactionData = transactionDb[transactionKey]
# yield rlp.decode(transactionData)
# else:
# break
iterator getBlockTransactions(self: BaseChainDB; transactionRoot: Hash256;
transactionClass: typedesc): transactionClass =
for encodedTransaction in self.getBlockTransactionData(transactionRoot):
yield rlp.decode(encodedTransaction, transactionClass)
# proc addBlockNumberToHashLookup*(self: BaseChainDB; header: BlockHeader): void =
# var blockNumberToHashKey = makeBlockNumberToHashLookupKey(header.blockNumber)
# self.db.set(blockNumberToHashKey, rlp.encode(header.hash))
proc persistHeaderToDb*(self: BaseChainDB; header: BlockHeader): seq[BlockHeader] =
let isGenesis = header.parentHash == GENESIS_PARENT_HASH
if not isGenesis and not self.headerExists(header.parentHash):
raise newException(ParentNotFound, "Cannot persist block header " &
$header.hash & " with unknown parent " & $header.parentHash)
self.db.set(genericHashKey(header.hash), rlp.encode(header).toSeq())
let score = if isGenesis: header.difficulty
else: self.getScore(header.parentHash).u256 + header.difficulty
self.db.set(blockHashToScoreKey(header.hash), rlp.encode(score).toSeq())
var headScore: int
try:
headScore = self.getScore(self.getCanonicalHead().hash)
except CanonicalHeadNotFound:
return self.setAsCanonicalChainHead(header.hash)
# proc persistHeaderToDb*(self: BaseChainDB; header: BlockHeader): void =
# if header.parentHash != GENESISPARENTHASH and
# notself.headerExists(header.parentHash):
# raise newException(ParentNotFound, "Cannot persist block header ({}) with unknown parent ({})".format(
# encodeHex(header.hash), encodeHex(header.parentHash)))
# self.db.set(header.hash, rlp.encode(header))
# if header.parentHash == GENESISPARENTHASH:
# var score = header.difficulty
# else:
# score = self.getScore(header.parentHash) + header.difficulty
# self.db.set(makeBlockHashToScoreLookupKey(header.hash), rlp.encode(score))
# try:
# var headScore = self.getScore(self.getCanonicalHead().hash)
# except CanonicalHeadNotFound:
# self.setAsCanonicalChainHead(header)
if score > headScore.u256:
result = self.setAsCanonicalChainHead(header.hash)
# proc persistBlockToDb*(self: BaseChainDB; block: FrontierBlock): void =
# self.persistHeaderToDb(block.header)
# var transactionDb = HexaryTrie(self.db)
# for i in 0 ..< len(block.transactions):
# var indexKey = rlp.encode(i)
# transactionDb[indexKey] = rlp.encode(block.transactions[i])
# nil
# self.db.set(block.header.unclesHash, rlp.encode(block.uncles))
proc addTransactionToCanonicalChain(self: BaseChainDB, txHash: Hash256,
blockHeader: BlockHeader, index: int) =
let k: TransactionKey = (blockHeader.blockNumber, index)
self.db.set(transactionHashToBlockKey(txHash), rlp.encode(k).toSeq())
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)
result = keccak256.digest(enc.toOpenArray())
self.db.set(genericHashKey(result), enc.toSeq())
proc persistBlockToDb*(self: BaseChainDB; blk: Block) =
## Persist the given block's header and uncles.
## Assumes all block transactions have been persisted already.
let newCanonicalHeaders = self.persistHeaderToDb(blk.header)
for header in newCanonicalHeaders:
var index = 0
for txHash in self.getBlockTransactionHashes(header):
self.addTransactionToCanonicalChain(txHash, header, index)
inc index
if blk.uncles.len != 0:
let ommersHash = self.persistUncles(blk.uncles)
assert ommersHash == blk.header.ommersHash
# proc addTransaction*(self: BaseChainDB; blockHeader: BlockHeader; indexKey: cstring;
# transaction: FrontierTransaction): cstring =
@ -163,5 +232,3 @@ proc headerExists*(self: BaseChainDB; blockHash: Hash256): bool =
method getStateDb*(self: BaseChainDB; stateRoot: Hash256; readOnly: bool = false): AccountStateDB =
# TODO
result = newAccountStateDB(initTable[string, string]())
# var CANONICALHEADHASHDBKEY = cstring"v1:canonical_head_hash"