From a23b740bdf4a67b730faa1f85b9d7a7f1049f4f3 Mon Sep 17 00:00:00 2001 From: Max Greb Date: Tue, 16 Apr 2024 22:51:19 +0200 Subject: [PATCH] fix: add logs for transaction failing (#614) * fix: add logs for transaction failing --- .../src/common/logger/logger-middleware.ts | 25 ++- .../common/logger/logger-overrides.test.ts | 172 +++++++++++++++--- .../sdk/src/common/logger/logger-overrides.ts | 6 +- 3 files changed, 175 insertions(+), 28 deletions(-) diff --git a/packages/sdk/src/common/logger/logger-middleware.ts b/packages/sdk/src/common/logger/logger-middleware.ts index e2ba07eb0..6558f5003 100644 --- a/packages/sdk/src/common/logger/logger-middleware.ts +++ b/packages/sdk/src/common/logger/logger-middleware.ts @@ -149,12 +149,33 @@ export function getInternalLoggerMiddleware( remoteLogger.raw(dataContainer.getErrorData(wrappedError || err)) } } - return wrappedError ? responsePromise.catch(() => { throw wrappedError }) - : responsePromise + + const returnedPromis = (wrappedError ? responsePromise.catch(() => { throw wrappedError }) + : responsePromise) + + returnedPromis.then(async (tx) => { + if (tx?.transaction?.constructor.name === "BlockchainEthereumTransaction") { + try { + await tx.transaction.wait() + await remoteLogger.raw(await dataContainer.getTraceData({ method: replaceMethodPart(callable.name, "wait") })) + } catch(err: any) { + wrappedError = wrapSpecialErrors(err) + await remoteLogger.raw(dataContainer.getErrorData(wrappedError || err, { method: replaceMethodPart(callable.name, "wait") })) + } + } + }).catch(() => {}) + + return returnedPromis }] } } +function replaceMethodPart(method: string, argForReplace: string): string { + let parts = method.split(".") + parts[parts.length - 1] = argForReplace + return parts.join(".") +} + function isCallable(fn: any): fn is WrappedAdvancedFn { return fn instanceof WrappedAdvancedFn || fn?.constructor?.name === "WrappedAdvancedFn" } diff --git a/packages/sdk/src/common/logger/logger-overrides.test.ts b/packages/sdk/src/common/logger/logger-overrides.test.ts index 5c34f7df5..04468e17c 100644 --- a/packages/sdk/src/common/logger/logger-overrides.test.ts +++ b/packages/sdk/src/common/logger/logger-overrides.test.ts @@ -21,13 +21,13 @@ import { getExecRevertedMessage, isErrorWarning } from "./logger-overrides" describe("logger overrides", () => { describe("isErrorWarning", () => { - test("EthereumProviderError (transaction underpriced)", async () => { const err = new EthereumProviderError({ data: null, error: { code: -32603, - message: '[ethjs-query] while formatting outputs from RPC \'{"value":{"code":-32603,"data":{"code":-32000,"message":"transaction underpriced"}}}', + message: + '[ethjs-query] while formatting outputs from RPC \'{"value":{"code":-32603,"data":{"code":-32000,"message":"transaction underpriced"}}}', }, method: "any", }) @@ -55,7 +55,7 @@ describe("logger overrides", () => { { message: "Popup closed" }, { code: 4001 }, ] - test.each(errors)("isInfoLevel test with message=$message and code=$code", (error) => { + test.each(errors)("isInfoLevel test with message=$message and code=$code", error => { const err = new EthereumProviderError({ data: null, error, @@ -71,12 +71,13 @@ describe("logger overrides", () => { }) test("ethers error", () => { - const ethersError = "Error while gas estimation with message cannot estimate gas; transaction may fail or may require manual gas limit [ See: https://links.ethers.org/v5-errors-UNPREDICTABLE_GAS_LIMIT ] (reason=\"execution reverted: Function call not successful\", method=\"estimateGas\", transaction={\"from\":\"0x2Cbc450E04a6379d37F1b85655d1fc09bdA3E6dA\",\"to\":\"0x12b3897a36fDB436ddE2788C06Eff0ffD997066e\",\"data\":\"0x0c53c5" + const ethersError = + 'Error while gas estimation with message cannot estimate gas; transaction may fail or may require manual gas limit [ See: https://links.ethers.org/v5-errors-UNPREDICTABLE_GAS_LIMIT ] (reason="execution reverted: Function call not successful", method="estimateGas", transaction={"from":"0x2Cbc450E04a6379d37F1b85655d1fc09bdA3E6dA","to":"0x12b3897a36fDB436ddE2788C06Eff0ffD997066e","data":"0x0c53c5' expect(getExecRevertedMessage(ethersError)).toEqual("Function call not successful") }) test("RPC error", () => { - const error = "Internal JSON-RPC error.\n{\n \"code\": -32000,\n \"message\": \"execution reverted\"\n}" + const error = 'Internal JSON-RPC error.\n{\n "code": -32000,\n "message": "execution reverted"\n}' expect(getExecRevertedMessage(error)).toEqual(error) }) @@ -96,7 +97,9 @@ describe("logger overrides", () => { }) test("flow proposal key error error", () => { - const error = new Error("[Error Code: 1007] error caused by: 1 error occurred:\\n\\t* checking sequence number failed: [Error Code: 1007] invalid proposal key: public key 0 on account 201362ac764cf16f has sequence number 284, but given 283\\n\\n") + const error = new Error( + "[Error Code: 1007] error caused by: 1 error occurred:\\n\\t* checking sequence number failed: [Error Code: 1007] invalid proposal key: public key 0 on account 201362ac764cf16f has sequence number 284, but given 283\\n\\n", + ) expect(isErrorWarning(error, WalletType.FLOW)).toBeTruthy() }) @@ -105,26 +108,27 @@ describe("logger overrides", () => { const ethereum = new Web3Ethereum({ web3: new Web3(provider) }) const ethereumWallet = new EthereumWallet(ethereum) - const erc721Address = convertEthereumContractAddress("0xF3348949Db80297C78EC17d19611c263fc61f987", Blockchain.ETHEREUM) + const erc721Address = convertEthereumContractAddress( + "0xF3348949Db80297C78EC17d19611c263fc61f987", + Blockchain.ETHEREUM, + ) test("should mint ERC721 token", async () => { const mockLogger = jest.fn() const sdk = createRaribleSdk(ethereumWallet, "development", { apiKey: getAPIKey("development"), - logger: new RemoteLogger( - async (msg: LoggableValue) => mockLogger(msg), - { - initialContext: getSdkContext({ - env: "development", - sessionId: "", - config: { - logs: LogsLevel.ERROR, - }, - }), - dropBatchInterval: 100, - maxByteSize: 5 * 10240, + logger: new RemoteLogger(async (msg: LoggableValue) => mockLogger(msg), { + initialContext: getSdkContext({ + env: "development", + sessionId: "", + config: { + logs: LogsLevel.ERROR, + }, }), + dropBatchInterval: 100, + maxByteSize: 5 * 10240, + }), logs: LogsLevel.ERROR, }) @@ -137,10 +141,12 @@ describe("logger overrides", () => { }) const result = await action.submit({ uri: "ipfs://ipfs/QmfVqzkQcKR1vCNqcZkeVVy94684hyLki7QcVzd9rmjuG5", - creators: [{ - account: sender, - value: 10000, - }], + creators: [ + { + account: sender, + value: 10000, + }, + ], royalties: [], lazyMint: false, supply: 1, @@ -162,6 +168,124 @@ describe("logger overrides", () => { expect(logObject.status).toBe(404) expect(logObject.code).toBe("NETWORK_ERR") }) - }) + test("successful transaction wait", async () => { + const mockLogger = jest.fn() + + const sdk = createRaribleSdk(ethereumWallet, "development", { + apiKey: getAPIKey("development"), + logger: new RemoteLogger(async (msg: LoggableValue) => mockLogger(msg), { + initialContext: getSdkContext({ + env: "development", + sessionId: "", + config: { + logs: LogsLevel.ERROR, + }, + }), + dropBatchInterval: 100, + maxByteSize: 5 * 10240, + }), + logs: LogsLevel.ERROR, + }) + + const senderRaw = wallet.getAddressString() + const sender = convertEthereumToUnionAddress(senderRaw, Blockchain.ETHEREUM) + + try { + const rightAddress = "ETHEREUM:0x5fc5fc8693211d29b53c2923222083a81fced33c" + const action = await sdk.nft.mint.prepare({ + collectionId: toCollectionId(rightAddress), + }) + const result = await action.submit({ + uri: "ipfs://ipfs/QmfVqzkQcKR1vCNqcZkeVVy94684hyLki7QcVzd9rmjuG5", + creators: [ + { + account: sender, + value: 10000, + }, + ], + royalties: [], + lazyMint: false, + supply: 1, + }) + + if (result.type === MintType.ON_CHAIN) { + const transaction = await result.transaction.wait() + expect(transaction.blockchain).toEqual("ETHEREUM") + expect(transaction.hash).toBeTruthy() + } else { + throw new Error("Must be on chain") + } + } catch (e) { } + + await delay(1000) + + const trace = mockLogger.mock.calls[0][0][0].level + expect(trace).toBe("TRACE") + }) + + test("failed transaction wait", async () => { + class BlockchainEthereumTransaction { + async wait(): Promise { return Promise.reject("asd") } + } + + jest.mock("@rarible/sdk-transaction", () => { // replace with actual path + return { + BlockchainEthereumTransaction: jest.fn().mockImplementation(() => { + return new BlockchainEthereumTransaction() + }), + } + }) + const mockLogger = jest.fn() + + const sdk = createRaribleSdk(ethereumWallet, "development", { + apiKey: getAPIKey("development"), + logger: new RemoteLogger(async (msg: LoggableValue) => mockLogger(msg), { + initialContext: getSdkContext({ + env: "development", + sessionId: "", + config: { + logs: LogsLevel.ERROR, + }, + }), + dropBatchInterval: 100, + maxByteSize: 5 * 10240, + }), + logs: LogsLevel.ERROR, + }) + + const senderRaw = wallet.getAddressString() + const sender = convertEthereumToUnionAddress(senderRaw, Blockchain.ETHEREUM) + + try { + const rightAddress = "ETHEREUM:0x5fc5fc8693211d29b53c2923222083a81fced33c" + const action = await sdk.nft.mint.prepare({ + collectionId: toCollectionId(rightAddress), + }) + const result = await action.submit({ + uri: "ipfs://ipfs/QmfVqzkQcKR1vCNqcZkeVVy94684hyLki7QcVzd9rmjuG5", + creators: [ + { + account: sender, + value: 10000, + }, + ], + royalties: [], + lazyMint: false, + supply: 1, + }) + + if (result.type === MintType.ON_CHAIN) { + await result.transaction.wait() + } else { + throw new Error("Must be on chain") + } + } catch (e) { } + + await delay(2000) + const trace = mockLogger.mock.calls[0][0] + const foundCall = trace.find((call: any) => call.method === "nft.mint.prepare.submit.wait") + expect(foundCall).toBeTruthy() + }) + }) }) diff --git a/packages/sdk/src/common/logger/logger-overrides.ts b/packages/sdk/src/common/logger/logger-overrides.ts index 9eef53bb9..e2ab9305e 100644 --- a/packages/sdk/src/common/logger/logger-overrides.ts +++ b/packages/sdk/src/common/logger/logger-overrides.ts @@ -164,7 +164,7 @@ export class LoggerDataContainer { } return parsedArgs } - async getTraceData() { + async getTraceData(additionalFields?: Record) { const res = await this.input.responsePromise return { level: LogLevel.TRACE, @@ -174,10 +174,11 @@ export class LoggerDataContainer { args: this.stringifiedArgs, resp: JSON.stringify(res), ...(this.extraFields || {}), + ...(additionalFields || {}), } } - getErrorData(rawError: T) { + getErrorData(rawError: T, additionalFields?: Record) { let data const error = WrappedError.isWrappedError(rawError) ? rawError.error as Error : rawError try { @@ -190,6 +191,7 @@ export class LoggerDataContainer { args: this.stringifiedArgs, requestAddress: undefined as undefined | string, ...(this.extraFields || {}), + ...(additionalFields || {}), } if (error instanceof NetworkError || error?.name === "NetworkError") { data.requestAddress = (error as NetworkError)?.url