diff --git a/src/block.nim b/src/block.nim index c7d83e020..63da46c31 100644 --- a/src/block.nim +++ b/src/block.nim @@ -8,3 +8,4 @@ type Block* = ref object of RootObj header*: Header uncles*: CountableList[Header] + blockNumber*: Int256 diff --git a/src/constants.nim b/src/constants.nim index 3747fa018..5090ca603 100644 --- a/src/constants.nim +++ b/src/constants.nim @@ -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" diff --git a/src/transaction.nim b/src/transaction.nim index f770ac0db..35c98a04a 100644 --- a/src/transaction.nim +++ b/src/transaction.nim @@ -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"" \ No newline at end of file diff --git a/src/utils/header.nim b/src/utils/header.nim index 9c029593d..c5b656491 100644 --- a/src/utils/header.nim +++ b/src/utils/header.nim @@ -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 diff --git a/src/validation.nim b/src/validation.nim index 65c4c99b6..e7ce01420 100644 --- a/src/validation.nim +++ b/src/validation.nim @@ -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}") diff --git a/src/vm/base.nim b/src/vm/base.nim index ac97695ad..5ecd3efe3 100644 --- a/src/vm/base.nim +++ b/src/vm/base.nim @@ -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`) diff --git a/src/vm/forks/frontier/frontier_block.nim b/src/vm/forks/frontier/frontier_block.nim new file mode 100644 index 000000000..a9d3c72ca --- /dev/null +++ b/src/vm/forks/frontier/frontier_block.nim @@ -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 + diff --git a/src/vm/forks/frontier/frontier_headers.nim b/src/vm/forks/frontier/frontier_headers.nim new file mode 100644 index 000000000..7e75bcf21 --- /dev/null +++ b/src/vm/forks/frontier/frontier_headers.nim @@ -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() + diff --git a/src/vm/forks/frontier/frontier_validation.nim b/src/vm/forks/frontier/frontier_validation.nim new file mode 100644 index 000000000..5d978e97d --- /dev/null +++ b/src/vm/forks/frontier/frontier_validation.nim @@ -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") diff --git a/src/vm/forks/frontier/frontier_vm_state.nim b/src/vm/forks/frontier/frontier_vm_state.nim new file mode 100644 index 000000000..543e89c66 --- /dev/null +++ b/src/vm/forks/frontier/frontier_vm_state.nim @@ -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 + diff --git a/src/vm/forks/frontier/nim.cfg b/src/vm/forks/frontier/nim.cfg new file mode 100644 index 000000000..ebbc1d958 --- /dev/null +++ b/src/vm/forks/frontier/nim.cfg @@ -0,0 +1 @@ +p:"../../../" diff --git a/src/vm/forks/frontier/vm.nim b/src/vm/forks/frontier/vm.nim new file mode 100644 index 000000000..a16bde32a --- /dev/null +++ b/src/vm/forks/frontier/vm.nim @@ -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