Prototype an example fork subclass

This commit is contained in:
Alexander Ivanov 2018-01-17 16:16:00 +02:00
parent 84ccfc5966
commit 637b56fa4c
12 changed files with 330 additions and 31 deletions

View File

@ -8,3 +8,4 @@ type
Block* = ref object of RootObj
header*: Header
uncles*: CountableList[Header]
blockNumber*: Int256

View File

@ -173,14 +173,14 @@ const
GASLIMITADJUSTMENTFACTOR = 1024
GASLIMITMINIMUM = 5000
# GASLIMITMAXIMUM = 2 ^ 63 - 1
GASLIMITUSAGEADJUSTMENTNUMERATOR = 3
GASLIMITUSAGEADJUSTMENTDENOMINATOR = 2
DIFFICULTYADJUSTMENTDENOMINATOR = 2048
DIFFICULTYMINIMUM = 131072
BOMBEXPONENTIALPERIOD = 100000
# BOMBEXPONENTIALFREEPERIODS = 2
# BLOCKREWARD = 5 * denoms.ether
UNCLEDEPTHPENALTYFACTOR = 8
GAS_LIMIT_USAGE_ADJUSTMENT_NUMERATOR* = 3.Int256
GAS_LIMIT_USAGE_ADJUSTMENT_DENOMINATOR* = 2.Int256
DIFFICULTY_ADJUSTMENT_DENOMINATOR* = 2_048.Int256
DIFFICULTY_MINIMUM* = 131_072.Int256
BOMB_EXPONENTIAL_PERIOD* = 100_000.Int256
BOMB_EXPONENTIAL_FREE_PERIODS* = 2.Int256
BLOCK_REWARD* = 5.Int256 * 2.Int256 # denoms.ether
UNCLE_DEPTH_PENALTY_FACTOR* = 8.Int256
MAXUNCLEDEPTH = 6
MAXUNCLES = 2
# SECPK1P = 2 ^ 256 - 2 ^ 32 - 977
@ -192,8 +192,8 @@ const
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"
GENESIS_BLOCK_NUMBER* = 0.Int256
GENESIS_DIFFICULTY* = 131072.Int256
GENESIS_GAS_LIMIT* = 3141592.Int256
GENESIS_DIFFICULTY* = 131_072.Int256
GENESIS_GAS_LIMIT* = 3_141_592.Int256
GENESIS_PARENT_HASH* = ZERO_HASH32
GENESIS_COINBASE* = ZERO_ADDRESS
GENESIS_NONCE* = cstring"\x00\x00\x00\x00\x00\x00\x00B"

View File

@ -30,3 +30,7 @@ proc validate*(t: BaseTransaction) =
if t.intrinsic_gas() > t.gas:
raise newException(ValidationError, "Insufficient gas")
# self.check_signature_validity()
proc sender*(t: BaseTransaction): cstring =
# TODO
cstring""

View File

