Port parts of several other modules

Focus on getting most types defined with minimal implementation
and fields sufficient for simple tests
This commit is contained in:
Alexander Ivanov 2018-01-17 14:57:50 +02:00
parent e1c04e587f
commit 84ccfc5966
9 changed files with 432 additions and 19 deletions

10
src/block.nim Normal file
View File

@ -0,0 +1,10 @@
import
logging, constants, utils/header
type
CountableList*[T] = ref object
elements: seq[T] # TODO
Block* = ref object of RootObj
header*: Header
uncles*: CountableList[Header]

60
src/chain.nim Normal file
View File

@ -0,0 +1,60 @@
import
tables,
logging, constants, errors, validation, utils / [hexadecimal]
type
BlockHeader* = ref object
# Placeholder TODO
Chain* = ref object
## An Chain is a combination of one or more VM classes. Each VM is associated
## with a range of blocks. The Chain class acts as a wrapper around these other
## VM classes, delegating operations to the appropriate VM depending on the
## current block number.
header*: BlockHeader
logger*: Logger
networkId*: cstring
vmsByRange*: seq[tuple[blockNumber: Int256, vm: VM]] # TODO
importBlock*: bool
validateBlock*: bool
db*: BaseChainDB
GenesisParams* = ref object
blockNumber*: Int256
difficulty*: Int256
gasLimit*: Int256
parentHash*: cstring
coinbase*: cstring
nonce: cstring
mixHash: cstring
extraData: cstring
timestamp: int,
stateRoot: cstring
FundedAddress* = ref object
balance*: Int256
nonce*: int
code*: cstring
proc configureChain*(name: string, blockNumber: Int256, vm: VM, importBlock: bool = true, validateBlock: bool = true): Chain =
new(result)
result.vmsByRange = @[(blockNumber: blockNumber, vm: vm)]
proc fromGenesis*(
chain: Chain,
chainDB: BaseChainDB,
genesisParams: GenesisParams,
genesisState: Table[string, FundedAddress]): Chain =
## Initialize the Chain from a genesis state
var stateDB = chaindb.getStateDB(BLANK_ROOT_HASH)
for account, accountData in genesisState:
stateDB.setBalance(account, accountData.balance)
stateDB.setNonce(account, accountData.nonce)
stateDB.setCode(account, accountData.code)
new(result)
result.db = chainDB
result.header = BlockHeader()
result.logger = logging.getLogger("evm.chain.chain.Chain")
chainDB.persistBlockToDB(result.getBlock())

View File

@ -114,7 +114,7 @@ const
UINT_256_CEILING* = (2 ^ X).Int256
UINT_255_MAX* = (2 ^ (X - 1) - 1).Int256
UINT_255_CEILING* = (2 ^ (X - 1)).Int256
NULLBYTE = cstring"\\x00"
NULLBYTE = cstring"\x00"
EMPTYWORD = repeat(NULLBYTE, 32)
# UINT160CEILING = 2 ^ 160
CREATE_CONTRACT_ADDRESS* = cstring""
@ -190,15 +190,16 @@ const
SECPK1Gx = 0
SECPK1Gy = 0
SECPK1G = (SECPK1Gx, SECPK1Gy)
EMPTYUNCLEHASH = cstring"\\x1d\\xccM\\xe8\\xde\\xc7]z\\xab\\x85\\xb5g\\xb6\\xcc\\xd4\\x1a\\xd3\\x12E\\x1b\\x94\\x8at\\x13\\xf0\\xa1B\\xfd@\\xd4\\x93G"
EMPTYUNCLEHASH = cstring"\x1d\xccM\xe8\xde\xc7]z\xab\x85\xb5g\xb6\xcc\xd4\x1a\xd3\x12E\x1b\x94\x8at\x13\xf0\xa1B\xfd@\xd4\x93G"
GENESIS_BLOCK_NUMBER* = 0.Int256
GENESIS_DIFFICULTY* = 131072.Int256
GENESISGASLIMIT = 3141592
GENESISPARENTHASH = ZEROHASH32
GENESISCOINBASE = ZEROADDRESS
GENESISNONCE = cstring"\\x00\\x00\\x00\\x00\\x00\\x00\\x00B"
GENESISMIXHASH = ZEROHASH32
EMPTYSHA3 = cstring"\\xc5\\xd2F\\x01\\x86\\xf7#<\\x92~}\\xb2\\xdc\\xc7\\x03\\xc0\\xe5\\x00\\xb6S\\xca\\x82';{\\xfa\\xd8\\x04]\\x85\\xa4p"
BLANKROOTHASH = cstring"V\\xe8\\x1f\\x17\\x1b\\xccU\\xa6\\xff\\x83E\\xe6\\x92\\xc0\\xf8n[H\\xe0\\x1b\\x99l\\xad\\xc0\\x01b/\\xb5\\xe3c\\xb4!"
GASMODEXPQUADRATICDENOMINATOR = 20
MAXPREVHEADERDEPTH = 256
GENESIS_GAS_LIMIT* = 3141592.Int256
GENESIS_PARENT_HASH* = ZERO_HASH32
GENESIS_COINBASE* = ZERO_ADDRESS
GENESIS_NONCE* = cstring"\x00\x00\x00\x00\x00\x00\x00B"
GENESIS_MIX_HASH* = ZERO_HASH32
GENESIS_EXTRA_DATA = cstring""
EMPTYSHA3 = cstring"\xc5\xd2F\x01\x86\xf7#<\x92~}\xb2\xdc\xc7\x03\xc0\xe5\x00\xb6S\xca\x82';{\xfa\xd8\x04]\x85\xa4p"
BLANK_ROOT_HASH* = cstring"V\xe8\x1f\x17\x1b\xccU\xa6\xff\x83E\xe6\x92\xc0\xf8n[H\xe0\x1b\x99l\xad\xc0\x01b/\xb5\xe3c\xb4!"
GAS_MOD_EXP_QUADRATIC_DENOMINATOR* = 20
MAX_PREV_HEADER_DEPTH* = 256

