-
Notifications
You must be signed in to change notification settings - Fork 574
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
eth/sendTransaction RPC (WIP) (#5268)
* Add send transaction RPC * Fix lint
- Loading branch information
1 parent
6367504
commit aa24089
Showing
5 changed files
with
248 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
62 changes: 62 additions & 0 deletions
62
ironfish/src/rpc/routes/eth/__fixtures__/sendTransaction.test.ts.fixture
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
{ | ||
"Route eth/sendRawTransaction should construct evm transaction and submit to node": [ | ||
{ | ||
"value": { | ||
"version": 4, | ||
"id": "a564b402-05d4-409c-8870-38123af5df55", | ||
"name": "sender", | ||
"spendingKey": "0292797f0960c31dea187f003077e6b50060a0fb4c353ea7489686594db820fe", | ||
"viewKey": "c935c8075cc794bdc1828d9b8ba0f7c337ded09ccfd87408f9bb659d35cd583663a66a64ea5346dd90ba8e789835ac2eb3897def8f3e5f827706a8c30b4133e5", | ||
"incomingViewKey": "5081a2f748587124c358263159f9dc37dca2be3c1435ec7bc2582400991bc802", | ||
"outgoingViewKey": "ba97180b44cbe448bcc9d2e353df8a777972ce39b802f452389957b09111c53d", | ||
"publicAddress": "800243d1a8f126222e1a7e1a5d3c3ea1a8a960b173695176b29be6fc71847e37", | ||
"createdAt": { | ||
"hash": { | ||
"type": "Buffer", | ||
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY=" | ||
}, | ||
"sequence": 1 | ||
}, | ||
"scanningEnabled": true, | ||
"proofAuthorizingKey": "ce16da48daf6e5aebaa68baa7bb9cde2e37da86187253da1eee60dc4931e6d00" | ||
}, | ||
"head": { | ||
"hash": { | ||
"type": "Buffer", | ||
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY=" | ||
}, | ||
"sequence": 1 | ||
} | ||
} | ||
], | ||
"Route eth/sendRawTransaction should construct an evm shield transaction and submit to node": [ | ||
{ | ||
"value": { | ||
"version": 4, | ||
"id": "b8e50242-f166-457f-957b-2e11cf8ba92a", | ||
"name": "ifReceivingAccount", | ||
"spendingKey": "21044d211270de5755ce9117384db1803e70f2abbf0339c1b969cf6293f07440", | ||
"viewKey": "edbc41e9b9354e90a0bd85f72b01155b940cff688de69c87565b89297a7b25140d58fa27d5104bb9ac5c64effa1e92dc2a9943991221a96af3022a28806cdd9f", | ||
"incomingViewKey": "fb25acfbace7285463cc7b50d9553d7818f21f4355df252175d2eeff05c26c02", | ||
"outgoingViewKey": "7b7ef391d01fdc30834009acc2e768cef636d78f4cf67f1fb99997096dbd546b", | ||
"publicAddress": "9fa0f27a65d94e3f80dbb1c00a8d1ff14c33869f30cb3ed9412ac802146d9f8f", | ||
"createdAt": { | ||
"hash": { | ||
"type": "Buffer", | ||
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY=" | ||
}, | ||
"sequence": 1 | ||
}, | ||
"scanningEnabled": true, | ||
"proofAuthorizingKey": "97e9cf89bc242e78aa8505854411b59e7b76f5fef3115907c44e8bdbdb7fe503" | ||
}, | ||
"head": { | ||
"hash": { | ||
"type": "Buffer", | ||
"data": "base64:R5HXrp+X3xAO8VWOhHctagm0N2I4goP3XG8goyqIqoY=" | ||
}, | ||
"sequence": 1 | ||
} | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ | ||
import { Account as EthAccount, Address } from '@ethereumjs/util' | ||
import { ethers } from 'ethers' | ||
import { Assert } from '../../../assert' | ||
import { ContractArtifact, GLOBAL_CONTRACT_ADDRESS } from '../../../evm' | ||
import { useAccountFixture } from '../../../testUtilities' | ||
import { createRouteTest } from '../../../testUtilities/routeTest' | ||
|
||
describe('Route eth/sendRawTransaction', () => { | ||
const routeTest = createRouteTest() | ||
const globalContract = new ethers.Interface(ContractArtifact.abi) | ||
|
||
it('should construct evm transaction and submit to node', async () => { | ||
const senderIf = await useAccountFixture(routeTest.node.wallet, 'sender') | ||
|
||
const evmPrivateKey = Uint8Array.from(Buffer.from(senderIf.spendingKey || '', 'hex')) | ||
|
||
const evmSenderAddress = Address.fromPrivateKey(evmPrivateKey) | ||
const senderAccount = new EthAccount(BigInt(0), 500_000_000n) | ||
|
||
await routeTest.node.chain.blockchainDb.stateManager.checkpoint() | ||
await routeTest.node.chain.blockchainDb.stateManager.putAccount( | ||
evmSenderAddress, | ||
senderAccount, | ||
) | ||
await routeTest.node.chain.blockchainDb.stateManager.commit() | ||
|
||
const evmAccount = await routeTest.node.chain.blockchainDb.stateManager.getAccount( | ||
evmSenderAddress, | ||
) | ||
Assert.isNotUndefined(evmAccount) | ||
|
||
const response = await routeTest.client.eth.sendTransaction({ | ||
nonce: '0x0', | ||
to: evmSenderAddress.toString(), | ||
from: evmSenderAddress.toString(), | ||
value: '0xEE6B280', // 250000000 | ||
}) | ||
|
||
expect(response.status).toEqual(200) | ||
}) | ||
|
||
it('should construct an evm shield transaction and submit to node', async () => { | ||
const { wallet } = routeTest | ||
|
||
const ifReceivingAccount = await useAccountFixture(wallet, 'ifReceivingAccount') | ||
|
||
const encodedFunctionData = globalContract.encodeFunctionData('shield', [ | ||
Buffer.from(ifReceivingAccount.publicAddress, 'hex'), | ||
2n, | ||
500n, | ||
]) | ||
|
||
const response = await routeTest.client.eth.sendTransaction({ | ||
to: GLOBAL_CONTRACT_ADDRESS.toString(), | ||
from: ifReceivingAccount.ethAddress!.toString(), | ||
data: encodedFunctionData, | ||
}) | ||
|
||
expect(response.status).toEqual(200) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ | ||
import { LegacyTransaction } from '@ethereumjs/tx' | ||
import { Address } from '@ethereumjs/util' | ||
import * as yup from 'yup' | ||
import { Assert } from '../../../assert' | ||
import { GLOBAL_IF_ACCOUNT } from '../../../evm' | ||
import { FullNode } from '../../../node' | ||
import { legacyTransactionToEvmDescription } from '../../../primitives' | ||
import { EthUtils } from '../../../utils/eth' | ||
import { AssertSpending } from '../../../wallet/account/account' | ||
import { ApiNamespace } from '../namespaces' | ||
import { routes } from '../router' | ||
|
||
export type EthSendTransactionRequest = { | ||
from: string | ||
to?: string | ||
gas?: string | ||
gasPrice?: string | ||
value?: string | ||
data?: string | ||
nonce?: string | ||
} | ||
|
||
export type EthSendTransactionResponse = { | ||
result: string | ||
} | ||
|
||
export const EthSendTransactionRequestSchema: yup.ObjectSchema<EthSendTransactionRequest> = yup | ||
.object({ | ||
from: yup.string().defined(), | ||
to: yup.string().optional(), | ||
gas: yup.string().optional(), | ||
gasPrice: yup.string().optional(), | ||
value: yup.string().optional(), | ||
data: yup.string().optional(), | ||
nonce: yup.string().optional(), | ||
}) | ||
.defined() | ||
|
||
export const EthSendTransactionResponseSchema: yup.ObjectSchema<EthSendTransactionResponse> = | ||
yup | ||
.object({ | ||
result: yup.string().defined(), | ||
}) | ||
.defined() | ||
|
||
routes.register<typeof EthSendTransactionRequestSchema, EthSendTransactionResponse>( | ||
`${ApiNamespace.eth}/sendTransaction`, | ||
EthSendTransactionRequestSchema, | ||
async (request, node): Promise<void> => { | ||
Assert.isInstanceOf(node, FullNode) | ||
|
||
const account = node.wallet.listAccounts().find((a) => a.ethAddress === request.data.from) | ||
Assert.isNotUndefined(account, 'Account not found') | ||
AssertSpending(account) | ||
|
||
const ethAccount = account.ethAddress | ||
? await node.chain.evm.getAccount( | ||
Address.fromString(EthUtils.prefix0x(account.ethAddress)), | ||
) | ||
: undefined | ||
|
||
const nonce = request.data.nonce | ||
? BigInt(request.data.nonce) | ||
: ethAccount?.nonce ?? BigInt(0) | ||
|
||
const gas = request.data.gas ? BigInt(request.data.gas) : 1000000n | ||
const gasPrice = request.data.gasPrice ? BigInt(request.data.gasPrice) : 0n | ||
const value = request.data.value ? BigInt(request.data.value) : undefined | ||
|
||
const ethTransaction = new LegacyTransaction({ | ||
nonce: nonce, | ||
to: request.data.to, | ||
gasLimit: gas, | ||
gasPrice: gasPrice, | ||
value: value, | ||
data: request.data.data, | ||
}) | ||
|
||
const signed = ethTransaction.sign(Buffer.from(account.spendingKey, 'hex')) | ||
|
||
const evmDescription = legacyTransactionToEvmDescription(signed) | ||
const result = await node.chain.evm.simulateTx({ tx: signed }) | ||
const events = result.events | ||
Assert.isNotUndefined(events) | ||
|
||
const raw = await node.wallet.createEvmTransaction({ | ||
evm: evmDescription, | ||
evmEvents: events, | ||
account: account, | ||
}) | ||
|
||
// TODO: This is pretty hacky to figure out which key to post with | ||
const unshields = events.filter((e) => e.name === 'unshield') | ||
const spendingKey = | ||
unshields.length > 0 ? account.spendingKey : GLOBAL_IF_ACCOUNT.spendingKey | ||
|
||
const { transaction } = await node.wallet.post({ | ||
transaction: raw, | ||
spendingKey: spendingKey, | ||
}) | ||
|
||
request.end({ | ||
result: Buffer.from(transaction.hash()).toString('hex'), | ||
}) | ||
}, | ||
) |