@ -1,5 +1,10 @@
import ../constants
type
Header* = ref object
timestamp*: int
difficulty*: Int256
blockNumber*: Int256
# TODO
proc generateHeaderFromParentHeader*(
@ -35,3 +40,15 @@ proc generateHeaderFromParentHeader*(
# )
# return header
proc computeGasLimit*(header: Header, gasLimitFloor: Int256): Int256 =
# TODO
gasLimitFloor
proc gasUsed*(header: Header): Int256 =
# TODO
0.Int256
proc gasLimit*(header: Header): Int256 =
# TODO
0.Int256

View File

@ -13,6 +13,16 @@ proc validateCanonicalAddress*(value: cstring, title: string = "Value") =
proc validateGte*(value: Int256, minimum: int, title: string = "Value") =
if value <= minimum.Int256:
raise newException(ValidationError,
fmt"{title} {value} is not greater than or equal to {minimum}")
proc validateGt*(value: Int256, minimum: int, title: string = "Value") =
if value < minimum.Int256:
raise newException(ValidationError,
fmt"{title} {value} is not greater than or equal to {minimum}")
proc validateGt*(value: int, minimum: int, title: string = "Value") =
if value < minimum:
raise newException(ValidationError,
fmt"{title} {value} is not greater than or equal to {minimum}")

View File

@ -1,5 +1,5 @@
import
../logging, ../constants, ../errors, ../transaction, ../computation, "../block", ../vm_state, ../db/chain, ../utils/db, ../utils/header
../logging, ../constants, ../errors, ../transaction, ../computation, "../block", ../vm_state, ../vm_state_transactions, ../db/chain, ../utils/header
type
VM* = ref object of RootObj
@ -11,38 +11,26 @@ type
chainDB*: BaseChainDB
isStateless*: bool
state*: BaseVMState
`block`*: Block
proc newVM*(header: Header, chainDB: BaseChainDB): VM =
new(result)
result.chainDB = chainDB
method name*(vm: VM): string =
"VM"
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)
# var receipt = vm.state.makeReceipt(transaction, computation)
# var transactionIdx = len(vm.`block`.transactions)
# TODO
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)
@ -51,11 +39,10 @@ method applyTransaction*(vm: var VM, transaction: BaseTransaction): (BaseComputa
# 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)
discard vm.addTransaction(transaction, computation)
result = (computation, vm.`block`)

View File

@ -0,0 +1,54 @@
import
logging, constants, errors, transaction, "block", utils/header
type
FrontierBlock* = object of Block
# bloomFilter*: BloomFilter
# header*: BlockHeader
transactions*: seq[BaseTransaction]
# transactionClass*: Any
stateRoot*: cstring
# fields*: seq[(string, Function)]
# cachedRlp: cstring
# uncles*: void
# import
# rlp, rlp.sedes, eth_bloom, evm.constants, evm.rlp.receipts, evm.rlp.blocks,
# evm.rlp.headers, evm.utils.keccak, transactions
# method makeFrontierBlock*(header: auto; transactions: auto; uncles: void): auto =
# if transactions is None:
# transactions = @[]
# if uncles is None:
# uncles = @[]
# result.bloomFilter = BloomFilter(header.bloom)
# super(FrontierBlock, result).__init__()
# method number*(self: FrontierBlock): int =
# return self.header.blockNumber
# method hash*(self: FrontierBlock): cstring =
# return self.header.hash
# method getTransactionClass*(cls: typedesc): typedesc =
# return cls.transactionClass
# method getReceipts*(self: FrontierBlock; chaindb: BaseChainDB): seq[Receipt] =
# return chaindb.getReceipts(self.header, Receipt)
# method fromHeader*(cls: typedesc; header: BlockHeader; chaindb: BaseChainDB): FrontierBlock =
# ## Returns the block denoted by the given block header.
# if header.unclesHash == EMPTYUNCLEHASH:
# var uncles = @[]
# else:
# uncles = chaindb.getBlockUncles(header.unclesHash)
# var transactions = chaindb.getBlockTransactions(header, cls.getTransactionClass())
# return cls()
# proc makeFrontierBlock*(): FrontierBlock =
# result.transactionClass = FrontierTransaction
# result.fields = @[("header", BlockHeader),
# ("transactions", CountableList(transactionClass)),
# ("uncles", CountableList(BlockHeader))]
# result.bloomFilter = nil

View File

@ -0,0 +1,21 @@
import
logging, constants, errors, validation, utils/header, vm / forks / frontier / vm
method computeDifficulty*(parentHeader: Header, timestamp: int): Int256 =
validateGt(timestamp, parentHeader.timestamp, title="Header timestamp")
let offset = parentHeader.difficulty div DIFFICULTY_ADJUSTMENT_DENOMINATOR
# We set the minimum to the lowest of the protocol minimum and the parent
# minimum to allow for the initial frontier *warming* period during which
# the difficulty begins lower than the protocol minimum
let difficultyMinimum = min(parentHeader.difficulty, DIFFICULTY_MINIMUM)
# let test = (timestamp - parentHeader.timestamp).Int256 < FRONTIER_DIFFICULTY_ADJUSTMENT_CUTOFF
# let baseDifficulty = max(parent.Header.difficulty + (if test: offset else: -offset), difficultyMinimum)
# # Adjust for difficulty bomb
# let numBombPeriods = ((parentHeader.blockNumber + 1) div BOMB_EXPONENTIAL_PERIOD) - BOMB_EXPONENTIAL_FREE_PERIODS
# result = if numBombPeriods >= 0: max(baseDifficulty + 2.Int256 ^ numBombPeriods, DIFFICULTY_MINIMUM) else: baseDifficulty
result = 0.Int256
method createHeaderFromParent*(parentHeader: Header): Header =
# TODO
result = Header()

View File

@ -0,0 +1,24 @@
import
strformat,
constants, errors, vm_state, transaction, utils/header
proc validateFrontierTransaction*(vmState: BaseVmState, transaction: BaseTransaction) =
let gasCost = transaction.gas * transaction.gasPrice
var senderBalance: Int256
# inDB(vmState.stateDB(readOnly=true):
# senderBalance = db.getBalance(transaction.sender)
senderBalance = gasCost # TODO
if senderBalance < gasCost:
raise newException(ValidationError, %"Sender account balance cannot afford txn gas: {transaction.sender}")
let totalCost = transaction.value + gasCost
if senderBalance < totalCost:
raise newException(ValidationError, "Sender account balance cannot afford txn")
if vmState.blockHeader.gasUsed + transaction.gas > vmState.blockHeader.gasLimit:
raise newException(ValidationError, "Transaction exceeds gas limit")
# inDB(vmState.stateDb(readOnly=true):
# if db.getNonce(transaction.sender) != transaction.nonce:
# raise newException(ValidationError, "Invalid transaction nonce")

View File

@ -0,0 +1,155 @@
import
logging, constants, errors, vm_state, utils/header, db/chain
type
FrontierVMState* = object of BaseVMState
# receipts*:
# computationClass*: Any
# accessLogs*: AccessLogs
# import
# py2nim_helpers, __future__, rlp, evm, evm.constants, evm.exceptions, evm.rlp.logs,
# evm.rlp.receipts, evm.vm.message, evm.vm_state, evm.utils.address,
# evm.utils.hexadecimal, evm.utils.keccak, evm.validation, computation, constants,
# validation
# type
# FrontierVMState* = object of Function
# prevHeaders*: seq[BlockHeader]
# receipts*: void
# computationClass*: Any
# _chaindb*: BaseChainDB
# accessLogs*: AccessLogs
# blockHeader*: BlockHeader
# proc _executeFrontierTransaction*(vmState: FrontierVMState;
# transaction: FrontierTransaction): FrontierComputation =
# transaction.validate()
# validateFrontierTransaction(vmState, transaction)
# var gasFee = transaction.gas * transaction.gasPrice
# with vmState.stateDb(),
# stateDb.deltaBalance(transaction.sender, -1 * gasFee)
# stateDb.incrementNonce(transaction.sender)
# var messageGas = transaction.gas - transaction.intrinsicGas
# if transaction.to == constants.CREATECONTRACTADDRESS:
# var
# contractAddress = generateContractAddress(transaction.sender,
# stateDb.getNonce(transaction.sender) - 1)
# data = cstring""
# code = transaction.data
# else:
# contractAddress = None
# data = transaction.data
# code = stateDb.getCode(transaction.to)
# vmState.logger.info("TRANSACTION: sender: %s | to: %s | value: %s | gas: %s | gas-price: %s | s: %s | r: %s | v: %s | data-hash: %s",
# encodeHex(transaction.sender), encodeHex(transaction.to),
# transaction.value, transaction.gas, transaction.gasPrice,
# transaction.s, transaction.r, transaction.v,
# encodeHex(keccak(transaction.data)))
# var message = Message()
# if message.isCreate:
# with vmState.stateDb(),
# var isCollision = stateDb.accountHasCodeOrNonce(contractAddress)
# if isCollision:
# var computation = vmState.getComputation(message)
# computation._error = ContractCreationCollision("Address collision while creating contract: {0}".format(
# encodeHex(contractAddress)))
# vmState.logger.debug("Address collision while creating contract: %s",
# encodeHex(contractAddress))
# else:
# computation = vmState.getComputation(message).applyCreateMessage()
# else:
# computation = vmState.getComputation(message).applyMessage()
# var numDeletions = len(computation.getAccountsForDeletion())
# if numDeletions:
# computation.gasMeter.refundGas(REFUNDSELFDESTRUCT * numDeletions)
# var
# gasRemaining = computation.getGasRemaining()
# gasRefunded = computation.getGasRefund()
# gasUsed = transaction.gas - gasRemaining
# gasRefund = min(gasRefunded, gasUsed div 2)
# gasRefundAmount = gasRefund + gasRemaining * transaction.gasPrice
# if gasRefundAmount:
# vmState.logger.debug("TRANSACTION REFUND: %s -> %s", gasRefundAmount,
# encodeHex(message.sender))
# with vmState.stateDb(),
# stateDb.deltaBalance(message.sender, gasRefundAmount)
# var transactionFee = transaction.gas - gasRemaining - gasRefund *
# transaction.gasPrice
# vmState.logger.debug("TRANSACTION FEE: %s -> %s", transactionFee,
# encodeHex(vmState.coinbase))
# with vmState.stateDb(),
# stateDb.deltaBalance(vmState.coinbase, transactionFee)
# with vmState.stateDb(),
# for account, beneficiary in computation.getAccountsForDeletion():
# vmState.logger.debug("DELETING ACCOUNT: %s", encodeHex(account))
# stateDb.setBalance(account, 0)
# stateDb.deleteAccount(account)
# return computation
# proc _makeFrontierReceipt*(vmState: FrontierVMState;
# transaction: FrontierTransaction;
# computation: FrontierComputation): Receipt =
# var
# logs = ## py2nim can't generate code for
# ## Log(address, topics, data)
# gasRemaining = computation.getGasRemaining()
# gasRefund = computation.getGasRefund()
# txGasUsed = transaction.gas - gasRemaining -
# min(gasRefund, transaction.gas - gasRemaining div 2)
# gasUsed = vmState.blockHeader.gasUsed + txGasUsed
# receipt = Receipt()
# return receipt
# method executeTransaction*(self: FrontierVMState; transaction: FrontierTransaction): (
# , ) =
# var computation = _executeFrontierTransaction(self, transaction)
# return (computation, self.blockHeader)
# method makeReceipt*(self: FrontierVMState; transaction: FrontierTransaction;
# computation: FrontierComputation): Receipt =
# var receipt = _makeFrontierReceipt(self, transaction, computation)
# return receipt
# method validateBlock*(self: FrontierVMState; block: FrontierBlock): void =
# if notblock.isGenesis:
# var parentHeader = self.parentHeader
# self._validateGasLimit(block)
# validateLengthLte(block.header.extraData, 32)
# if block.header.timestamp < parentHeader.timestamp:
# raise newException(ValidationError, "`timestamp` is before the parent block\'s timestamp.\\n- block : {0}\\n- parent : {1}. ".format(
# block.header.timestamp, parentHeader.timestamp))
# elif block.header.timestamp == parentHeader.timestamp:
# raise ValidationError("`timestamp` is equal to the parent block\'s timestamp\\n- block : {0}\\n- parent: {1}. ".format(
# block.header.timestamp, parentHeader.timestamp))
# if len(block.uncles) > MAXUNCLES:
# raise newException(ValidationError, "Blocks may have a maximum of {0} uncles. Found {1}.".format(
# MAXUNCLES, len(block.uncles)))
# for uncle in block.uncles:
# self.validateUncle(block, uncle)
# if notself.isKeyExists(block.header.stateRoot):
# raise newException(ValidationError, "`state_root` was not found in the db.\\n- state_root: {0}".format(
# block.header.stateRoot))
# var localUncleHash = keccak(rlp.encode(block.uncles))
# if localUncleHash != block.header.unclesHash:
# raise newException(ValidationError, "`uncles_hash` and block `uncles` do not match.\\n - num_uncles : {0}\\n - block uncle_hash : {1}\\n - header uncle_hash: {2}".format(
# len(block.uncles), localUncleHash, block.header.uncleHash))
# method _validateGasLimit*(self: FrontierVMState; block: FrontierBlock): void =
# var gasLimit = block.header.gasLimit
# if gasLimit < GASLIMITMINIMUM:
# raise newException(ValidationError, "Gas limit {0} is below minimum {1}".format(
# gasLimit, GASLIMITMINIMUM))
# if gasLimit > GASLIMITMAXIMUM:
# raise newException(ValidationError, "Gas limit {0} is above maximum {1}".format(
# gasLimit, GASLIMITMAXIMUM))
# var
# parentGasLimit = self.parentHeader.gasLimit
# diff = gasLimit - parentGasLimit
# if diff > parentGasLimit // GASLIMITADJUSTMENTFACTOR:
# raise newException(ValidationError, "Gas limit {0} difference to parent {1} is too big {2}".format(
# gasLimit, parentGasLimit, diff))
# proc makeFrontierVMState*(): FrontierVMState =
# result.computationClass = FrontierComputation

View File

@ -0,0 +1 @@
p:"../../../"

View File

@ -0,0 +1,25 @@
import
logging, constants, errors, "block", vm / [base, stack], db / chain, utils / header,
frontier_block, frontier_vm_state, frontier_validation
type
FrontierVM* = ref object of VM
method name*(vm: FrontierVM): string =
"FrontierVM"
method getBlockReward(vm: FrontierVM): Int256 =
BLOCK_REWARD
method getetUncleReward(vm: FrontierVM, blockNumber: Int256, uncle: Block): Int256 =
BLOCK_REWARD * (UNCLE_DEPTH_PENALTY_FACTOR + uncle.blockNumber - blockNumber) div UNCLE_DEPTH_PENALTY_FACTOR
method getNephewReward(vm: FrontierVM): Int256 =
vm.getBlockReward() div 32
proc newFrontierVM*(header: Header, chainDB: BaseChainDB): FrontierVM =
new(result)
result.chainDB = chainDB
result.isStateless = true