diff --git a/src/core/KnownTags.ts b/src/core/KnownTags.ts index d9809630..70f66905 100644 --- a/src/core/KnownTags.ts +++ b/src/core/KnownTags.ts @@ -36,6 +36,7 @@ export const WARP_TAGS = { WASM_META: 'Wasm-Meta', REQUEST_VRF: 'Request-Vrf', SIGNATURE_TYPE: 'Signature-Type', + UPLOADER_TX_ID: 'Uploader-Tx-Id', WARP_TESTNET: 'Warp-Testnet', MANIFEST: 'Contract-Manifest', NONCE: 'Nonce' diff --git a/src/core/modules/impl/ArweaveGQLTxsFetcher.ts b/src/core/modules/impl/ArweaveGQLTxsFetcher.ts index 1a286b30..ff5dcc5d 100644 --- a/src/core/modules/impl/ArweaveGQLTxsFetcher.ts +++ b/src/core/modules/impl/ArweaveGQLTxsFetcher.ts @@ -4,6 +4,7 @@ import { ArweaveWrapper } from '../../../utils/ArweaveWrapper'; import { sleep } from '../../../utils/utils'; import { Benchmark } from '../../../logging/Benchmark'; import { Warp } from '../../Warp'; +import { WARP_TAGS } from '../../KnownTags'; const TRANSACTIONS_QUERY = `query Transactions($tags: [TagFilter!]!, $blockFilter: BlockFilter!, $first: Int!, $after: String) { transactions(tags: $tags, block: $blockFilter, first: $first, sort: HEIGHT_ASC, after: $after) { @@ -56,6 +57,33 @@ const TRANSACTION_QUERY = `query Transaction($id: ID!) { } }`; +// this is a query for old/legacy bundler format +const TRANSACTION_QUERY_USING_TAG = `query Transactions($tags: [TagFilter!]!) { + transactions(tags: $tags) { + edges { + node { + id + owner { address, key } + recipient + tags { + name + value + } + block { + height + id + timestamp + } + fee { winston, ar } + quantity { winston, ar } + parent { id } + bundledIn { id } + signature + } + } + } +}`; + interface TagFilter { name: string; values: string[]; @@ -93,6 +121,23 @@ export class ArweaveGQLTxsFetcher { return response.transaction; } + /** + * Fetches transaction stored using legacy bundling format. + */ + async transactionUsingUploaderTag(transactionId: string): Promise { + const txTag: TagFilter = { + name: WARP_TAGS.UPLOADER_TX_ID, + values: [transactionId] + }; + const response = (await this.fetch(TRANSACTION_QUERY_USING_TAG, { tags: [txTag] })) + .transactions; + + if (response.edges.length < 1) { + throw new Error(`No interaction with tag ${WARP_TAGS.UPLOADER_TX_ID}:${transactionId}`); + } + return response.edges[0].node; + } + async transactions(variables: ArweaveTransactionQuery): Promise { let pageResult = (await this.fetch(TRANSACTIONS_QUERY, variables)).transactions; const edges: GQLEdgeInterface[] = [...pageResult.edges]; diff --git a/src/core/modules/impl/ArweaveGatewayBundledContractDefinitionLoader.ts b/src/core/modules/impl/ArweaveGatewayBundledContractDefinitionLoader.ts index 2c501b6d..ee88d412 100644 --- a/src/core/modules/impl/ArweaveGatewayBundledContractDefinitionLoader.ts +++ b/src/core/modules/impl/ArweaveGatewayBundledContractDefinitionLoader.ts @@ -17,6 +17,7 @@ import { DefinitionLoader } from '../DefinitionLoader'; import { GW_TYPE } from '../InteractionsLoader'; import { ArweaveGQLTxsFetcher } from './ArweaveGQLTxsFetcher'; import { WasmSrc } from './wasm/WasmSrc'; +import Arweave from 'arweave'; function getTagValue(tags: GQLTagInterface[], tagName: string, orDefault = undefined) { const tag = tags.find(({ name }) => name === tagName); @@ -32,7 +33,7 @@ export class ArweaveGatewayBundledContractDefinitionLoader implements Definition async load(contractTxId: string, evolvedSrcTxId?: string): Promise> { const benchmark = Benchmark.measure(); - const contractTx = await this.arweaveTransactions.transaction(contractTxId); + const contractTx: GQLTransaction = await this.fetchContractTx(contractTxId); this.logger.debug('Contract tx fetch time', benchmark.elapsed()); const owner = contractTx.owner.address; @@ -85,6 +86,14 @@ export class ArweaveGatewayBundledContractDefinitionLoader implements Definition return contractDefinition; } + async fetchContractTx(contractTxId: string): Promise { + const txUsingId = await this.arweaveTransactions.transaction(contractTxId); + if (txUsingId == null) { + return await this.arweaveTransactions.transactionUsingUploaderTag(contractTxId); + } + return txUsingId; + } + private async convertToWarpCompatibleContractTx(gqlTransaction: GQLTransaction) { const tags = gqlTransaction.tags.map(({ name, value }) => ({ name: Buffer.from(name).toString('base64url'), @@ -103,7 +112,7 @@ export class ArweaveGatewayBundledContractDefinitionLoader implements Definition async loadContractSource(srcTxId: string): Promise { const benchmark = Benchmark.measure(); - const contractSrcTx = await this.arweaveTransactions.transaction(srcTxId); + const contractSrcTx = await this.fetchContractTx(srcTxId); const srcContentType = getTagValue(contractSrcTx.tags, SMART_WEAVE_TAGS.CONTENT_TYPE); if (!SUPPORTED_SRC_CONTENT_TYPES.includes(srcContentType)) { @@ -112,10 +121,7 @@ export class ArweaveGatewayBundledContractDefinitionLoader implements Definition const contractType: ContractType = srcContentType === 'application/javascript' ? 'js' : 'wasm'; - const src = - contractType === 'js' - ? await this.arweaveWrapper.txDataString(srcTxId) - : await this.arweaveWrapper.txData(srcTxId); + const src = await this.contractSource(contractSrcTx, contractType); let srcWasmLang: string | undefined; let wasmSrc: WasmSrc; @@ -124,7 +130,7 @@ export class ArweaveGatewayBundledContractDefinitionLoader implements Definition wasmSrc = new WasmSrc(src as Buffer); srcWasmLang = getTagValue(contractSrcTx.tags, WARP_TAGS.WASM_LANG); if (!srcWasmLang) { - throw new Error(`Wasm lang not set for wasm contract src ${srcTxId}`); + throw new Error(`Wasm lang not set for wasm contract src ${contractSrcTx.id}`); } srcMetaData = JSON.parse(getTagValue(contractSrcTx.tags, WARP_TAGS.WASM_META)); } @@ -142,6 +148,22 @@ export class ArweaveGatewayBundledContractDefinitionLoader implements Definition }; } + private async contractSource(contractSrcTx: GQLTransaction, contractType: ContractType): Promise { + const uploaderId = getTagValue(contractSrcTx.tags, WARP_TAGS.UPLOADER_TX_ID); + + if (uploaderId != null) { + const txString = await this.arweaveWrapper.txDataString(contractSrcTx.id); + if (contractType === 'wasm') { + throw new Error('WASM contracts in legacy format are not supported using AR GW'); + } + return Arweave.utils.b64UrlToString(JSON.parse(txString).data); + } + + return contractType === 'js' + ? await this.arweaveWrapper.txDataString(contractSrcTx.id) + : await this.arweaveWrapper.txData(contractSrcTx.id); + } + private async evalInitialState(contractTx: GQLTransaction): Promise { if (getTagValue(contractTx.tags, WARP_TAGS.INIT_STATE)) { return getTagValue(contractTx.tags, WARP_TAGS.INIT_STATE);