2018-04-06 14:52:10 +00:00
|
|
|
# Nimbus
|
|
|
|
# Copyright (c) 2018 Status Research & Development GmbH
|
|
|
|
# Licensed under either of
|
|
|
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
|
|
|
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
|
|
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
|
|
|
|
2018-01-17 12:57:50 +00:00
|
|
|
import
|
2019-02-26 07:04:12 +00:00
|
|
|
constants, errors, eth/[common, rlp, keys], nimcrypto, utils
|
2018-01-17 12:57:50 +00:00
|
|
|
|
2018-11-20 17:35:11 +00:00
|
|
|
proc initTransaction*(nonce: AccountNonce, gasPrice, gasLimit: GasInt, to: EthAddress,
|
|
|
|
value: UInt256, payload: Blob, V: byte, R, S: UInt256, isContractCreation = false): Transaction =
|
|
|
|
result.accountNonce = nonce
|
|
|
|
result.gasPrice = gasPrice
|
|
|
|
result.gasLimit = gasLimit
|
|
|
|
result.to = to
|
|
|
|
result.value = value
|
2019-02-26 07:04:12 +00:00
|
|
|
result.payload = payload
|
2018-11-20 17:35:11 +00:00
|
|
|
result.V = V
|
|
|
|
result.R = R
|
|
|
|
result.S = S
|
|
|
|
result.isContractCreation = isContractCreation
|
|
|
|
|
2018-12-03 19:47:20 +00:00
|
|
|
func intrinsicGas*(data: openarray[byte]): GasInt =
|
|
|
|
result = 21_000 # GasTransaction
|
|
|
|
for i in data:
|
|
|
|
if i == 0:
|
|
|
|
result += 4 # GasTXDataZero
|
|
|
|
else:
|
|
|
|
result += 68 # GasTXDataNonZero
|
|
|
|
|
2018-08-24 15:46:48 +00:00
|
|
|
proc intrinsicGas*(t: Transaction): GasInt =
|
2018-01-17 12:57:50 +00:00
|
|
|
# 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)
|
2018-12-03 19:47:20 +00:00
|
|
|
result = t.payload.intrinsicGas
|
2018-01-17 12:57:50 +00:00
|
|
|
|
2018-08-24 15:46:48 +00:00
|
|
|
proc validate*(t: Transaction) =
|
2018-01-17 12:57:50 +00:00
|
|
|
# Hook called during instantiation to ensure that all transaction
|
|
|
|
# parameters pass validation rules
|
2018-08-24 15:46:48 +00:00
|
|
|
if t.intrinsicGas() > t.gasLimit:
|
2018-01-17 12:57:50 +00:00
|
|
|
raise newException(ValidationError, "Insufficient gas")
|
|
|
|
# self.check_signature_validity()
|
2018-01-17 14:16:00 +00:00
|
|
|
|
2018-09-10 08:44:07 +00:00
|
|
|
type
|
|
|
|
TransHashObj = object
|
|
|
|
accountNonce: AccountNonce
|
|
|
|
gasPrice: GasInt
|
|
|
|
gasLimit: GasInt
|
|
|
|
to {.rlpCustomSerialization.}: EthAddress
|
|
|
|
value: UInt256
|
|
|
|
payload: Blob
|
|
|
|
mIsContractCreation {.rlpIgnore.}: bool
|
|
|
|
|
|
|
|
proc read(rlp: var Rlp, t: var TransHashObj, _: type EthAddress): EthAddress {.inline.} =
|
|
|
|
if rlp.blobLen != 0:
|
|
|
|
result = rlp.read(EthAddress)
|
|
|
|
else:
|
|
|
|
t.mIsContractCreation = true
|
|
|
|
|
|
|
|
proc append(rlpWriter: var RlpWriter, t: TransHashObj, a: EthAddress) {.inline.} =
|
|
|
|
if t.mIsContractCreation:
|
|
|
|
rlpWriter.append("")
|
|
|
|
else:
|
|
|
|
rlpWriter.append(a)
|
|
|
|
|
2018-09-05 23:48:06 +00:00
|
|
|
func rlpEncode*(transaction: Transaction): auto =
|
|
|
|
# Encode transaction without signature
|
|
|
|
return rlp.encode(TransHashObj(
|
2018-08-24 15:46:48 +00:00
|
|
|
accountNonce: transaction.accountNonce,
|
|
|
|
gasPrice: transaction.gasPrice,
|
|
|
|
gasLimit: transaction.gasLimit,
|
|
|
|
to: transaction.to,
|
|
|
|
value: transaction.value,
|
2018-09-10 08:44:07 +00:00
|
|
|
payload: transaction.payload,
|
|
|
|
mIsContractCreation: transaction.isContractCreation
|
2018-09-05 23:48:06 +00:00
|
|
|
))
|
|
|
|
|
2018-09-10 08:44:07 +00:00
|
|
|
func txHashNoSignature*(transaction: Transaction): Hash256 =
|
2018-09-05 23:48:06 +00:00
|
|
|
# Hash transaction without signature
|
2018-09-25 12:52:28 +00:00
|
|
|
return keccak256.digest(transaction.rlpEncode)
|
2018-08-24 15:46:48 +00:00
|
|
|
|
2018-09-10 08:44:07 +00:00
|
|
|
proc getSignature*(transaction: Transaction, output: var Signature): bool =
|
2018-08-24 15:46:48 +00:00
|
|
|
var bytes: array[65, byte]
|
|
|
|
bytes[0..31] = transaction.R.toByteArrayBE()
|
|
|
|
bytes[32..63] = transaction.S.toByteArrayBE()
|
|
|
|
# TODO: V will become a byte or range soon.
|
2018-09-10 08:44:07 +00:00
|
|
|
let v = transaction.V.int
|
|
|
|
var chainId: int
|
|
|
|
if v > 36:
|
|
|
|
chainId = (v - 35) div 2
|
|
|
|
elif v == 27 or v == 28:
|
|
|
|
chainId = -4
|
|
|
|
else:
|
|
|
|
return false
|
|
|
|
|
|
|
|
bytes[64] = byte(v - (chainId * 2 + 35))
|
|
|
|
result = recoverSignature(bytes, output) == EthKeysStatus.Success
|
|
|
|
|
|
|
|
proc toSignature*(transaction: Transaction): Signature =
|
|
|
|
if not getSignature(transaction, result):
|
|
|
|
raise newException(Exception, "Invalid signaure")
|
2018-08-24 15:46:48 +00:00
|
|
|
|
|
|
|
proc getSender*(transaction: Transaction, output: var EthAddress): bool =
|
|
|
|
## Find the address the transaction was sent from.
|
2018-09-10 08:44:07 +00:00
|
|
|
var sig: Signature
|
|
|
|
if transaction.getSignature(sig):
|
|
|
|
var pubKey: PublicKey
|
|
|
|
let txHash = transaction.txHashNoSignature
|
|
|
|
if recoverSignatureKey(sig, txHash.data, pubKey) == EthKeysStatus.Success:
|
|
|
|
output = pubKey.toCanonicalAddress()
|
|
|
|
result = true
|
2018-08-24 15:46:48 +00:00
|
|
|
|
2018-08-29 15:52:12 +00:00
|
|
|
proc getSender*(transaction: Transaction): EthAddress =
|
|
|
|
## Raises error on failure to recover public key
|
|
|
|
if not transaction.getSender(result):
|
|
|
|
raise newException(ValidationError, "Could not derive sender address from transaction")
|
2018-09-10 08:44:07 +00:00
|
|
|
|
2019-02-26 07:04:12 +00:00
|
|
|
proc getRecipient*(tx: Transaction): EthAddress =
|
|
|
|
if tx.isContractCreation:
|
|
|
|
let sender = tx.getSender()
|
|
|
|
result = generateAddress(sender, tx.accountNonce)
|
|
|
|
else:
|
|
|
|
result = tx.to
|