Skip to content

Commit

Permalink
Show error method for a failed extrinsic, #1020 (#1022)
Browse files Browse the repository at this point in the history
* Show extrinsic error code on detail list

* fix: address parsing

* refactor error data structure

* Rename variable, #1020
  • Loading branch information
hyifeng authored Feb 26, 2025
1 parent f000b77 commit 4910369
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,84 @@ function getLifetime(extrinsic, indexer) {
return [mortal.birth(indexer.blockHeight), mortal.death(indexer.blockHeight)];
}

function getDispatchError(dispatchError) {
if (dispatchError.isModule) {
try {
const mod = dispatchError.asModule;
const error = dispatchError.registry.findMetaError(mod);

return {
type: dispatchError.type,
detail: {
section: error.section,
method: error.method,
docs: error.docs,
},
};
} catch (error) {
// swallow
}
} else if (dispatchError.isToken) {
return {
type: dispatchError.type,
detail: {
type: dispatchError.asToken.type,
},
};
} else if (dispatchError.isTransactional) {
return {
type: dispatchError.type,
detail: {
type: dispatchError.asTransactional.type,
},
};
} else if (dispatchError.isArithmetic) {
return {
type: dispatchError.type,
detail: {
type: dispatchError.asArithmetic.type,
},
};
}

return {
type: dispatchError.type,
};
}

function extractExtrinsicError(events) {
const failedEvent = events.find(
(e) => e.event.section === "system" && e.event.method === "ExtrinsicFailed",
);
if (failedEvent) {
const [dispatchError] = failedEvent.event.data;
return getDispatchError(dispatchError);
}

const proxyExecutedEvent = events.find(
(e) => e.event.section === "proxy" && e.event.method === "ProxyExecuted",
);
if (proxyExecutedEvent) {
const [result] = proxyExecutedEvent.event.data;
if (result.isErr) {
return getDispatchError(result.asErr);
}
}

return null;
}

function normalizeExtrinsic(extrinsic, events, indexer) {
let hash = extrinsic.hash.toHex();
if (["gargantua", "nexus"].includes(process.env.CHAIN)) {
hash = keccakAsHex(extrinsic.toU8a(), 256);
}
const version = extrinsic.version;
const isSuccess = isExtrinsicSuccess(events);
let error = null;
if (!isSuccess) {
error = extractExtrinsicError(events);
}
const call = normalizeCall(extrinsic.method);

const isSigned = extrinsic.isSigned;
Expand All @@ -30,6 +101,7 @@ function normalizeExtrinsic(extrinsic, events, indexer) {
isSuccess,
call,
isSigned,
error,
};

if (isSigned) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ async function extrinsic(_, _args) {
blockHeight,
extrinsicIndex,
);
return extractExtrinsicInfo(api, extrinsicData);
return await extractExtrinsicInfo(api, extrinsicData);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const extrinsic = /* GraphQL */ `
signer: String
tip: String
version: Int
error: DispatchError
}
type Event {
Expand Down Expand Up @@ -50,6 +51,11 @@ const extrinsic = /* GraphQL */ `
extrinsicIndex: Int
callIndex: Int
}
type DispatchError {
type: String
detail: JSONObject
}
`;

module.exports = {
Expand Down
10 changes: 10 additions & 0 deletions site/src/components/icons/question.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import styled from "styled-components";
import { ReactComponent as Question } from "./question.svg";

const QuestionIcon = styled(Question)`
path {
fill: ${(p) => p.theme.fontSecondary};
}
`;

export default QuestionIcon;
3 changes: 3 additions & 0 deletions site/src/components/icons/question.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions site/src/hooks/useQueryExtrinsicInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ const GET_EXTRINSIC_INFO = gql`
signer
tip
version
error {
type
detail
}
}
}
`;
Expand Down
4 changes: 2 additions & 2 deletions site/src/utils/dataTransformer.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export function convertArgsForTableView(args, section, method) {
arg.name,
<AddressOrIdentity
key={`arg-${index}`}
address={arg.value.id || arg.value}
address={arg.value.id || arg.value.address32 || arg.value}
ellipsis={false}
/>,
];
Expand All @@ -76,7 +76,7 @@ export function convertArgsForTableView(args, section, method) {
arg.value.map((v, i) => (
<AddressOrIdentity
key={`arg-${index}-${i}`}
address={typeof v === "string" ? v : v?.id}
address={typeof v === "string" ? v : v?.id || v?.address32}
ellipsis={false}
/>
)),
Expand Down
36 changes: 35 additions & 1 deletion site/src/utils/viewFuncs/toDetailItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { chainSettingSelector } from "../../store/reducers/settingSlice";
import dark from "../../styles/theme/dark";
import styled from "styled-components";
import BigNumber from "bignumber.js";
import QuestionIcon from "../../components/icons/question";

export const TextSecondaryWithCopy = withCopy(TextSecondary);
const ColoredMonoLinkWithCopy = withCopy(ColoredMonoLink);
Expand Down Expand Up @@ -473,6 +474,39 @@ export const toEventDetailItem = (event) => {
};
};

function ExtrinsicErrorResult({ extrinsic }) {
let errorType = extrinsic?.error?.type;
let message = "";

const detail = extrinsic?.error?.detail;
if (detail) {
if (errorType === "Module") {
errorType = detail?.method;
message = (detail?.docs || []).join(" ");
} else {
errorType = detail?.type;
}
}

return (
<Flex gap={8}>
<CrossIcon />
{errorType && (
<FlexCenter gap={4}>
<TextSecondary>Failed ({errorType})</TextSecondary>
{message && (
<Tooltip tip={message}>
<FlexCenter>
<QuestionIcon />
</FlexCenter>
</Tooltip>
)}
</FlexCenter>
)}
</Flex>
);
}

/**
* @param {object} opts
* @param {object} opts.modules chain settings modules
Expand Down Expand Up @@ -549,7 +583,7 @@ export const toExtrinsicDetailItem = (extrinsic, opts) => {
extrinsic?.isSuccess ? (
<CheckIcon />
) : (
<CrossIcon />
<ExtrinsicErrorResult extrinsic={extrinsic} />
)
) : (
<TimerIcon />
Expand Down

0 comments on commit 4910369

Please sign in to comment.