151
src/db/chain.nim Normal file
View File

@ -0,0 +1,151 @@
type
BaseChainDB* = ref object
# TODO db*: JournalDB
# proc makeBaseChainDB*(db: MemoryDB): BaseChainDB =
# result.db = JournalDB(db)
# proc exists*(self: BaseChainDB; key: cstring): bool =
# return self.db.exists(key)
# 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 getCanonicalBlockHeaderByNumber*(self: BaseChainDB; blockNumber: int): BlockHeader =
# ## Returns the block header with the given number in the canonical chain.
# ##
# ## Raises BlockNotFound if there's no block header with the given number in the
# ## canonical chain.
# validateUint256(blockNumber)
# return self.getBlockHeaderByHash(self.lookupBlockHash(blockNumber))
# proc getScore*(self: BaseChainDB; blockHash: cstring): int =
# return rlp.decode(self.db.get(makeBlockHashToScoreLookupKey(blockHash)))
# 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 =
# ## 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:
# yield h
# if h.parentHash == GENESISPARENTHASH:
# break
# try:
# var orig = self.getCanonicalBlockHeaderByNumber(h.blockNumber)
# except KeyError:
# nil
# h = self.getBlockHeaderByHash(h.parentHash)
# proc getBlockHeaderByHash*(self: BaseChainDB; blockHash: cstring): BlockHeader =
# ## Returns the requested block header as specified by block hash.
# ##
# ## Raises BlockNotFound if it is not present in the db.
# validateWord(blockHash)
# try:
# var block = self.db.get(blockHash)
# except KeyError:
# raise newException(BlockNotFound, "No block with hash {0} found".format(
# encodeHex(blockHash)))
# return rlp.decode(block)
# proc headerExists*(self: BaseChainDB; blockHash: cstring): bool =
# ## Returns True if the header with the given block hash is in our DB.
# return self.db.exists(blockHash)
# proc lookupBlockHash*(self: BaseChainDB; blockNumber: int): cstring =
# ## Return the block hash for the given block number.
# validateUint256(blockNumber)
# var
# numberToHashKey = makeBlockNumberToHashLookupKey(blockNumber)
# blockHash = rlp.decode(self.db.get(numberToHashKey))
# return blockHash
# iterator getReceipts*(self: BaseChainDB; header: BlockHeader; receiptClass: typedesc): Receipt =
# var receiptDb = HexaryTrie()
# for receiptIdx in itertools.count():
# var receiptKey = rlp.encode(receiptIdx)
# if receiptKey in receiptDb:
# var receiptData = receiptDb[receiptKey]
# yield rlp.decode(receiptData)
# 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
# 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): 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)
# 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 addTransaction*(self: BaseChainDB; blockHeader: BlockHeader; indexKey: cstring;
# transaction: FrontierTransaction): cstring =
# var transactionDb = HexaryTrie(self.db)
# transactionDb[indexKey] = rlp.encode(transaction)
# return transactionDb.rootHash
# proc addReceipt*(self: BaseChainDB; blockHeader: BlockHeader; indexKey: cstring;
# receipt: Receipt): cstring =
# var receiptDb = HexaryTrie()
# receiptDb[indexKey] = rlp.encode(receipt)
# return receiptDb.rootHash
# proc snapshot*(self: BaseChainDB): UUID =
# return self.db.snapshot()
# proc commit*(self: BaseChainDB; checkpoint: UUID): void =
# self.db.commit(checkpoint)
# proc clear*(self: BaseChainDB): void =
# self.db.clear()
# proc getStateDb*(self: BaseChainDB; stateRoot: cstring; readOnly: bool): AccountStateDB =
# return AccountStateDB()
# var CANONICALHEADHASHDBKEY = cstring"v1:canonical_head_hash"

