Skip to content

Commit

Permalink
feature: submit transaction
Browse files Browse the repository at this point in the history
Adds a mutation to submit a signed and serialised transaction to the network.
The mutation accepts the transaction data (CBOR hex) and returns the transaction
hash if accepted by the node. It supports submitting transactions against any of the current and upcoming eras.
  • Loading branch information
rhyslbw committed Feb 1, 2021
1 parent 4615ded commit 29f1adb
Show file tree
Hide file tree
Showing 12 changed files with 120 additions and 4 deletions.
Binary file not shown.
Binary file added packages-cache/@types-temp-write-4.0.0.tgz
Binary file not shown.
Binary file added packages-cache/temp-dir-1.0.0.tgz
Binary file not shown.
Binary file added packages-cache/temp-write-4.0.0.tgz
Binary file not shown.
3 changes: 3 additions & 0 deletions packages/api-cardano-db-hasura/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
],
"dependencies": {
"@cardano-graphql/util": "3.1.0",
"@emurgo/cardano-serialization-lib-nodejs": "^5.0.0-rc.0",
"@graphql-tools/delegate": "^6.0.10",
"@graphql-tools/schema": "^6.0.9",
"@graphql-tools/wrap": "^6.0.9",
Expand All @@ -51,6 +52,7 @@
"pg": "^8.5.1",
"pg-listen": "^1.6.0",
"set-interval-async": "^1.0.33",
"temp-write": "^4.0.0",
"ts-log": "^2.2.3"
},
"devDependencies": {
Expand All @@ -62,6 +64,7 @@
"@types/node": "^14.0.13",
"@types/pg": "^7.14.4",
"@types/set-interval-async": "^1.0.0",
"@types/temp-write": "^4.0.0",
"shx": "^0.3.2",
"typescript": "^3.9.5"
}
Expand Down
10 changes: 10 additions & 0 deletions packages/api-cardano-db-hasura/schema.graphql
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
schema {
mutation: Mutation
query: Query
}

Expand All @@ -16,6 +17,11 @@ scalar Timestamp
scalar URL
scalar VRFVerificationKey

type Mutation {
# Submit a signed transaction to the network
submitTransaction (transaction: String!): TransactionSubmitResponse!
}

