From 79044f1e92d6c5d6b2bac7f77661f4c64df6247b Mon Sep 17 00:00:00 2001 From: jangko Date: Sat, 15 May 2021 13:37:40 +0700 Subject: [PATCH] eip2718: test_blockchain_json pass test --- nimbus/chain_config.nim | 5 -- nimbus/p2p/executor.nim | 37 +++++--- nimbus/transaction.nim | 109 +++++++++++++++++++----- nimbus/transaction/call_evm.nim | 2 +- nimbus/vm/interpreter/gas_costs.nim | 2 +- nimbus/vm/interpreter/opcode_values.nim | 2 +- nimbus/vm/interpreter_dispatch.nim | 2 +- premix/parser.nim | 40 +++++---- tests/test_blockchain_json.nim | 4 +- tests/test_helpers.nim | 87 ++++++++++++++++--- 10 files changed, 212 insertions(+), 78 deletions(-) diff --git a/nimbus/chain_config.nim b/nimbus/chain_config.nim index f65d13e13..53e972afc 100644 --- a/nimbus/chain_config.nim +++ b/nimbus/chain_config.nim @@ -17,11 +17,6 @@ import json_serialization/lexer type - # beware that although in some cases - # chainId have identical value to networkId - # they are separate entity - ChainId* = distinct uint - ChainOptions = object chainId : ChainId homesteadBlock : Option[BlockNumber] diff --git a/nimbus/p2p/executor.nim b/nimbus/p2p/executor.nim index 63b2d43d8..8e0accc40 100644 --- a/nimbus/p2p/executor.nim +++ b/nimbus/p2p/executor.nim @@ -40,9 +40,9 @@ proc validateTransaction*(vmState: BaseVMState, tx: Transaction, require=tx.intrinsicGas(fork) return - if tx.accountNonce != nonce: + if tx.nonce != nonce: debug "invalid tx: account nonce mismatch", - txNonce=tx.accountnonce, + txNonce=tx.nonce, accountNonce=nonce return @@ -96,19 +96,30 @@ func logsBloom(logs: openArray[Log]): LogsBloom = func createBloom*(receipts: openArray[Receipt]): Bloom = var bloom: LogsBloom - for receipt in receipts: - bloom.value = bloom.value or logsBloom(receipt.logs).value + for rec in receipts: + bloom.value = bloom.value or logsBloom(rec.logs).value result = bloom.value.toByteArrayBE -proc makeReceipt*(vmState: BaseVMState, fork = FkFrontier): Receipt = - if fork < FkByzantium: - result.stateRootOrStatus = hashOrStatus(vmState.accountDb.rootHash) - else: - result.stateRootOrStatus = hashOrStatus(vmState.status) +proc makeReceipt*(vmState: BaseVMState, fork: Fork, txType: TxType): Receipt = + if txType == AccessListTxType: + var rec = AccessListReceipt( + status: 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 - result.logs = vmState.getAndClearLogEntries() - result.bloom = logsBloom(result.logs).value.toByteArrayBE + var rec: LegacyReceipt + if fork < FkByzantium: + 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.} = n.u256 * pow(10.u256, 18) @@ -178,7 +189,7 @@ proc processBlock*(chainDB: BaseChainDB, header: BlockHeader, body: BlockBody, v else: debug "Could not get sender", txIndex, tx return ValidationResult.Error - vmState.receipts[txIndex] = makeReceipt(vmState, fork) + vmState.receipts[txIndex] = makeReceipt(vmState, fork, tx.txType) if header.ommersHash != EMPTY_UNCLE_HASH: let h = chainDB.persistUncles(body.uncles) diff --git a/nimbus/transaction.nim b/nimbus/transaction.nim index 0e9808613..8922f9ebf 100644 --- a/nimbus/transaction.nim +++ b/nimbus/transaction.nim @@ -7,11 +7,30 @@ import ./constants, ./errors, eth/[common, keys], ./utils, + stew/shims/macros, ./vm_types2, ./vm_gas_costs import eth/common/transaction as 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 = result = gasFees[fork][GasTransaction] for i in data: @@ -20,7 +39,7 @@ func intrinsicGas*(data: openarray[byte], fork: Fork): GasInt = else: 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 # of gas needed to send this transaction (but that is not actually used # for computation) @@ -29,13 +48,16 @@ proc intrinsicGas*(tx: Transaction, fork: Fork): GasInt = if tx.isContractCreation: 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] - bytes[0..31] = transaction.R.toByteArrayBE() - bytes[32..63] = transaction.S.toByteArrayBE() + bytes[0..31] = tx.R.toByteArrayBE() + bytes[32..63] = tx.S.toByteArrayBE() # TODO: V will become a byte or range soon. - var v = transaction.V.int + var v = tx.V if v >= EIP155_CHAIN_ID_OFFSET: v = 28 - (v and 0x01) elif v == 27 or v == 28: @@ -50,32 +72,76 @@ proc getSignature*(transaction: Transaction, output: var Signature): bool = return true return false -proc toSignature*(transaction: Transaction): Signature = - if not getSignature(transaction, result): +proc getSignature*(tx: AccessListTx, output: var Signature): bool = + 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") -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. var sig: Signature - if transaction.getSignature(sig): - var txHash = transaction.txHashNoSignature + if tx.getSignature(sig): + var txHash = tx.txHashNoSignature let pubkey = recover(sig, SkMessage(txHash.data)) if pubkey.isOk: output = pubkey[].toCanonicalAddress() result = true -proc getSender*(transaction: Transaction): EthAddress = +proc getSender*(tx: LegacyTx | AccessListTx | Transaction): EthAddress = ## 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") -proc getRecipient*(tx: Transaction, sender: EthAddress): EthAddress = +proc getRecipient*(tx: LegacyTx | AccessListTx, sender: EthAddress): EthAddress = if tx.isContractCreation: - result = generateAddress(sender, tx.accountNonce) + result = generateAddress(sender, tx.nonce) else: 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 # parameters pass validation rules 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") var - vMin = 27 - vMax = 28 + vMin = 27'i64 + vMax = 28'i64 - if tx.V.int >= EIP155_CHAIN_ID_OFFSET: - let chainId = (tx.V.int - EIP155_CHAIN_ID_OFFSET) div 2 + if tx.V >= EIP155_CHAIN_ID_OFFSET: + let chainId = (tx.V - EIP155_CHAIN_ID_OFFSET) div 2 vMin = 35 + (2 * chainId) vMax = vMin + 1 var isValid = tx.R >= Uint256.one isValid = isValid and tx.S >= Uint256.one - isValid = isValid and tx.V.int >= vMin - isValid = isValid and tx.V.int <= vMax + isValid = isValid and tx.V >= vMin + isValid = isValid and tx.V <= vMax isValid = isValid and tx.S < SECPK1_N isValid = isValid and tx.R < SECPK1_N @@ -107,4 +173,3 @@ proc validate*(tx: Transaction, fork: Fork) = if not isValid: raise newException(ValidationError, "Invalid transaction") - diff --git a/nimbus/transaction/call_evm.nim b/nimbus/transaction/call_evm.nim index 10b8d8695..8b1acf367 100644 --- a/nimbus/transaction/call_evm.nim +++ b/nimbus/transaction/call_evm.nim @@ -217,7 +217,7 @@ proc asmSetupComputation(tx: Transaction, sender: EthAddress, vmState: BaseVMSta forkOverride = forkOverride, ) - let contractAddress = generateAddress(sender, tx.accountNonce) + let contractAddress = generateAddress(sender, tx.nonce) let msg = Message( kind: evmcCall, depth: 0, diff --git a/nimbus/vm/interpreter/gas_costs.nim b/nimbus/vm/interpreter/gas_costs.nim index 1d37ee466..fbb16096e 100644 --- a/nimbus/vm/interpreter/gas_costs.nim +++ b/nimbus/vm/interpreter/gas_costs.nim @@ -535,7 +535,7 @@ template gasCosts(fork: Fork, prefix, ResultGasCostsName: untyped) = Number: fixed GasBase, Difficulty: fixed GasBase, GasLimit: fixed GasBase, - ChainID: fixed GasBase, + ChainIdOp: fixed GasBase, SelfBalance: fixed GasLow, # 50s: Stack, Memory, Storage and Flow Operations diff --git a/nimbus/vm/interpreter/opcode_values.nim b/nimbus/vm/interpreter/opcode_values.nim index 5b6aa0e22..7ecbce36a 100644 --- a/nimbus/vm/interpreter/opcode_values.nim +++ b/nimbus/vm/interpreter/opcode_values.nim @@ -79,7 +79,7 @@ fill_enum_holes: Difficulty = 0x44, # Get the block's difficulty. GasLimit = 0x45, # Get the block's gas limit. - ChainId = 0x46, # Get current chain’s EIP-155 unique identifier. + ChainIdOp = 0x46, # Get current chain’s EIP-155 unique identifier. SelfBalance = 0x47, # Get current contract's balance. # 50s: Stack, Memory, Storage and Flow Operations diff --git a/nimbus/vm/interpreter_dispatch.nim b/nimbus/vm/interpreter_dispatch.nim index b3397095e..b06adc3ca 100644 --- a/nimbus/vm/interpreter_dispatch.nim +++ b/nimbus/vm/interpreter_dispatch.nim @@ -223,7 +223,7 @@ let PetersburgOpDispatch {.compileTime.}: array[Op, NimNode] = genPetersburgJump proc genIstanbulJumpTable(ops: array[Op, NimNode]): array[Op, NimNode] {.compileTime.} = result = ops - result[ChainId] = newIdentNode "chainId" + result[ChainIdOp] = newIdentNode "chainId" result[SelfBalance] = newIdentNode "selfBalance" result[SStore] = newIdentNode "sstoreEIP2200" diff --git a/premix/parser.nim b/premix/parser.nim index 75522fb99..84866e483 100644 --- a/premix/parser.nim +++ b/premix/parser.nim @@ -81,23 +81,25 @@ proc parseBlockHeader*(n: JsonNode): BlockHeader = n.fromJson "nonce", result.nonce proc parseTransaction*(n: JsonNode): Transaction = - n.fromJson "nonce", result.accountNonce - n.fromJson "gasPrice", result.gasPrice - n.fromJson "gas", result.gasLimit + var tx: LegacyTx + n.fromJson "nonce", tx.nonce + n.fromJson "gasPrice", tx.gasPrice + n.fromJson "gas", tx.gasLimit - result.isContractCreation = n["to"].kind == JNull - if not result.isContractCreation: - n.fromJson "to", result.to + tx.isContractCreation = n["to"].kind == JNull + if not tx.isContractCreation: + n.fromJson "to", tx.to - n.fromJson "value", result.value - n.fromJson "input", result.payload - n.fromJson "v", result.V - n.fromJson "r", result.R - n.fromJson "s", result.S + n.fromJson "value", tx.value + n.fromJson "input", tx.payload + n.fromJson "v", tx.V + n.fromJson "r", tx.R + n.fromJson "s", tx.S - var sender = result.getSender() + var sender = tx.getSender() 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 = n.fromJson "address", result.address @@ -118,18 +120,20 @@ proc parseLogs(n: JsonNode): seq[Log] = result = @[] proc parseReceipt*(n: JsonNode): Receipt = + var rec: LegacyReceipt if n.hasKey("root"): var hash: Hash256 n.fromJson "root", hash - result.stateRootOrStatus = hashOrStatus(hash) + rec.stateRootOrStatus = hashOrStatus(hash) else: var status: int n.fromJson "status", status - result.stateRootOrStatus = hashOrStatus(status == 1) + rec.stateRootOrStatus = hashOrStatus(status == 1) - n.fromJson "cumulativeGasUsed", result.cumulativeGasUsed - n.fromJson "logsBloom", result.bloom - result.logs = parseLogs(n["logs"]) + n.fromJson "cumulativeGasUsed", rec.cumulativeGasUsed + n.fromJson "logsBloom", rec.bloom + rec.logs = parseLogs(n["logs"]) + Receipt(receiptType: LegacyReceiptType, legacyReceipt: rec) proc headerHash*(n: JsonNode): Hash256 = n.fromJson "hash", result diff --git a/tests/test_blockchain_json.nim b/tests/test_blockchain_json.nim index 78dc39bbc..93fddfb1f 100644 --- a/tests/test_blockchain_json.nim +++ b/tests/test_blockchain_json.nim @@ -525,7 +525,7 @@ proc runTester(tester: var Tester, chainDB: BaseChainDB, testStatusIMPL: var Tes try: let (_, _, _) = tester.applyFixtureBlockToChain(testBlock, chainDB, checkSeal, validation = true, testStatusIMPL) - except ValueError, ValidationError, BlockNotFound, MalformedRlpError, RlpTypeMismatch: + except ValueError, ValidationError, BlockNotFound, RlpError: # failure is expected on this bad block check (testBlock.hasException or (not testBlock.goodBlock)) noError = false @@ -601,7 +601,7 @@ proc testFixture(node: JsonNode, testStatusIMPL: var TestStatus, debugMode = fal var success = true try: tester.runTester(chainDB, testStatusIMPL) - + success = testStatusIMPL == OK let latestBlockHash = chainDB.getCanonicalHead().blockHash if latestBlockHash != tester.lastBlockHash: verifyStateDB(fixture["postState"], tester.vmState.readOnlyStateDB) diff --git a/tests/test_helpers.nim b/tests/test_helpers.nim index 615d18a23..0090a52cf 100644 --- a/tests/test_helpers.nim +++ b/tests/test_helpers.nim @@ -184,32 +184,91 @@ proc verifyStateDB*(wantedState: JsonNode, stateDB: ReadOnlyStateDB) = if wantedNonce != actualNonce: 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 = - result.accountNonce = j["nonce"].getHexadecimalInt.AccountNonce - result.gasPrice = j["gasPrice"].getHexadecimalInt - result.gasLimit = j["gasLimit"][gasIndex].getHexadecimalInt + let nonce = j["nonce"].getHexadecimalInt.AccountNonce + let gasPrice = j["gasPrice"].getHexadecimalInt + let gasLimit = j["gasLimit"][gasIndex].getHexadecimalInt + + var toAddr: EthAddress + var contract: bool # TODO: there are a couple fixtures which appear to distinguish between # empty and 0 transaction.to; check/verify whether correct conditions. let rawTo = j["to"].getStr if rawTo == "": - result.to = "0x".parseAddress - result.isContractCreation = true + toAddr = "0x".parseAddress + contract = true else: - result.to = rawTo.parseAddress - result.isContractCreation = false - result.value = fromHex(UInt256, j["value"][valueIndex].getStr) - result.payload = j["data"][dataIndex].getStr.safeHexToSeqByte + toAddr = rawTo.parseAddress + contract = false + + let value = fromHex(UInt256, j["value"][valueIndex].getStr) + let payload = j["data"][dataIndex].getStr.safeHexToSeqByte var secretKey = j["secretKey"].getStr removePrefix(secretKey, "0x") let privateKey = PrivateKey.fromHex(secretKey).tryGet() - let sig = sign(privateKey, result.rlpEncode) - let raw = sig.toRaw() - result.R = fromBytesBE(Uint256, raw[0..31]) - result.S = fromBytesBE(Uint256, raw[32..63]) - result.V = raw[64] + 27.byte + if j.hasKey("accessLists"): + let accList = j["accessLists"][dataIndex] + 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 = toLowerAscii("0x" & $keccakHash(rlp.encode(logs)))