32
src/transaction.nim Normal file
View File

@ -0,0 +1,32 @@
import
constants, errors
type
BaseTransaction* = ref object
nonce*: Int256
gasPrice*: Int256
gas*: Int256
to*: cstring
value*: Int256
data*: cstring
v*: Int256
r*: Int256
s*: Int256
proc intrinsicGas*(t: BaseTransaction): Int256 =
# Compute the baseline gas cost for this transaction. This is the amount
# of gas needed to send this transaction (but that is not actually used
# for computation)
raise newException(ValueError, "not implemented intrinsicGas")
proc validate*(t: BaseTransaction) =
# Hook called during instantiation to ensure that all transaction
# parameters pass validation rules
if t.intrinsic_gas() > t.gas:
raise newException(ValidationError, "Insufficient gas")
# self.check_signature_validity()

37
src/utils/header.nim Normal file
View File

@ -0,0 +1,37 @@
type
Header* = ref object
# TODO
proc generateHeaderFromParentHeader*(
computeDifficultyFn: proc(parentHeader: Header, timestamp: int): int,
parentHeader: Header,
coinbase: cstring,
timestamp: int = -1,
extraData: cstring = cstring""): Header =
Header()
# Generate BlockHeader from state_root and parent_header
# if timestamp is None:
# timestamp = max(int(time.time()), parent_header.timestamp + 1)
# elif timestamp <= parent_header.timestamp:
# raise ValueError(
# "header.timestamp ({}) should be higher than"
# "parent_header.timestamp ({})".format(
# timestamp,
# parent_header.timestamp,
# )
# )
# header = BlockHeader(
# difficulty=compute_difficulty_fn(parent_header, timestamp),
# block_number=(parent_header.block_number + 1),
# gas_limit=compute_gas_limit(
# parent_header,
# gas_limit_floor=GENESIS_GAS_LIMIT,
# ),
# timestamp=timestamp,
# parent_hash=parent_header.hash,
# state_root=parent_header.state_root,
# coinbase=coinbase,
# extra_data=extra_data,
# )
# return header

61
src/vm/base.nim Normal file
View File