type Query {
activeStake (
limit: Int
Expand Down Expand Up @@ -1044,6 +1050,10 @@ type TransactionOutput_sum_fields {
value: String
}

type TransactionSubmitResponse {
hash: String!
}

type Block {
# Genesis block does not belong to the 0th epoch, therefore it could be null
epoch: Epoch
Expand Down
18 changes: 16 additions & 2 deletions packages/api-cardano-db-hasura/src/CardanoCli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ const isEraMismatch = (errorMessage: string, era: string): boolean => {
export interface CardanoCli {
getLedgerState(): Promise<LedgerState>,
getProtocolParams(): Promise<ProtocolParams>,
getTip(): Promise<CardanoCliTip>
getTip(): Promise<CardanoCliTip>,
submitTransaction(filePath: string): Promise<void>
}

export function createCardanoCli (
Expand Down Expand Up @@ -75,6 +76,19 @@ export function createCardanoCli (
withEraFlag: true
}
),
getTip: () => query<CardanoCliTip>('tip')
getTip: () => query<CardanoCliTip>('tip'),
submitTransaction: (filePath) => {
return new Promise((resolve, reject) => {
exec(
`${cardanoCliPath} transaction submit --tx-file ${filePath} ${networkArg}`,
(error, _stdout, stderr) => {
if (error !== null || stderr.toString() !== '') {
return reject(new Error(stderr.toString()))
}
return resolve()
}
)
})
}
}
}
45 changes: 43 additions & 2 deletions packages/api-cardano-db-hasura/src/CardanoNodeClient.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { CardanoCli } from './CardanoCli'
import fs from 'fs-extra'
import { AssetSupply } from './graphql_types'
import { AssetSupply, Transaction } from './graphql_types'
import pRetry from 'p-retry'
import util, { DataFetcher } from '@cardano-graphql/util'
import util, { DataFetcher, knownEras } from '@cardano-graphql/util'
import tempWrite from 'temp-write'
import { dummyLogger, Logger } from 'ts-log'
import { getHashOfSignedTransaction } from './util'

export type LedgerState = {
accountState: {
Expand All @@ -15,6 +17,23 @@ export type LedgerState = {
}
}

const fileTypeFromEra = (era: string) => {
switch (era) {
case 'mary' :
return 'Tx MaryEra'
case 'allegra' :
return 'Tx AllegraEra'
case 'shelley' :
return 'TxSignedShelley'
default :
throw new Error(`Transaction not submitted. ${era} era not supported.`)
}
}

const isEraMismatch = (errorMessage: string): boolean =>
errorMessage.includes('DecoderErrorDeserialiseFailure') ||
errorMessage.includes('The era of the node and the tx do not match')

export class CardanoNodeClient {
readonly networkParams: string[]
public adaCirculatingSupply: AssetSupply['circulating']
Expand Down Expand Up @@ -79,4 +98,26 @@ export class CardanoNodeClient {
public async shutdown () {
await this.ledgerStateFetcher.shutdown()
}

public async submitTransaction (transaction: string): Promise<Transaction['hash']> {
for (const era of knownEras) {
const filePath = await tempWrite(`{
"type": "${fileTypeFromEra(era)}",
"description": "",
"cborHex": "${transaction}"
}`)
const hash = getHashOfSignedTransaction(transaction)
try {
await this.cardanoCli.submitTransaction(filePath)
this.logger.info('submitTransaction', { module: 'CardanoNodeClient', hash: hash })
return hash
} catch (error) {
if (!isEraMismatch(error.message)) {
throw error
}
} finally {
await fs.unlink(filePath)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mutation submitTransaction(
$transaction: String!
) {
submitTransaction(transaction: $transaction)
}
7 changes: 7 additions & 0 deletions packages/api-cardano-db-hasura/src/executableSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ export async function buildSchema (
}
return makeExecutableSchema({
resolvers: Object.assign({}, scalarResolvers, {
Mutation: {
submitTransaction: async (_root, args) => {
await throwIfNotInCurrentEra('submitTransaction')
const hash = await cardanoNodeClient.submitTransaction(args.transaction)
return { hash }
}
},
PaymentAddress: {
summary: async (parent, args) => {
try {
Expand Down
8 changes: 8 additions & 0 deletions packages/api-cardano-db-hasura/src/util.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import CardanoWasm from '@emurgo/cardano-serialization-lib-nodejs'
import { Config } from './Config'
import fs from 'fs-extra'
import path from 'path'
Expand All @@ -9,3 +10,10 @@ export async function readSecrets (rootDir: string): Promise<Partial<Config['db'
user: (await fs.readFile(path.join(rootDir, 'postgres_user'), 'utf8')).toString()
}
}

export function getHashOfSignedTransaction (signedTransaction: string): string {
const signedTransactionBytes = Buffer.from(signedTransaction, 'hex')
const parsed = CardanoWasm.Transaction.from_bytes(signedTransactionBytes)
const hashBuffer = parsed && parsed.body() && Buffer.from(CardanoWasm.hash_transaction(parsed.body()).to_bytes())
return hashBuffer.toString('hex')
}
28 changes: 28 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,11 @@
exec-sh "^0.3.2"
minimist "^1.2.0"

"@emurgo/cardano-serialization-lib-nodejs@^5.0.0-rc.0":
version "5.0.0-rc.0"
resolved "https://registry.yarnpkg.com/@emurgo/cardano-serialization-lib-nodejs/-/cardano-serialization-lib-nodejs-5.0.0-rc.0.tgz#da6e7679ee83e467adfc78c2789b2a5129f7f6b1"
integrity sha512-CVd5YHVIsxiokJTYnyZunzr8jaeuEMVb83Jjt4j0OTNJJfp3ReV0ZIvGsoGJUP2K+07nT8xWnHDEDrMH97ZIkQ==

"@graphql-codegen/cli@^1.15.2":
version "1.16.2"
resolved "https://registry.yarnpkg.com/@graphql-codegen/cli/-/cli-1.16.2.tgz#ff92e5e6813a404616d7504500be26ff88b7092d"
Expand Down Expand Up @@ -1699,6 +1704,13 @@
resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1"
integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==

"@types/temp-write@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/temp-write/-/temp-write-4.0.0.tgz#983237bb6dbcac883137dba8189b8c6177e8a04d"
integrity sha512-BNcDNG/ujXmzR49TeVxcWMbglOO55YQbe1ij3LE6WoZdLtZvclSEl5GAmmlfsr0Y5kM9mLIE1wk3r3pzQz62gQ==
dependencies:
temp-write "*"

"@types/through@*":
version "0.0.30"
resolved "https://registry.yarnpkg.com/@types/through/-/through-0.0.30.tgz#e0e42ce77e897bd6aead6f6ea62aeb135b8a3895"
Expand Down Expand Up @@ -8676,6 +8688,22 @@ tdigest@^0.1.1:
dependencies:
bintrees "1.0.1"

temp-dir@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d"
integrity sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=

temp-write@*, temp-write@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/temp-write/-/temp-write-4.0.0.tgz#cd2e0825fc826ae72d201dc26eef3bf7e6fc9320"
integrity sha512-HIeWmj77uOOHb0QX7siN3OtwV3CTntquin6TNVg6SHOqCP3hYKmox90eeFOGaY1MqJ9WYDDjkyZrW6qS5AWpbw==
dependencies:
graceful-fs "^4.1.15"
is-stream "^2.0.0"
make-dir "^3.0.0"
temp-dir "^1.0.0"
uuid "^3.3.2"

terminal-link@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994"
Expand Down

0 comments on commit 29f1adb

Please sign in to comment.