Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add eth_getTransactionByHash endpoint #90

Merged
merged 5 commits into from
Apr 17, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion packages/ovm/src/app/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,12 @@ export const getOvmTransactionMetadata = (
* Converts an EVM receipt to an OVM receipt.
*
* @param internalTxReceipt The EVM tx receipt to convert to an OVM tx receipt
* @param ovmTxHash The OVM tx hash to replace the internal tx hash with.
* @returns The converted receipt
*/
export const internalTxReceiptToOvmTxReceipt = async (
internalTxReceipt: TransactionReceipt
internalTxReceipt: TransactionReceipt,
ovmTxHash?: string
): Promise<OvmTransactionReceipt> => {
const ovmTransactionMetadata = getOvmTransactionMetadata(internalTxReceipt)
// Construct a new receipt
Expand All @@ -187,6 +189,10 @@ export const internalTxReceiptToOvmTxReceipt = async (

ovmTxReceipt.status = ovmTransactionMetadata.ovmTxSucceeded ? 1 : 0

if (!!ovmTxReceipt.transactionHash && !!ovmTxHash) {
ovmTxReceipt.transactionHash = ovmTxHash
}

if (ovmTransactionMetadata.revertMessage !== undefined) {
ovmTxReceipt.revertMessage = ovmTransactionMetadata.revertMessage
}
Expand Down
28 changes: 20 additions & 8 deletions packages/ovm/src/contracts/L2ExecutionManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {ExecutionManager} from "./ExecutionManager.sol";
*/
contract L2ExecutionManager is ExecutionManager {
mapping(bytes32 => bytes32) ovmHashToEvmHash;
mapping(bytes32 => bytes) ovmHashToOvmTx;

constructor(
uint256 _opcodeWhitelistMask,
Expand All @@ -20,21 +21,32 @@ contract L2ExecutionManager is ExecutionManager {
) ExecutionManager(_opcodeWhitelistMask, _owner, _gasLimit, _overridePurityChecker) public {}

/**
@notice Associates the provided OVM transaction hash with the EVM transaction hash so that we can properly
look up transaction receipts based on the OVM transaction hash.
@notice Stores the provided OVM transaction, mapping its hash to its value and its hash to the EVM tx
willmeister marked this conversation as resolved.
Show resolved Hide resolved
with which it's associated.
@param ovmTransactionHash The OVM transaction hash, used publicly as the reference to the transaction.
@param internalTransactionHash The internal transaction hash of the transaction actually executed.
@param signedOvmTx The signed OVM tx that we received
*/
function mapOvmTransactionHashToInternalTransactionHash(bytes32 ovmTransactionHash, bytes32 internalTransactionHash) public {
function storeOvmTransaction(bytes32 ovmTransactionHash, bytes32 internalTransactionHash, bytes memory signedOvmTx) public {
ovmHashToEvmHash[ovmTransactionHash] = internalTransactionHash;
ovmHashToOvmTx[ovmTransactionHash] = signedOvmTx;
}

/**
@notice Gets the EVM transaction hash associated with the provided OVM transaction hash.
@param ovmTransactionHash The OVM transaction hash.
@return The associated EVM transaction hash.
*/
/**
@notice Gets the EVM transaction hash associated with the provided OVM transaction hash.
@param ovmTransactionHash The OVM transaction hash.
@return The associated EVM transaction hash.
*/
function getInternalTransactionHash(bytes32 ovmTransactionHash) public view returns (bytes32) {
return ovmHashToEvmHash[ovmTransactionHash];
}

/**
@notice Gets the OVM transaction associated with the provided OVM transaction hash.
@param ovmTransactionHash The OVM transaction hash.
@return The associated signed OVM transaction.
*/
function getOvmTransaction(bytes32 ovmTransactionHash) public view returns (bytes memory) {
return ovmHashToOvmTx[ovmTransactionHash];
}
}
70 changes: 61 additions & 9 deletions packages/rollup-full-node/src/app/web3-rpc-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ export class DefaultWeb3Handler
args = this.assertParameters(params, 1)
response = await this.getLogs([0])
break
case Web3RpcMethods.getTransactionByHash:
args = this.assertParameters(params, 1)
response = await this.getTransactionByHash(args[0])
break
case Web3RpcMethods.getTransactionCount:
args = this.assertParameters(params, 2, latestBlock)
response = await this.getTransactionCount(args[0], args[1])
Expand Down Expand Up @@ -423,6 +427,31 @@ export class DefaultWeb3Handler
return res
}

public async getTransactionByHash(ovmTxHash: string): Promise<any> {
log.debug('Getting tx for ovm tx hash:', ovmTxHash)
// First convert our ovmTxHash into an internalTxHash
const signedOvmTx: string = await this.getOvmTransactionByHash(ovmTxHash)

if (!signedOvmTx) {
log.debug(`There is no OVM tx associated with OVM tx hash [${ovmTxHash}]`)
return null
}

log.debug(
`OVM tx hash [${ovmTxHash}] is associated with signed OVM tx [${signedOvmTx}]`
)

const ovmTx = utils.parseTransaction(signedOvmTx)

log.debug(
`OVM tx hash [${ovmTxHash}] is associated with parsed OVM tx [${JSON.stringify(
ovmTx
)}]`
)

return ovmTx
}

public async getTransactionCount(
address: Address,
defaultBlock: string
Expand Down Expand Up @@ -464,7 +493,8 @@ export class DefaultWeb3Handler

// Now let's parse the internal transaction reciept
const ovmTxReceipt: OvmTransactionReceipt = await internalTxReceiptToOvmTxReceipt(
internalTxReceipt
internalTxReceipt,
ovmTxHash
)
if (ovmTxReceipt.revertMessage !== undefined && !includeRevertMessage) {
delete ovmTxReceipt.revertMessage
Expand Down Expand Up @@ -508,8 +538,6 @@ export class DefaultWeb3Handler
)}`
)

const wallet: Wallet = this.getNewWallet()

// Convert the OVM transaction into an "internal" tx which we can use for our execution manager
const internalTx = await this.ovmTxToInternalTx(ovmTx)
// Now compute the hash of the OVM transaction which we will return
Expand All @@ -523,7 +551,7 @@ export class DefaultWeb3Handler
)

// Make sure we have a way to look up our internal tx hash from the ovm tx hash.
await this.mapOvmTxHashToInternalTxHash(ovmTxHash, internalTxHash)
await this.storeOvmTransaction(ovmTxHash, internalTxHash, rawOvmTx)

let returnedInternalTxHash: string
try {
Expand Down Expand Up @@ -691,17 +719,22 @@ export class DefaultWeb3Handler
* @param internalTxHash Our internal transactions's hash.
* @throws if not stored properly
*/
private async mapOvmTxHashToInternalTxHash(
private async storeOvmTransaction(
ovmTxHash: string,
internalTxHash: string
internalTxHash: string,
signedOvmTransaction: string
): Promise<void> {
log.debug(
`Mapping ovmTxHash: ${ovmTxHash} to internal tx hash: ${internalTxHash}.`
)

const calldata: string = this.context.executionManager.interface.functions[
'mapOvmTransactionHashToInternalTransactionHash'
].encode([add0x(ovmTxHash), add0x(internalTxHash)])
'storeOvmTransaction'
].encode([
add0x(ovmTxHash),
add0x(internalTxHash),
add0x(signedOvmTransaction),
])

const signedTx = this.getSignedTransaction(
calldata,
Expand All @@ -726,12 +759,28 @@ export class DefaultWeb3Handler
}
}

/**
* Gets the internal EVM transaction hash for the provided OVM transaction hash, if one exists.
*
* @param ovmTxHash The OVM transaction hash
* @returns The EVM tx hash if one exists, else undefined.
*/
private async getInternalTxHash(ovmTxHash: string): Promise<string> {
return this.context.executionManager.getInternalTransactionHash(
add0x(ovmTxHash)
)
}

/**
* Gets the signed OVM transaction that we received by its hash.
*
* @param ovmTxHash The hash of the signed tx.
* @returns The signed OVM transaction if one exists, else undefined.
*/
private async getOvmTransactionByHash(ovmTxHash: string): Promise<string> {
return this.context.executionManager.getOvmTransaction(add0x(ovmTxHash))
}

/**
* Wraps the provided OVM transaction in a signed EVM transaction capable
* of execution within the L2 node.
Expand Down Expand Up @@ -848,7 +897,10 @@ export class DefaultWeb3Handler
return []
}
} else if (params.length === expected - 1 || params.length === expected) {
return params.length === expected ? params : [...params, defaultLast]
const nonEmptyParams = params.filter((x) => !!x)
return nonEmptyParams.length === expected
? nonEmptyParams
: [...nonEmptyParams, defaultLast]
}
throw new InvalidParametersError(
`Expected ${expected} parameters but received ${
Expand Down
2 changes: 2 additions & 0 deletions packages/rollup-full-node/src/types/web3-rpc-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface Web3Handler {
getCode(address: Address, defaultBlock: string): Promise<string>
getExecutionManagerAddress()
getLogs(filter: any): Promise<any[]>
getTransactionByHash(transactionHash: string): Promise<any>
getTransactionCount(address: Address, defaultBlock: string): Promise<string>
getTransactionReceipt(txHash: string): Promise<string>
networkVersion(): Promise<string>
Expand All @@ -37,6 +38,7 @@ export enum Web3RpcMethods {
getCode = 'eth_getCode',
getExecutionManagerAddress = 'ovm_getExecutionManagerAddress',
getLogs = 'eth_getLogs',
getTransactionByHash = 'eth_getTransactionByHash',
getTransactionCount = 'eth_getTransactionCount',
getTransactionReceipt = 'eth_getTransactionReceipt',
networkVersion = 'net_version',
Expand Down
84 changes: 75 additions & 9 deletions packages/rollup-full-node/test/app/web-rpc-handler.spec.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import '../setup'
/* External Imports */
import { getLogger, numberToHexString } from '@eth-optimism/core-utils'
import { ethers, ContractFactory, Wallet, Contract } from 'ethers'
import {
add0x,
getLogger,
keccak256,
numberToHexString,
ZERO_ADDRESS,
} from '@eth-optimism/core-utils'
import { CHAIN_ID } from '@eth-optimism/ovm'

import { ethers, ContractFactory, Wallet, Contract, utils } from 'ethers'
import { resolve } from 'path'
import * as rimraf from 'rimraf'
import * as fs from 'fs'
import assert from 'assert'

/* Internal Imports */
import {
FullnodeRpcServer,
DefaultWeb3Handler,
TestWeb3Handler,
} from '../../src/app'
import { FullnodeRpcServer, DefaultWeb3Handler } from '../../src/app'
import * as SimpleStorage from '../contracts/build/untranspiled/SimpleStorage.json'
import { Web3RpcMethods } from '../../src/types'

const log = getLogger('web3-handler', true)

Expand Down Expand Up @@ -68,14 +74,14 @@ const setStorage = async (
simpleStorage: Contract,
httpProvider,
executionManagerAddress
): Promise<void> => {
): Promise<any> => {
// Set storage with our new storage elements
const tx = await simpleStorage.setStorage(
executionManagerAddress,
storageKey,
storageValue
)
await httpProvider.getTransactionReceipt(tx.hash)
return httpProvider.getTransactionReceipt(tx.hash)
}

const getAndVerifyStorage = async (
Expand Down Expand Up @@ -210,6 +216,66 @@ describe('Web3Handler', () => {
})
})

describe('the eth_getTransactionByHash endpoint', () => {
it('should return null if no tx exists', async () => {
const garbageHash = add0x(
keccak256(Buffer.from('garbage').toString('hex'))
)
const txByHash = await httpProvider.send(
Web3RpcMethods.getTransactionByHash,
[garbageHash]
)

assert(
txByHash === null,
'Should not have gotten a tx for garbage hash!'
)
})

it('should return a tx by OVM hash', async () => {
const executionManagerAddress = await httpProvider.send(
'ovm_getExecutionManagerAddress',
[]
)
const wallet = getWallet(httpProvider)
const simpleStorage = await deploySimpleStorage(wallet)

const calldata = simpleStorage.interface.functions[
'setStorage'
].encode([executionManagerAddress, storageKey, storageValue])

const tx = {
nonce: await wallet.getTransactionCount(),
gasPrice: 0,
gasLimit: 9999999999,
to: executionManagerAddress,
data: calldata,
chainId: CHAIN_ID,
}

const signedTransaction = await wallet.sign(tx)

const hash = await httpProvider.send(
Web3RpcMethods.sendRawTransaction,
[signedTransaction]
)

await httpProvider.waitForTransaction(hash)

const returnedSignedTx = await httpProvider.send(
Web3RpcMethods.getTransactionByHash,
[hash]
)

const parsedSignedTx = utils.parseTransaction(signedTransaction)

JSON.stringify(parsedSignedTx).should.eq(
JSON.stringify(returnedSignedTx),
'Signed transactions do not match!'
)
})
})

describe('SimpleStorage integration test', () => {
it('should set storage & retrieve the value', async () => {
const executionManagerAddress = await httpProvider.send(
Expand Down