@ -0,0 +1,61 @@
import
../logging, ../constants, ../errors, ../transaction, ../computation, "../block", ../vm_state, ../db/chain, ../utils/db, ../utils/header
type
VM* = ref object of RootObj
# The VM class represents the Chain rules for a specific protocol definition
# such as the Frontier or Homestead network. Defining an Chain defining
# individual VM classes for each fork of the protocol rules within that
# network
chainDB*: BaseChainDB
isStateless*: bool
state*: BaseVMState
proc newVM*(header: Header, chainDB: BaseChainDB): VM =
new(result)
result.chainDB = chainDB
method addTransaction*(vm: var VM, transaction: BaseTransaction, computation: BaseComputation): Block =
# Add a transaction to the given block and save the block data into chaindb
var receipt = self.state.makeReceipt(transaction, computation)
var transactionIdx = len(vm.`block`.transactions)
return Block()
# var indexKey = rlp.encode(transaction_idx, sedes=rlp.sedes.big_endian_int)
# self.block.transactions.append(transaction)
# tx_root_hash = self.chaindb.add_transaction(self.block.header, index_key, transaction)
# receipt_root_hash = self.chaindb.add_receipt(self.block.header, index_key, receipt)
# self.block.bloom_filter |= receipt.bloom
# self.block.header.transaction_root = tx_root_hash
# self.block.header.receipt_root = receipt_root_hash
# self.block.header.bloom = int(self.block.bloom_filter)
# self.block.header.gas_used = receipt.gas_used
# return self.block
method applyTransaction*(vm: var VM, transaction: BaseTransaction): (BaseComputation, Block) =
# Apply the transaction to the vm in the current block
if vm.isStateless:
var (computation, b, trieData) = vm.state.applyTransaction(
vm.state,
transaction,
vm.`block`,
isStateless=true)
vm.`block` = b
result = (computation, b)
# Persist changed transaction and receipt key-values to self.chaindb.
else:
var (computation, _, _) = vm.state.applyTransaction(
vm.state,
transaction,
vm.`block`,
isStateless=false)
vm.addTransaction(transaction, computation)
result = (computation, vm.`block`)

View File

@ -1,15 +1,15 @@
import
strformat,
logging, constants, errors, utils/state
strformat, tables,
logging, constants, errors, transaction, db/chain, utils/state, utils/header
type
BaseVMState* = ref object of RootObj
prevHeaders*: bool
receipts*: bool
computationClass*: bool
chaindb*: bool
accessLogs*: seq[bool]
blockHeader*: bool
prevHeaders*: seq[Header]
receipts*: seq[string]
# computationClass*: bool
chaindb*: BaseChainDB
accessLogs*: seq[string]
blockHeader*: Header
name*: string
proc newBaseVMState*: BaseVMState =

View File

@ -0,0 +1,61 @@
import
strformat, tables,
logging, constants, errors, computation, transaction, vm_state, "block", db/chain, utils/state, utils/header
method executeTransaction(vmState: var BaseVMState, transaction: BaseTransaction): (BaseComputation, Header) =
# Execute the transaction in the vm
raise newException(ValueError, "Must be implemented by subclasses")
method addTransaction*(vmState: var BaseVMState, transaction: BaseTransaction, computation: BaseComputation, b: Block): (Block, Table[cstring, cstring]) =
# Add a transaction to the given block and
# return `trieData` to store the transaction data in chaindb in VM layer
# Update the bloomFilter, transaction trie and receipt trie roots, bloom_filter,
# bloom, and usedGas of the block
# transaction: the executed transaction
# computation: the Computation object with executed result
# block: the Block which the transaction is added in
# var receipt = vmState.makeReceipt(transaction, computation)
# vmState.add_receipt(receipt)
# block.transactions.append(transaction)
# # Get trie roots and changed key-values.
# tx_root_hash, tx_kv_nodes = make_trie_root_and_nodes(block.transactions)
# receipt_root_hash, receipt_kv_nodes = make_trie_root_and_nodes(self.receipts)
# trie_data = merge(tx_kv_nodes, receipt_kv_nodes)
# block.bloom_filter |= receipt.bloom
# block.header.transaction_root = tx_root_hash
# block.header.receipt_root = receipt_root_hash
# block.header.bloom = int(block.bloom_filter)
# block.header.gas_used = receipt.gas_used
# return block, trie_data
result = (b, initTable[cstring, cstring]())
method applyTransaction*(
vmState: var BaseVMState,
transaction: BaseTransaction,
b: Block,
isStateless: bool): (BaseComputation, Block, Table[cstring, cstring]) =
# Apply transaction to the given block
# transaction: the transaction need to be applied
# b: the block which the transaction applies on
# isStateless: if isStateless, call vmState.addTransaction to set block
if isStateless:
var ourBlock = b # deepcopy
vmState.blockHeader = b.header
var (computation, blockHeader) = vmState.executeTransaction(transaction)
ourBlock.header = blockHeader
var trieData: Table[cstring, cstring]
(ourBlock, trieData) = vmState.addTransaction(transaction, computation, ourBlock)
result = (computation, ourBlock, trieData)
else:
var (computation, blockHeader) = vmState.executeTransaction(transaction)
return (computation, nil, initTable[cstring, cstring]())