Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: add logs for transaction failing #614

Merged
merged 5 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions packages/sdk/src/common/logger/logger-middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to improve instance checking

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"
}
Expand Down
172 changes: 148 additions & 24 deletions packages/sdk/src/common/logger/logger-overrides.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
})
Expand Down Expand Up @@ -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,
Expand All @@ -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)
})

Expand All @@ -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()
})

Expand All @@ -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,
})

Expand All @@ -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,
Expand All @@ -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<any> { 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()
})
})
})
6 changes: 4 additions & 2 deletions packages/sdk/src/common/logger/logger-overrides.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ export class LoggerDataContainer {
}
return parsedArgs
}
async getTraceData() {
async getTraceData(additionalFields?: Record<string, any>) {
const res = await this.input.responsePromise
return {
level: LogLevel.TRACE,
Expand All @@ -174,10 +174,11 @@ export class LoggerDataContainer {
args: this.stringifiedArgs,
resp: JSON.stringify(res),
...(this.extraFields || {}),
...(additionalFields || {}),
}
}

getErrorData<T extends Error | WrappedError>(rawError: T) {
getErrorData<T extends Error | WrappedError>(rawError: T, additionalFields?: Record<string, any>) {
let data
const error = WrappedError.isWrappedError(rawError) ? rawError.error as Error : rawError
try {
Expand All @@ -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
Expand Down