eip2718: test_blockchain_json pass test

This commit is contained in:
jangko 2021-05-15 13:37:40 +07:00
parent 2a9c3982d9
commit 79044f1e92
No known key found for this signature in database
GPG Key ID: 31702AE10541E6B9
10 changed files with 212 additions and 78 deletions

View File

@ -17,11 +17,6 @@ import
json_serialization/lexer json_serialization/lexer
type type
# beware that although in some cases
# chainId have identical value to networkId
# they are separate entity
ChainId* = distinct uint
ChainOptions = object ChainOptions = object
chainId : ChainId chainId : ChainId
homesteadBlock : Option[BlockNumber] homesteadBlock : Option[BlockNumber]

View File

@ -40,9 +40,9 @@ proc validateTransaction*(vmState: BaseVMState, tx: Transaction,
require=tx.intrinsicGas(fork) require=tx.intrinsicGas(fork)
return return
if tx.accountNonce != nonce: if tx.nonce != nonce:
debug "invalid tx: account nonce mismatch", debug "invalid tx: account nonce mismatch",
txNonce=tx.accountnonce, txNonce=tx.nonce,
accountNonce=nonce accountNonce=nonce
return return
@ -96,19 +96,30 @@ func logsBloom(logs: openArray[Log]): LogsBloom =
func createBloom*(receipts: openArray[Receipt]): Bloom = func createBloom*(receipts: openArray[Receipt]): Bloom =
var bloom: LogsBloom var bloom: LogsBloom
for receipt in receipts: for rec in receipts:
bloom.value = bloom.value or logsBloom(receipt.logs).value bloom.value = bloom.value or logsBloom(rec.logs).value
result = bloom.value.toByteArrayBE result = bloom.value.toByteArrayBE
proc makeReceipt*(vmState: BaseVMState, fork = FkFrontier): Receipt = proc makeReceipt*(vmState: BaseVMState, fork: Fork, txType: TxType): Receipt =
if fork < FkByzantium: if txType == AccessListTxType:
result.stateRootOrStatus = hashOrStatus(vmState.accountDb.rootHash) var rec = AccessListReceipt(
else: status: vmState.status,
result.stateRootOrStatus = hashOrStatus(vmState.status) cumulativeGasUsed: vmState.cumulativeGasUsed,
logs: vmState.getAndClearLogEntries()
)
rec.bloom = logsBloom(rec.logs).value.toByteArrayBE
return Receipt(receiptType: AccessListReceiptType, accessListReceipt: rec)
result.cumulativeGasUsed = vmState.cumulativeGasUsed var rec: LegacyReceipt
result.logs = vmState.getAndClearLogEntries() if fork < FkByzantium:
result.bloom = logsBloom(result.logs).value.toByteArrayBE rec.stateRootOrStatus = hashOrStatus(vmState.accountDb.rootHash)
else:
rec.stateRootOrStatus = hashOrStatus(vmState.status)
rec.cumulativeGasUsed = vmState.cumulativeGasUsed
rec.logs = vmState.getAndClearLogEntries()
rec.bloom = logsBloom(rec.logs).value.toByteArrayBE
Receipt(receiptType: LegacyReceiptType, legacyReceipt: rec)
func eth(n: int): Uint256 {.compileTime.} = func eth(n: int): Uint256 {.compileTime.} =
n.u256 * pow(10.u256, 18) n.u256 * pow(10.u256, 18)
@ -178,7 +189,7 @@ proc processBlock*(chainDB: BaseChainDB, header: BlockHeader, body: BlockBody, v
else: else:
debug "Could not get sender", txIndex, tx debug "Could not get sender", txIndex, tx
return ValidationResult.Error return ValidationResult.Error
vmState.receipts[txIndex] = makeReceipt(vmState, fork) vmState.receipts[txIndex] = makeReceipt(vmState, fork, tx.txType)
if header.ommersHash != EMPTY_UNCLE_HASH: if header.ommersHash != EMPTY_UNCLE_HASH:
let h = chainDB.persistUncles(body.uncles) let h = chainDB.persistUncles(body.uncles)

View File

@ -7,11 +7,30 @@
import import
./constants, ./errors, eth/[common, keys], ./utils, ./constants, ./errors, eth/[common, keys], ./utils,
stew/shims/macros,
./vm_types2, ./vm_gas_costs ./vm_types2, ./vm_gas_costs
import eth/common/transaction as common_transaction import eth/common/transaction as common_transaction
export common_transaction export common_transaction
template txc(fn: untyped, params: varargs[untyped]): untyped =
if tx.txType == LegacyTxType:
unpackArgs(fn, [tx.legacyTx, params])
else:
unpackArgs(fn, [tx.accessListTx, params])
template txField(field: untyped): untyped =
if tx.txType == LegacyTxType:
tx.legacyTx.field
else:
tx.accessListTx.field
template txFieldAsgn(field, data: untyped) =
if tx.txType == LegacyTxType:
tx.legacyTx.field = data
else:
tx.accessListTx.field = data
func intrinsicGas*(data: openarray[byte], fork: Fork): GasInt = func intrinsicGas*(data: openarray[byte], fork: Fork): GasInt =
result = gasFees[fork][GasTransaction] result = gasFees[fork][GasTransaction]
for i in data: for i in data:
@ -20,7 +39,7 @@ func intrinsicGas*(data: openarray[byte], fork: Fork): GasInt =
else: else:
result += gasFees[fork][GasTXDataNonZero] result += gasFees[fork][GasTXDataNonZero]
proc intrinsicGas*(tx: Transaction, fork: Fork): GasInt = proc intrinsicGas*(tx: TxTypes, fork: Fork): GasInt =
# Compute the baseline gas cost for this transaction. This is the amount # 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 # of gas needed to send this transaction (but that is not actually used
# for computation) # for computation)
@ -29,13 +48,16 @@ proc intrinsicGas*(tx: Transaction, fork: Fork): GasInt =
if tx.isContractCreation: if tx.isContractCreation:
result = result + gasFees[fork][GasTXCreate] result = result + gasFees[fork][GasTXCreate]
proc getSignature*(transaction: Transaction, output: var Signature): bool = proc intrinsicGas*(tx: Transaction, fork: Fork): GasInt =
txc(intrinsicGas, fork)
proc getSignature*(tx: LegacyTx, output: var Signature): bool =
var bytes: array[65, byte] var bytes: array[65, byte]
bytes[0..31] = transaction.R.toByteArrayBE() bytes[0..31] = tx.R.toByteArrayBE()
bytes[32..63] = transaction.S.toByteArrayBE() bytes[32..63] = tx.S.toByteArrayBE()
# TODO: V will become a byte or range soon. # TODO: V will become a byte or range soon.
var v = transaction.V.int var v = tx.V
if v >= EIP155_CHAIN_ID_OFFSET: if v >= EIP155_CHAIN_ID_OFFSET:
v = 28 - (v and 0x01) v = 28 - (v and 0x01)
elif v == 27 or v == 28: elif v == 27 or v == 28:
@ -50,32 +72,76 @@ proc getSignature*(transaction: Transaction, output: var Signature): bool =
return true return true
return false return false
proc toSignature*(transaction: Transaction): Signature = proc getSignature*(tx: AccessListTx, output: var Signature): bool =
if not getSignature(transaction, result): var bytes: array[65, byte]
bytes[0..31] = tx.R.toByteArrayBE()
bytes[32..63] = tx.S.toByteArrayBE()
bytes[64] = tx.V.byte
let sig = Signature.fromRaw(bytes)
if sig.isOk:
output = sig[]
return true
return false
proc getSignature*(tx: Transaction, output: var Signature): bool =
txc(getSignature, output)
proc toSignature*(tx: Transaction): Signature =
if not getSignature(tx, result):
raise newException(Exception, "Invalid signature") raise newException(Exception, "Invalid signature")
proc getSender*(transaction: Transaction, output: var EthAddress): bool = proc getSender*(tx: LegacyTx | AccessListTx | Transaction, output: var EthAddress): bool =
## Find the address the transaction was sent from. ## Find the address the transaction was sent from.
var sig: Signature var sig: Signature
if transaction.getSignature(sig): if tx.getSignature(sig):
var txHash = transaction.txHashNoSignature var txHash = tx.txHashNoSignature
let pubkey = recover(sig, SkMessage(txHash.data)) let pubkey = recover(sig, SkMessage(txHash.data))
if pubkey.isOk: if pubkey.isOk:
output = pubkey[].toCanonicalAddress() output = pubkey[].toCanonicalAddress()
result = true result = true
proc getSender*(transaction: Transaction): EthAddress = proc getSender*(tx: LegacyTx | AccessListTx | Transaction): EthAddress =
## Raises error on failure to recover public key ## Raises error on failure to recover public key
if not transaction.getSender(result): if not tx.getSender(result):
raise newException(ValidationError, "Could not derive sender address from transaction") raise newException(ValidationError, "Could not derive sender address from transaction")
proc getRecipient*(tx: Transaction, sender: EthAddress): EthAddress = proc getRecipient*(tx: LegacyTx | AccessListTx, sender: EthAddress): EthAddress =
if tx.isContractCreation: if tx.isContractCreation:
result = generateAddress(sender, tx.accountNonce) result = generateAddress(sender, tx.nonce)
else: else:
result = tx.to result = tx.to
proc validate*(tx: Transaction, fork: Fork) = proc getRecipient*(tx: Transaction, sender: EthAddress): EthAddress =
txc(getRecipient, sender)
proc gasLimit*(tx: Transaction): GasInt =
txField(gasLimit)
proc gasPrice*(tx: Transaction): GasInt =
txField(gasPrice)
proc value*(tx: Transaction): UInt256 =
txField(value)
proc isContractCreation*(tx: Transaction): bool =
txField(isContractCreation)
proc to*(tx: Transaction): EthAddress =
txField(to)
proc payload*(tx: Transaction): Blob =
txField(payload)
proc nonce*(tx: Transaction): AccountNonce =
txField(nonce)
proc `payload=`*(tx: var Transaction, data: Blob) =
txFieldAsgn(payload, data)
proc `gasLimit=`*(tx: var Transaction, data: GasInt) =
txFieldAsgn(gasLimit, data)
proc validate*(tx: LegacyTx, fork: Fork) =
# Hook called during instantiation to ensure that all transaction # Hook called during instantiation to ensure that all transaction
# parameters pass validation rules # parameters pass validation rules
if tx.intrinsicGas(fork) > tx.gasLimit: if tx.intrinsicGas(fork) > tx.gasLimit:
@ -87,18 +153,18 @@ proc validate*(tx: Transaction, fork: Fork) =
raise newException(ValidationError, "Invalid signature or failed message verification") raise newException(ValidationError, "Invalid signature or failed message verification")
var var
vMin = 27 vMin = 27'i64
vMax = 28 vMax = 28'i64
if tx.V.int >= EIP155_CHAIN_ID_OFFSET: if tx.V >= EIP155_CHAIN_ID_OFFSET:
let chainId = (tx.V.int - EIP155_CHAIN_ID_OFFSET) div 2 let chainId = (tx.V - EIP155_CHAIN_ID_OFFSET) div 2
vMin = 35 + (2 * chainId) vMin = 35 + (2 * chainId)
vMax = vMin + 1 vMax = vMin + 1
var isValid = tx.R >= Uint256.one var isValid = tx.R >= Uint256.one
isValid = isValid and tx.S >= Uint256.one isValid = isValid and tx.S >= Uint256.one
isValid = isValid and tx.V.int >= vMin isValid = isValid and tx.V >= vMin
isValid = isValid and tx.V.int <= vMax isValid = isValid and tx.V <= vMax
isValid = isValid and tx.S < SECPK1_N isValid = isValid and tx.S < SECPK1_N
isValid = isValid and tx.R < SECPK1_N isValid = isValid and tx.R < SECPK1_N
@ -107,4 +173,3 @@ proc validate*(tx: Transaction, fork: Fork) =
if not isValid: if not isValid:
raise newException(ValidationError, "Invalid transaction") raise newException(ValidationError, "Invalid transaction")

View File

@ -217,7 +217,7 @@ proc asmSetupComputation(tx: Transaction, sender: EthAddress, vmState: BaseVMSta
forkOverride = forkOverride, forkOverride = forkOverride,
) )
let contractAddress = generateAddress(sender, tx.accountNonce) let contractAddress = generateAddress(sender, tx.nonce)
let msg = Message( let msg = Message(
kind: evmcCall, kind: evmcCall,
depth: 0, depth: 0,

View File

@ -535,7 +535,7 @@ template gasCosts(fork: Fork, prefix, ResultGasCostsName: untyped) =
Number: fixed GasBase, Number: fixed GasBase,
Difficulty: fixed GasBase, Difficulty: fixed GasBase,
GasLimit: fixed GasBase, GasLimit: fixed GasBase,
ChainID: fixed GasBase, ChainIdOp: fixed GasBase,
SelfBalance: fixed GasLow, SelfBalance: fixed GasLow,
# 50s: Stack, Memory, Storage and Flow Operations # 50s: Stack, Memory, Storage and Flow Operations

View File

@ -79,7 +79,7 @@ fill_enum_holes:
Difficulty = 0x44, # Get the block's difficulty. Difficulty = 0x44, # Get the block's difficulty.
GasLimit = 0x45, # Get the block's gas limit. GasLimit = 0x45, # Get the block's gas limit.
ChainId = 0x46, # Get current chains EIP-155 unique identifier. ChainIdOp = 0x46, # Get current chains EIP-155 unique identifier.
SelfBalance = 0x47, # Get current contract's balance. SelfBalance = 0x47, # Get current contract's balance.
# 50s: Stack, Memory, Storage and Flow Operations # 50s: Stack, Memory, Storage and Flow Operations

View File

@ -223,7 +223,7 @@ let PetersburgOpDispatch {.compileTime.}: array[Op, NimNode] = genPetersburgJump
proc genIstanbulJumpTable(ops: array[Op, NimNode]): array[Op, NimNode] {.compileTime.} = proc genIstanbulJumpTable(ops: array[Op, NimNode]): array[Op, NimNode] {.compileTime.} =
result = ops result = ops
result[ChainId] = newIdentNode "chainId" result[ChainIdOp] = newIdentNode "chainId"
result[SelfBalance] = newIdentNode "selfBalance" result[SelfBalance] = newIdentNode "selfBalance"
result[SStore] = newIdentNode "sstoreEIP2200" result[SStore] = newIdentNode "sstoreEIP2200"

View File

@ -81,23 +81,25 @@ proc parseBlockHeader*(n: JsonNode): BlockHeader =
n.fromJson "nonce", result.nonce n.fromJson "nonce", result.nonce
proc parseTransaction*(n: JsonNode): Transaction = proc parseTransaction*(n: JsonNode): Transaction =
n.fromJson "nonce", result.accountNonce var tx: LegacyTx
n.fromJson "gasPrice", result.gasPrice n.fromJson "nonce", tx.nonce
n.fromJson "gas", result.gasLimit n.fromJson "gasPrice", tx.gasPrice
n.fromJson "gas", tx.gasLimit
result.isContractCreation = n["to"].kind == JNull tx.isContractCreation = n["to"].kind == JNull
if not result.isContractCreation: if not tx.isContractCreation:
n.fromJson "to", result.to n.fromJson "to", tx.to
n.fromJson "value", result.value n.fromJson "value", tx.value
n.fromJson "input", result.payload n.fromJson "input", tx.payload
n.fromJson "v", result.V n.fromJson "v", tx.V
n.fromJson "r", result.R n.fromJson "r", tx.R
n.fromJson "s", result.S n.fromJson "s", tx.S
var sender = result.getSender() var sender = tx.getSender()
doAssert sender.prefixHex == n["from"].getStr() doAssert sender.prefixHex == n["from"].getStr()
doAssert n["hash"].getStr() == result.rlpHash().prefixHex doAssert n["hash"].getStr() == tx.rlpHash().prefixHex
result = Transaction(txType: LegacyTxType, legacyTx: tx)
proc parseLog(n: JsonNode): Log = proc parseLog(n: JsonNode): Log =
n.fromJson "address", result.address n.fromJson "address", result.address
@ -118,18 +120,20 @@ proc parseLogs(n: JsonNode): seq[Log] =
result = @[] result = @[]
proc parseReceipt*(n: JsonNode): Receipt = proc parseReceipt*(n: JsonNode): Receipt =
var rec: LegacyReceipt
if n.hasKey("root"): if n.hasKey("root"):
var hash: Hash256 var hash: Hash256
n.fromJson "root", hash n.fromJson "root", hash
result.stateRootOrStatus = hashOrStatus(hash) rec.stateRootOrStatus = hashOrStatus(hash)
else: else:
var status: int var status: int
n.fromJson "status", status n.fromJson "status", status
result.stateRootOrStatus = hashOrStatus(status == 1) rec.stateRootOrStatus = hashOrStatus(status == 1)
n.fromJson "cumulativeGasUsed", result.cumulativeGasUsed n.fromJson "cumulativeGasUsed", rec.cumulativeGasUsed
n.fromJson "logsBloom", result.bloom n.fromJson "logsBloom", rec.bloom
result.logs = parseLogs(n["logs"]) rec.logs = parseLogs(n["logs"])
Receipt(receiptType: LegacyReceiptType, legacyReceipt: rec)
proc headerHash*(n: JsonNode): Hash256 = proc headerHash*(n: JsonNode): Hash256 =
n.fromJson "hash", result n.fromJson "hash", result

View File

@ -525,7 +525,7 @@ proc runTester(tester: var Tester, chainDB: BaseChainDB, testStatusIMPL: var Tes
try: try:
let (_, _, _) = tester.applyFixtureBlockToChain(testBlock, let (_, _, _) = tester.applyFixtureBlockToChain(testBlock,
chainDB, checkSeal, validation = true, testStatusIMPL) chainDB, checkSeal, validation = true, testStatusIMPL)
except ValueError, ValidationError, BlockNotFound, MalformedRlpError, RlpTypeMismatch: except ValueError, ValidationError, BlockNotFound, RlpError:
# failure is expected on this bad block # failure is expected on this bad block
check (testBlock.hasException or (not testBlock.goodBlock)) check (testBlock.hasException or (not testBlock.goodBlock))
noError = false noError = false
@ -601,7 +601,7 @@ proc testFixture(node: JsonNode, testStatusIMPL: var TestStatus, debugMode = fal
var success = true var success = true
try: try:
tester.runTester(chainDB, testStatusIMPL) tester.runTester(chainDB, testStatusIMPL)
success = testStatusIMPL == OK
let latestBlockHash = chainDB.getCanonicalHead().blockHash let latestBlockHash = chainDB.getCanonicalHead().blockHash
if latestBlockHash != tester.lastBlockHash: if latestBlockHash != tester.lastBlockHash:
verifyStateDB(fixture["postState"], tester.vmState.readOnlyStateDB) verifyStateDB(fixture["postState"], tester.vmState.readOnlyStateDB)

View File

@ -184,32 +184,91 @@ proc verifyStateDB*(wantedState: JsonNode, stateDB: ReadOnlyStateDB) =
if wantedNonce != actualNonce: if wantedNonce != actualNonce:
raise newException(ValidationError, &"{ac} nonceDiff {wantedNonce.toHex} != {actualNonce.toHex}") raise newException(ValidationError, &"{ac} nonceDiff {wantedNonce.toHex} != {actualNonce.toHex}")
proc parseAccessList(n: JsonNode): AccessList =
if n.kind == JNull:
return
for x in n:
var ap = AccessPair(
address: parseAddress(x["address"].getStr)
)
let sks = x["storageKeys"]
for sk in sks:
ap.storageKeys.add hexToByteArray[32](sk.getStr())
result.add ap
proc signTx(tx: var LegacyTx, privateKey: PrivateKey) =
let sig = sign(privateKey, tx.rlpEncode)
let raw = sig.toRaw()
tx.R = fromBytesBE(Uint256, raw[0..31])
tx.S = fromBytesBE(Uint256, raw[32..63])
tx.V = raw[64].int64 + 27
proc signTx(tx: var AccessListTx, privateKey: PrivateKey) =
let sig = sign(privateKey, tx.rlpEncode)
let raw = sig.toRaw()
tx.R = fromBytesBE(Uint256, raw[0..31])
tx.S = fromBytesBE(Uint256, raw[32..63])
tx.V = raw[64].int64
proc getFixtureTransaction*(j: JsonNode, dataIndex, gasIndex, valueIndex: int): Transaction = proc getFixtureTransaction*(j: JsonNode, dataIndex, gasIndex, valueIndex: int): Transaction =
result.accountNonce = j["nonce"].getHexadecimalInt.AccountNonce let nonce = j["nonce"].getHexadecimalInt.AccountNonce
result.gasPrice = j["gasPrice"].getHexadecimalInt let gasPrice = j["gasPrice"].getHexadecimalInt
result.gasLimit = j["gasLimit"][gasIndex].getHexadecimalInt let gasLimit = j["gasLimit"][gasIndex].getHexadecimalInt
var toAddr: EthAddress
var contract: bool
# TODO: there are a couple fixtures which appear to distinguish between # TODO: there are a couple fixtures which appear to distinguish between
# empty and 0 transaction.to; check/verify whether correct conditions. # empty and 0 transaction.to; check/verify whether correct conditions.
let rawTo = j["to"].getStr let rawTo = j["to"].getStr
if rawTo == "": if rawTo == "":
result.to = "0x".parseAddress toAddr = "0x".parseAddress
result.isContractCreation = true contract = true
else: else:
result.to = rawTo.parseAddress toAddr = rawTo.parseAddress
result.isContractCreation = false contract = false
result.value = fromHex(UInt256, j["value"][valueIndex].getStr)
result.payload = j["data"][dataIndex].getStr.safeHexToSeqByte let value = fromHex(UInt256, j["value"][valueIndex].getStr)
let payload = j["data"][dataIndex].getStr.safeHexToSeqByte
var secretKey = j["secretKey"].getStr var secretKey = j["secretKey"].getStr
removePrefix(secretKey, "0x") removePrefix(secretKey, "0x")
let privateKey = PrivateKey.fromHex(secretKey).tryGet() let privateKey = PrivateKey.fromHex(secretKey).tryGet()
let sig = sign(privateKey, result.rlpEncode)
let raw = sig.toRaw()
result.R = fromBytesBE(Uint256, raw[0..31]) if j.hasKey("accessLists"):
result.S = fromBytesBE(Uint256, raw[32..63]) let accList = j["accessLists"][dataIndex]
result.V = raw[64] + 27.byte var tx = AccessListTx(
nonce: nonce,
gasPrice: gasPrice,
gasLimit: gasLimit,
to: toAddr,
isContractCreation: contract,
value: value,
payload: payload,
accessList: parseAccessList(accList),
chainId: common.ChainId(1)
)
signTx(tx, privateKey)
Transaction(
txType: AccessListTxType,
accessListTx: tx
)
else:
var tx = LegacyTx(
nonce: nonce,
gasPrice: gasPrice,
gasLimit: gasLimit,
to: toAddr,
isContractCreation: contract,
value: value,
payload: payload
)
signTx(tx, privateKey)
Transaction(
txType: LegacyTxType,
legacyTx: tx
)
proc hashLogEntries*(logs: seq[Log]): string = proc hashLogEntries*(logs: seq[Log]): string =
toLowerAscii("0x" & $keccakHash(rlp.encode(logs))) toLowerAscii("0x" & $keccakHash(rlp.encode(logs)))