176 lines
5.7 KiB
Nim
176 lines
5.7 KiB
Nim
import ../db/[db_chain, state_db], eth_common, chronicles, ../vm_state, ../vm_types, ../transaction, ranges,
|
|
../vm/[computation, interpreter_dispatch, message], ../constants, stint, nimcrypto, ../utils/addresses,
|
|
eth_trie/memdb, eth_trie, rlp
|
|
|
|
|
|
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
|
|
|
|
proc dataGas(data: openarray[byte]): GasInt =
|
|
for i in data:
|
|
if i == 0:
|
|
result += 4
|
|
else:
|
|
result += 68
|
|
|
|
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
|
|
echo "Sender: ", sender
|
|
echo "txHash: ", t.rlpHash
|
|
# Inct nonce:
|
|
db.setNonce(sender, db.getNonce(sender) + 1)
|
|
var transactionFailed = false
|
|
|
|
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
|
|
|
|
var gasUsed = 21000.GasInt
|
|
if t.payload.len != 0:
|
|
gasUsed += dataGas(t.payload)
|
|
|
|
if t.isContractCreation:
|
|
# gasUsed += 32000 # This appears in Homestead.
|
|
echo "Contract creation"
|
|
|
|
if gasUsed > t.gasLimit:
|
|
echo "Transaction failed. Out of gas."
|
|
transactionFailed = true
|
|
else:
|
|
if t.isContractCreation:
|
|
db.setCode(generateAddress(sender, t.accountNonce), t.payload.toRange)
|
|
let msg = newMessage(t.gasLimit, t.gasPrice, t.to, sender, t.value, @[], t.payload)
|
|
let vmState = newBaseVMState(head, chainDB)
|
|
var c = newBaseComputation(vmState, head.blockNumber, msg)
|
|
c.executeOpcodes()
|
|
echo "isError: ", c.isError
|
|
# TODO: Handle failure, and gas consumption
|
|
|
|
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)
|
|
# TODO: Run the vm
|
|
|
|
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:
|
|
tr.put(rlp.encode(i).toRange, rlp.encode(t).toRange)
|
|
return tr.rootHash
|
|
|
|
method persistBlocks*(c: Chain, headers: openarray[BlockHeader], bodies: openarray[BlockBody]) =
|
|
# Run the VM here
|
|
assert(headers.len == bodies.len)
|
|
|
|
let blockReward = 5.u256 * pow(10.u256, 18) # 5 ETH
|
|
|
|
echo "Persisting blocks: ", headers[0].blockNumber, " - ", headers[^1].blockNumber
|
|
for i in 0 ..< headers.len:
|
|
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)
|
|
|
|
if headers[i].txRoot != BLANK_ROOT_HASH:
|
|
# assert(head.blockNumber == headers[i].blockNumber - 1)
|
|
let vmState = newBaseVMState(head, c.db)
|
|
assert(bodies[i].transactions.len != 0)
|
|
|
|
if bodies[i].transactions.len != 0:
|
|
echo "block: ", headers[i].blockNumber
|
|
echo "h: ", headers[i].blockHash
|
|
|
|
for t in bodies[i].transactions:
|
|
var sender: EthAddress
|
|
if t.getSender(sender):
|
|
gasReward += processTransaction(stateDb, t, sender, head, c.db)
|
|
else:
|
|
assert(false, "Could not get sender")
|
|
|
|
var mainReward = blockReward + gasReward
|
|
|
|
if headers[i].ommersHash != EMPTY_UNCLE_HASH:
|
|
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)
|
|
|
|
|
|
discard c.db.persistHeaderToDb(headers[i])
|
|
assert(c.db.getCanonicalHead().blockHash == headers[i].blockHash)
|
|
|