Fix false negative not found response

This commit is contained in:
Justin Starry 2020-05-07 16:41:42 +08:00 committed by Michael Vines
parent e31b30c595
commit b68dc5cf85
4 changed files with 93 additions and 106 deletions

View File

@ -2,12 +2,10 @@ import React from "react";
import bs58 from "bs58";
import {
Source,
useFetchTransactionStatus,
useTransactionStatus,
useTransactionDetails,
useTransactionsDispatch,
useDetailsDispatch,
checkTransactionStatus,
ActionType,
FetchStatus
} from "../providers/transactions";
import { fetchDetails } from "providers/transactions/details";
@ -28,8 +26,7 @@ import { useHistory, useLocation } from "react-router-dom";
type Props = { signature: TransactionSignature };
export default function TransactionDetails({ signature }: Props) {
const dispatch = useTransactionsDispatch();
const { url } = useCluster();
const fetchTransaction = useFetchTransactionStatus();
const [, setShow] = useClusterModal();
const [search, setSearch] = React.useState(signature);
const history = useHistory();
@ -41,13 +38,8 @@ export default function TransactionDetails({ signature }: Props) {
// Fetch transaction on load
React.useEffect(() => {
dispatch({
type: ActionType.FetchSignature,
signature,
source: Source.Url
});
checkTransactionStatus(dispatch, signature, url);
}, [signature, dispatch, url]);
fetchTransaction(signature, Source.Url);
}, [signature]); // eslint-disable-line react-hooks/exhaustive-deps
const searchInput = (
<input
@ -104,20 +96,15 @@ export default function TransactionDetails({ signature }: Props) {
function StatusCard({ signature }: Props) {
const status = useTransactionStatus(signature);
const dispatch = useTransactionsDispatch();
const refresh = useFetchTransactionStatus();
const details = useTransactionDetails(signature);
const { url } = useCluster();
const refreshStatus = () => {
checkTransactionStatus(dispatch, signature, url);
};
if (!status || status.fetchStatus === FetchStatus.Fetching) {
return <LoadingCard />;
} else if (status?.fetchStatus === FetchStatus.FetchFailed) {
return <RetryCard retry={refreshStatus} text="Fetch Failed" />;
return <RetryCard retry={() => refresh(signature)} text="Fetch Failed" />;
} else if (!status.info) {
return <RetryCard retry={refreshStatus} text="Not Found" />;
return <RetryCard retry={() => refresh(signature)} text="Not Found" />;
}
const { info } = status;
@ -141,7 +128,10 @@ function StatusCard({ signature }: Props) {
<div className="card">
<div className="card-header align-items-center">
<h3 className="card-header-title">Status</h3>
<button className="btn btn-white btn-sm" onClick={refreshStatus}>
<button
className="btn btn-white btn-sm"
onClick={() => refresh(signature)}
>
<span className="fe fe-refresh-cw mr-2"></span>
Refresh
</button>
@ -179,14 +169,25 @@ function AccountsCard({ signature }: Props) {
const dispatch = useDetailsDispatch();
const { url } = useCluster();
const fetchStatus = useFetchTransactionStatus();
const refreshStatus = () => fetchStatus(signature);
const refreshDetails = () => fetchDetails(dispatch, signature, url);
const transaction = details?.transaction?.transaction;
const message = React.useMemo(() => {
return transaction?.compileMessage();
}, [transaction]);
if (!details) {
const status = useTransactionStatus(signature);
if (!status || !status.info) {
return null;
} else if (!details) {
return (
<RetryCard
retry={refreshStatus}
text="Details are not available until the transaction reaches MAX confirmations"
/>
);
} else if (details.fetchStatus === FetchStatus.Fetching) {
return <LoadingCard />;
} else if (details?.fetchStatus === FetchStatus.FetchFailed) {
@ -571,11 +572,17 @@ function LoadingCard() {
function RetryCard({ retry, text }: { retry: () => void; text: string }) {
return (
<div className="card">
<div className="card-body">
<div className="card-body text-center">
{text}
<span className="btn btn-white ml-3" onClick={retry}>
<span className="btn btn-white ml-3 d-none d-md-inline" onClick={retry}>
Try Again
</span>
<div className="d-block d-md-none mt-4">
<hr></hr>
<span className="btn btn-white" onClick={retry}>
Try Again
</span>
</div>
</div>
</div>
);

View File

@ -2,24 +2,20 @@ import React from "react";
import { Link } from "react-router-dom";
import {
useTransactions,
useTransactionsDispatch,
checkTransactionStatus,
ActionType,
TransactionStatus,
Source,
FetchStatus
FetchStatus,
useFetchTransactionStatus
} from "../providers/transactions";
import bs58 from "bs58";
import { assertUnreachable } from "../utils";
import { useCluster } from "../providers/cluster";
import Copyable from "./Copyable";
function TransactionsCard() {
const { transactions, idCounter } = useTransactions();
const dispatch = useTransactionsDispatch();
const fetchTransaction = useFetchTransactionStatus();
const signatureInput = React.useRef<HTMLInputElement>(null);
const [error, setError] = React.useState("");
const { url } = useCluster();
const onNew = (signature: string) => {
if (signature.length === 0) return;
@ -37,13 +33,7 @@ function TransactionsCard() {
return;
}
dispatch({
type: ActionType.FetchSignature,
signature,
source: Source.Input
});
checkTransactionStatus(dispatch, signature, url);
fetchTransaction(signature, Source.Input);
const inputEl = signatureInput.current;
if (inputEl) {
inputEl.value = "";
@ -98,9 +88,7 @@ function TransactionsCard() {
<td>-</td>
<td></td>
</tr>
{transactions.map(transaction =>
renderTransactionRow(transaction, dispatch, url)
)}
{transactions.map(transaction => renderTransactionRow(transaction))}
</tbody>
</table>
</div>
@ -120,11 +108,7 @@ const renderHeader = () => {
);
};
const renderTransactionRow = (
transactionStatus: TransactionStatus,
dispatch: any,
url: string
) => {
const renderTransactionRow = (transactionStatus: TransactionStatus) => {
const { fetchStatus, info, signature, id } = transactionStatus;
let statusText;
@ -162,30 +146,6 @@ const renderTransactionRow = (
confirmationsText = `${info.confirmations}`;
}
const renderDetails = () => {
if (info?.confirmations === "max") {
return (
<Link
to={location => ({ ...location, pathname: "/tx/" + signature })}
className="btn btn-rounded-circle btn-white btn-sm"
>
<span className="fe fe-arrow-right"></span>
</Link>
);
} else {
return (
<button
className="btn btn-rounded-circle btn-white btn-sm"
onClick={() => {
checkTransactionStatus(dispatch, signature, url);
}}
>
<span className="fe fe-refresh-cw"></span>
</button>
);
}
};
return (
<tr key={signature}>
<td>
@ -201,7 +161,14 @@ const renderTransactionRow = (
</td>
<td className="text-uppercase">{confirmationsText}</td>
<td>{slotText}</td>
<td>{renderDetails()}</td>
<td>
<Link
to={location => ({ ...location, pathname: "/tx/" + signature })}
className="btn btn-rounded-circle btn-white btn-sm"
>
<span className="fe fe-arrow-right"></span>
</Link>
</td>
</tr>
);
};

View File

@ -61,28 +61,24 @@ interface State {
status: ClusterStatus;
}
interface Connecting {
status: ClusterStatus.Connecting;
interface Action {
status: ClusterStatus;
cluster: Cluster;
customUrl: string;
}
interface Connected {
status: ClusterStatus.Connected;
}
interface Failure {
status: ClusterStatus.Failure;
}
type Action = Connected | Connecting | Failure;
type Dispatch = (action: Action) => void;
function clusterReducer(state: State, action: Action): State {
switch (action.status) {
case ClusterStatus.Connected:
case ClusterStatus.Failure: {
return Object.assign({}, state, { status: action.status });
if (
state.cluster !== action.cluster ||
state.customUrl !== action.customUrl
)
return state;
return action;
}
case ClusterStatus.Connecting: {
return action;
@ -197,10 +193,10 @@ async function updateCluster(
try {
const connection = new Connection(clusterUrl(cluster, customUrl));
await connection.getRecentBlockhash();
dispatch({ status: ClusterStatus.Connected });
dispatch({ status: ClusterStatus.Connected, cluster, customUrl });
} catch (error) {
console.error("Failed to update cluster", error);
dispatch({ status: ClusterStatus.Failure });
dispatch({ status: ClusterStatus.Failure, cluster, customUrl });
}
}

View File

@ -7,7 +7,7 @@ import {
SignatureResult
} from "@solana/web3.js";
import { useQuery } from "../../utils/url";
import { useCluster, Cluster } from "../cluster";
import { useCluster, Cluster, ClusterStatus } from "../cluster";
import {
DetailsProvider,
StateContext as DetailsStateContext,
@ -127,7 +127,7 @@ export function TransactionsProvider({ children }: TransactionsProviderProps) {
transactions: {}
});
const { cluster, url } = useCluster();
const { cluster, status: clusterStatus, url } = useCluster();
const accountsDispatch = useAccountsDispatch();
const query = useQuery();
const testFlag = query.get("test");
@ -140,14 +140,14 @@ export function TransactionsProvider({ children }: TransactionsProviderProps) {
signature,
source: Source.Url
});
checkTransactionStatus(dispatch, signature, url);
fetchTransactionStatus(dispatch, signature, url, clusterStatus);
});
// Create a test transaction
if (cluster === Cluster.Devnet && testFlag !== null) {
createTestTransaction(dispatch, accountsDispatch, url);
createTestTransaction(dispatch, accountsDispatch, url, clusterStatus);
}
}, [testFlag, cluster, url]); // eslint-disable-line react-hooks/exhaustive-deps
}, [testFlag, cluster, clusterStatus, url]); // eslint-disable-line react-hooks/exhaustive-deps
// Check for transactions in the url params
const values = TX_ALIASES.flatMap(key => [
@ -167,7 +167,7 @@ export function TransactionsProvider({ children }: TransactionsProviderProps) {
signature,
source: Source.Url
});
checkTransactionStatus(dispatch, signature, url);
fetchTransactionStatus(dispatch, signature, url, clusterStatus);
});
}, [values.toString()]); // eslint-disable-line react-hooks/exhaustive-deps
@ -183,7 +183,8 @@ export function TransactionsProvider({ children }: TransactionsProviderProps) {
async function createTestTransaction(
dispatch: Dispatch,
accountsDispatch: AccountsDispatch,
url: string
url: string,
clusterStatus: ClusterStatus
) {
const testKey = process.env.REACT_APP_TEST_KEY;
let testAccount = new Account();
@ -203,7 +204,7 @@ async function createTestTransaction(
signature,
source: Source.Test
});
checkTransactionStatus(dispatch, signature, url);
fetchTransactionStatus(dispatch, signature, url, clusterStatus);
accountsDispatch({
type: AccountsActionType.Input,
pubkey: testAccount.publicKey
@ -226,16 +227,17 @@ async function createTestTransaction(
signature,
source: Source.Test
});
checkTransactionStatus(dispatch, signature, url);
fetchTransactionStatus(dispatch, signature, url, clusterStatus);
} catch (error) {
console.error("Failed to create test failure transaction", error);
}
}
export async function checkTransactionStatus(
export async function fetchTransactionStatus(
dispatch: Dispatch,
signature: TransactionSignature,
url: string
url: string,
status: ClusterStatus
) {
dispatch({
type: ActionType.UpdateStatus,
@ -243,6 +245,9 @@ export async function checkTransactionStatus(
fetchStatus: FetchStatus.Fetching
});
// We will auto-refetch when status is no longer connecting
if (status === ClusterStatus.Connecting) return;
let fetchStatus;
let info: TransactionStatusInfo | undefined;
try {
@ -317,16 +322,6 @@ export function useTransactionDetails(signature: TransactionSignature) {
return context[signature];
}
export function useTransactionsDispatch() {
const context = React.useContext(DispatchContext);
if (!context) {
throw new Error(
`useTransactionsDispatch must be used within a TransactionsProvider`
);
}
return context;
}
export function useDetailsDispatch() {
const context = React.useContext(DetailsDispatchContext);
if (!context) {
@ -336,3 +331,25 @@ export function useDetailsDispatch() {
}
return context;
}
export function useFetchTransactionStatus() {
const dispatch = React.useContext(DispatchContext);
if (!dispatch) {
throw new Error(
`useFetchTransactionStatus must be used within a TransactionsProvider`
);
}
const { url, status } = useCluster();
return (signature: TransactionSignature, source?: Source) => {
if (source !== undefined) {
dispatch({
type: ActionType.FetchSignature,
signature,
source
});
}
fetchTransactionStatus(dispatch, signature, url, status);
};
}