2018-09-06 17:05:22 +00:00
import .. / db / [ db_chain , state_db ] , eth_common , chronicles , .. / vm_state , .. / vm_types , .. / transaction , ranges ,
2018-09-10 08:44:07 +00:00
.. / vm / [ computation , interpreter_dispatch , message ] , .. / constants , stint , nimcrypto , .. / utils / addresses ,
2018-09-27 19:09:26 +00:00
.. / vm_state_transactions ,
eth_trie / memdb , eth_trie , rlp ,
sugar
2018-08-29 08:49:01 +00:00
type
Chain * = ref object of AbstractChainDB
db : BaseChainDB
proc newChain * ( db : BaseChainDB ) : Chain =
result . new
result . db = db
method genesisHash * ( c : Chain ) : KeccakHash =
c . db . getBlockHash ( 0 . toBlockNumber )
method getBlockHeader * ( c : Chain , b : HashOrNum , output : var BlockHeader ) : bool =
case b . isHash
of true :
c . db . getBlockHeader ( b . hash , output )
else :
c . db . getBlockHeader ( b . number , output )
method getBestBlockHeader * ( c : Chain ) : BlockHeader =
c . db . getCanonicalHead ( )
method getSuccessorHeader * ( c : Chain , h : BlockHeader , output : var BlockHeader ) : bool =
let n = h . blockNumber + 1
c . db . getBlockHeader ( n , output )
method getBlockBody * ( c : Chain , blockHash : KeccakHash ) : BlockBodyRef =
result = nil
2018-09-20 20:06:22 +00:00
proc processTransaction ( db : var AccountStateDB , t : Transaction , sender : EthAddress , head : BlockHeader , chainDB : BaseChainDB ) : UInt256 =
## Process the transaction, write the results to db.
## Returns amount of ETH to be rewarded to miner
2018-09-10 08:44:07 +00:00
echo " Sender: " , sender
echo " txHash: " , t . rlpHash
# Inct nonce:
db . setNonce ( sender , db . getNonce ( sender ) + 1 )
var transactionFailed = false
2018-09-27 19:09:26 +00:00
#t.dump
# TODO: combine/refactor re validate
2018-09-10 08:44:07 +00:00
let upfrontGasCost = t . gasLimit . u256 * t . gasPrice . u256
let upfrontCost = upfrontGasCost + t . value
var balance = db . getBalance ( sender )
if balance < upfrontCost :
if balance < = upfrontGasCost :
result = balance
balance = 0 . u256
else :
result = upfrontGasCost
balance - = upfrontGasCost
transactionFailed = true
else :
balance - = upfrontCost
db . setBalance ( sender , balance )
if transactionFailed :
return
2018-09-27 19:09:26 +00:00
var gasUsed = t . payload . intrinsicGas . GasInt
2018-09-10 08:44:07 +00:00
if t . isContractCreation :
2018-09-20 20:06:22 +00:00
# gasUsed += 32000 # This appears in Homestead.
2018-09-10 08:44:07 +00:00
echo " Contract creation "
2018-09-27 19:09:26 +00:00
# TODO: check the 200x constant up front
2018-09-10 08:44:07 +00:00
if gasUsed > t . gasLimit :
echo " Transaction failed. Out of gas. "
transactionFailed = true
else :
if t . isContractCreation :
2018-09-27 19:09:26 +00:00
# TODO: split out into separate function?; interleaving the cases obfuscates
2018-09-20 20:06:22 +00:00
let vmState = newBaseVMState ( head , chainDB )
2018-09-27 19:09:26 +00:00
2018-09-27 21:48:23 +00:00
# TODO: setupComputation refactoring
let contractAddress = generateAddress ( sender , t . accountNonce )
let msg = newMessage ( t . gasLimit - gasUsed , t . gasPrice , t . to , sender , t . value , @ [ ] , t . payload ,
options = newMessageOptions ( origin = sender ,
createAddress = contractAddress ) )
2018-09-20 20:06:22 +00:00
var c = newBaseComputation ( vmState , head . blockNumber , msg )
2018-09-27 19:09:26 +00:00
if execComputation ( c , vmState ) :
db . addBalance ( contractAddress , t . value )
# XXX: copy/pasted from GST fixture
# TODO: more merging/refactoring/etc
# also a couple lines can collapse because variable used once
# once verified in GST fixture
let
gasRemaining = c . gasMeter . gasRemaining . u256
gasRefunded = c . gasMeter . gasRefunded . u256
gasUsed2 = t . gasLimit . u256 - gasRemaining
gasRefund = min ( gasRefunded , gasUsed2 div 2 )
gasRefundAmount = ( gasRefund + gasRemaining ) * t . gasPrice . u256
#echo "gasRemaining is ", gasRemaining, " and gasRefunded = ", gasRefunded, " and gasUsed2 = ", gasUsed2, " and gasRefund = ", gasRefund, " and gasRefundAmount = ", gasRefundAmount
2018-09-27 21:48:23 +00:00
var codeCost = 200 * c . output . len
# This apparently is not supposed to actually consume the gas, just be able to,
# for purposes of accounting. Py-EVM apparently does consume the gas, but it is
# not matching observed blockchain balances if consumeGas is called.
if gasRemaining > = codeCost . u256 :
db . setCode ( contractAddress , c . output . toRange )
else :
# XXX: Homestead behaves differently; reverts state on gas failure
# https://github.com/ethereum/py-evm/blob/master/eth/vm/forks/homestead/computation.py
codeCost = 0
db . setCode ( contractAddress , ByteRange ( ) )
db . addBalance ( sender , ( t . gasLimit . u256 - gasUsed2 - codeCost . u256 ) * t . gasPrice . u256 )
return ( gasUsed2 + codeCost . u256 ) * t . gasPrice . u256
2018-09-27 19:09:26 +00:00
else :
# FIXME: don't do this revert, but rather only subBalance correctly
# the if transactionfailed at end is what is supposed to pick it up
db . addBalance ( sender , t . value )
echo " isError: " , c . isError
return upfrontGasCost
2018-09-20 20:06:22 +00:00
2018-09-10 08:44:07 +00:00
else :
let code = db . getCode ( t . to )
if code . len = = 0 :
# Value transfer
echo " Transfer " , t . value , " from " , sender , " to " , t . to
db . addBalance ( t . to , t . value )
else :
# Contract call
echo " Contract call "
debug " Transaction " , sender , to = t . to , value = t . value , hasCode = code . len ! = 0
let msg = newMessage ( t . gasLimit , t . gasPrice , t . to , sender , t . value , t . payload , code . toSeq )
2018-09-20 20:06:22 +00:00
# TODO: Run the vm
2018-09-10 08:44:07 +00:00
if gasUsed > t . gasLimit :
gasUsed = t . gasLimit
var refund = ( t . gasLimit - gasUsed ) . u256 * t . gasPrice . u256
if transactionFailed :
refund + = t . value
db . addBalance ( sender , refund )
return gasUsed . u256 * t . gasPrice . u256
proc calcTxRoot ( transactions : openarray [ Transaction ] ) : Hash256 =
var tr = initHexaryTrie ( trieDB ( newMemDB ( ) ) )
for i , t in transactions :
2018-09-26 11:00:52 +00:00
tr . put ( rlp . encode ( i ) . toRange , rlp . encode ( t ) . toRange )
2018-09-10 08:44:07 +00:00
return tr . rootHash
2018-08-29 08:49:01 +00:00
method persistBlocks * ( c : Chain , headers : openarray [ BlockHeader ] , bodies : openarray [ BlockBody ] ) =
# Run the VM here
assert ( headers . len = = bodies . len )
2018-09-10 08:44:07 +00:00
let blockReward = 5 . u256 * pow ( 10 . u256 , 18 ) # 5 ETH
echo " Persisting blocks: " , headers [ 0 ] . blockNumber , " - " , headers [ ^ 1 ] . blockNumber
2018-08-29 08:49:01 +00:00
for i in 0 .. < headers . len :
2018-09-10 08:44:07 +00:00
let head = c . db . getCanonicalHead ( )
assert ( head . blockNumber = = headers [ i ] . blockNumber - 1 )
var stateDb = newAccountStateDB ( c . db . db , head . stateRoot )
var gasReward = 0 . u256
assert ( bodies [ i ] . transactions . calcTxRoot = = headers [ i ] . txRoot )
2018-09-06 17:05:22 +00:00
if headers [ i ] . txRoot ! = BLANK_ROOT_HASH :
# assert(head.blockNumber == headers[i].blockNumber - 1)
let vmState = newBaseVMState ( head , c . db )
2018-09-10 08:44:07 +00:00
assert ( bodies [ i ] . transactions . len ! = 0 )
2018-09-06 17:05:22 +00:00
if bodies [ i ] . transactions . len ! = 0 :
2018-09-10 08:44:07 +00:00
echo " block: " , headers [ i ] . blockNumber
echo " h: " , headers [ i ] . blockHash
2018-09-06 17:05:22 +00:00
for t in bodies [ i ] . transactions :
var sender : EthAddress
if t . getSender ( sender ) :
2018-09-20 20:06:22 +00:00
gasReward + = processTransaction ( stateDb , t , sender , head , c . db )
2018-09-10 08:44:07 +00:00
else :
assert ( false , " Could not get sender " )
var mainReward = blockReward + gasReward
2018-09-27 19:09:26 +00:00
#echo "mainReward = ", mainReward , " with blockReward = ", blockReward, " and gasReward = ", gasReward
2018-09-06 17:05:22 +00:00
if headers [ i ] . ommersHash ! = EMPTY_UNCLE_HASH :
2018-09-10 08:44:07 +00:00
let h = c . db . persistUncles ( bodies [ i ] . uncles )
assert ( h = = headers [ i ] . ommersHash )
for u in 0 .. < bodies [ i ] . uncles . len :
var uncleReward = bodies [ i ] . uncles [ u ] . blockNumber + 8 . u256
uncleReward - = headers [ i ] . blockNumber
uncleReward = uncleReward * blockReward
uncleReward = uncleReward div 8 . u256
stateDb . addBalance ( bodies [ i ] . uncles [ u ] . coinbase , uncleReward )
mainReward + = blockReward div 32 . u256
# Reward beneficiary
stateDb . addBalance ( headers [ i ] . coinbase , mainReward )
if headers [ i ] . stateRoot ! = stateDb . rootHash :
echo " Wrong state root in block " , headers [ i ] . blockNumber , " . Expected: " , headers [ i ] . stateRoot , " , Actual: " , stateDb . rootHash , " arrived from " , c . db . getCanonicalHead ( ) . stateRoot
assert ( headers [ i ] . stateRoot = = stateDb . rootHash )
2018-09-06 17:05:22 +00:00
discard c . db . persistHeaderToDb ( headers [ i ] )
assert ( c . db . getCanonicalHead ( ) . blockHash = = headers [ i ] . blockHash )