Skip to content

Commit

Permalink
feat: add transaction_v1 group of functions (#819)
Browse files Browse the repository at this point in the history
* feat: add transaction_v1 group of functions

* replace new block for awaiting a set amount of time.

* change upgraded flag for providers

* fix(rpc-test): await for RPC confirmation that the tx is included
  • Loading branch information
voliva authored Sep 11, 2024
1 parent c9f9ada commit 2e33fd6
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 2 deletions.
4 changes: 3 additions & 1 deletion packages/core/src/rpc/rpc-spec/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import * as ChainHeadV1RPC from './chainHead_v1.js'
import * as TransactionV1RPC from './transaction_v1.js'

export { ChainHeadV1RPC }
export { ChainHeadV1RPC, TransactionV1RPC }

const handlers = {
...ChainHeadV1RPC,
...TransactionV1RPC,
}

export default handlers
33 changes: 33 additions & 0 deletions packages/core/src/rpc/rpc-spec/transaction_v1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Handler } from '../shared.js'
import { HexString } from '@polkadot/util/types'
import { defaultLogger } from '../../logger.js'

const logger = defaultLogger.child({ name: 'rpc-transaction_v1' })
const randomId = () => Math.random().toString(36).substring(2)

/**
* Submit the extrinsic to the transaction pool
*
* @param context
* @param params - [`extrinsic`]
*
* @return operation id
*/
export const transaction_v1_broadcast: Handler<[HexString], string | null> = async (context, [extrinsic]) => {
await context.chain.submitExtrinsic(extrinsic).catch((err) => {
// As per the spec, the invalid transaction errors should be ignored.
logger.warn('Submit extrinsic failed', err)
})

return randomId()
}

/**
* Stop broadcasting the transaction to other nodes.
*
*/
export const transaction_v1_stop: Handler<[string], null> = async (_context, [_operationId]) => {
// Chopsticks doesn't have any process to broadcast the transaction through P2P
// so stopping doesn't have any effect.
return null
}
3 changes: 2 additions & 1 deletion packages/e2e/src/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ export const setupPolkadotApi = async (option: SetupOption) => {
chain: null as unknown as Blockchain,
substrateClient: null as unknown as SubstrateClient,
observableClient: null as unknown as ObservableClient,
ws: null as unknown as WsProvider,
}

beforeAll(async () => {
Expand All @@ -212,7 +213,7 @@ export const setupPolkadotApi = async (option: SetupOption) => {

beforeEach(async () => {
const res = await setup()
ws = res.ws
ws = result.ws = res.ws
chain = result.chain = res.chain
result.substrateClient = res.substrateClient
result.observableClient = res.observableClient
Expand Down
82 changes: 82 additions & 0 deletions packages/e2e/src/rpc-spec.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { ApiPromise } from '@polkadot/api'
import { RuntimeContext } from '@polkadot-api/observable-client'
import { describe, expect, it } from 'vitest'
import { dev, env, observe, setupPolkadotApi, testingPairs } from './helper.js'
import { firstValueFrom } from 'rxjs'

const testApi = await setupPolkadotApi(env.acalaV15)

const { alice, bob } = testingPairs()

describe('transaction_v1', async () => {
it('sends and executes transactions', async () => {
const chainHead = testApi.observableClient.chainHead$()

const api = await prepareChainForTx()

const TRANSFERRED_VALUE = 100n
const tx = await api.tx.balances.transferKeepAlive(bob.address, TRANSFERRED_VALUE).signAsync(alice)
const { nextValue, subscription } = observe(chainHead.trackTx$(tx.toHex()))
const resultPromise = nextValue()
await new Promise((onSuccess, onError) =>
testApi.substrateClient._request('transaction_v1_broadcast', [tx.toHex()], { onSuccess, onError }),
)
const hash = await dev.newBlock()

expect(await resultPromise).toMatchObject({
hash,
found: {
type: true,
},
})

const keyEncoder = (addr: string) => (ctx: RuntimeContext) =>
ctx.dynamicBuilder.buildStorage('System', 'Account').enc(addr)
const resultDecoder = (data: string | null, ctx: RuntimeContext) =>
data ? ctx.dynamicBuilder.buildStorage('System', 'Account').dec(data) : null
expect(
await firstValueFrom(chainHead.storage$(null, 'value', keyEncoder(bob.address), null, resultDecoder)),
).toMatchObject({
data: {
free: INITIAL_ACCOUNT_VALUE + TRANSFERRED_VALUE,
},
})

subscription.unsubscribe()
chainHead.unfollow()
})
})

const INITIAL_ACCOUNT_VALUE = 100_000_000_000_000n
async function prepareChainForTx() {
const api = await ApiPromise.create({
provider: testApi.ws,
noInitWarn: true,
})
await api.isReady
await dev.setStorage({
System: {
Account: [
[
[alice.address],
{
providers: 1,
data: { free: INITIAL_ACCOUNT_VALUE },
},
],
[
[bob.address],
{
providers: 1,
data: { free: INITIAL_ACCOUNT_VALUE },
},
],
],
},
Sudo: {
Key: alice.address,
},
})

return api
}

0 comments on commit 2e33fd6

Please sign in to comment.