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

show cell size error + improve searching algo #11

Merged
merged 2 commits into from
Aug 27, 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
7,186 changes: 7,186 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"lodash": "^4.17.21",
"moment": "^2.29.4",
"notistack": "^2.0.8",
"promise-retry": "^2.0.1",
"qrcode.react": "^3.1.0",
"react": "^18.2.0",
"react-animate-height": "^3.1.0",
Expand All @@ -47,6 +48,7 @@
"devDependencies": {
"@types/jest": "^29.4.0",
"@types/lodash": "^4.14.191",
"@types/promise-retry": "^1.1.6",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9",
"@vitejs/plugin-react": "^3.0.0",
Expand Down
2 changes: 1 addition & 1 deletion src/Unfreeze/Components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ export const UnfreezeBlock = ({

useEffect(() => {
setError(false);
setValue(unfreezeBlock);
setValue(unfreezeBlock ?? undefined);
}, [unfreezeBlock, open]);

const validateAndSubmit = () => {
Expand Down
24 changes: 15 additions & 9 deletions src/Unfreeze/Unfreeze.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useState, useEffect } from "react";
import { Typography } from "@mui/material";
import { Container } from "components";
import { StyledFlexColumn } from "../styles";
import { StyledFlexColumn, StyledFlexRow, textOverflow } from "../styles";
import { useUnfreezeCallback } from "lib/useUnfreezeCallback";
import { useAccountDetails } from "lib/useAccountDetails";
import { useUnfreezeTxn } from "lib/useUnfreezeTxn";
Expand All @@ -27,14 +27,15 @@ import { MonthsInput } from "./Components";
export function Unfreeze() {
let [searchParams, setSearchParams] = useSearchParams();
const [address, setAddress] = useState(searchParams.get("address") || "");
const startBlockParam = searchParams.get("startBlock");
const [startBlock, setStartBlock] = useState(
startBlockParam ? parseInt(startBlockParam) : undefined
);
const [months, setMonths] = useState<number>(12);
const [totalAmount, setTotalAmount] = useState<number>(0);
const [modifiedUnfreezeBlock, setModifiedUnfreezeBlock] = useState<
number | undefined
>();

const { data: accountDetails, isFetching: accoundDetailsLoading } =
useAccountDetails(address, modifiedUnfreezeBlock);
useAccountDetails(address, startBlock ?? undefined);

useEffect(() => {
setTotalAmount(
Expand All @@ -45,7 +46,7 @@ export function Unfreeze() {

const { mutate: unfreeze, isLoading: txLoading } = useUnfreezeCallback();
const { address: connectedWalletAddress } = useConnectionStore();
const unfreezeBlock = modifiedUnfreezeBlock || accountDetails?.unfreezeBlock;
const unfreezeBlock = accountDetails?.unfreezeBlock;

const { data: unfreezeTxnData, isInitialLoading: unfreezeTxnDataLoading } =
useUnfreezeTxn(
Expand All @@ -65,7 +66,7 @@ export function Unfreeze() {
const onAddressChange = (value: string) => {
setSearchParams(new URLSearchParams(`address=${value}`));
setAddress(value);
setModifiedUnfreezeBlock(undefined);
setStartBlock(undefined);
};

return (
Expand Down Expand Up @@ -108,17 +109,22 @@ export function Unfreeze() {
<UnfreezeBlock
isLoading={accoundDetailsLoading}
unfreezeBlock={unfreezeBlock}
onSubmit={setModifiedUnfreezeBlock}
onSubmit={setStartBlock}
initialUnfreezeBlock={accountDetails?.unfreezeBlock}
/>
<ExpectedStateInit
isLoading={accoundDetailsLoading || unfreezeTxnDataLoading}
stateInitHash={unfreezeTxnData?.stateInitHash}
error={unfreezeTxnData?.error}
/>
<DetailRow isLoading={!unfreezeTxnData} title="Size:">
{unfreezeTxnData?.sizeBits
? `${(unfreezeTxnData.sizeBits / 8 / 1024).toFixed(2)}KB`
: "N/A"}
</DetailRow>
</StyledFlexColumn>
<ActionButton
disabled={!unfreezeTxnData?.stateInitHash}
disabled={!unfreezeTxnData?.stateInitHash || !!unfreezeTxnData.error}
onSubmit={onSubmit}
loading={txLoading}
/>
Expand Down
137 changes: 93 additions & 44 deletions src/lib/findUnfreezeBlock.ts
Original file line number Diff line number Diff line change
@@ -1,64 +1,113 @@
import { Address } from "ton";
import { Address, TonClient4 } from "ton";
import { AccountDetails } from "types";
import { executeV4Function } from "./getClientV4";
import {
accountAtBlock,
accountAtLatestBlock,
latestBlock,
} from "./useAccountDetails";

type ExtractReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

type Account = UnwrapPromise<
ExtractReturnType<TonClient4["getAccount"]>
>["account"];

async function blockFromUtime(utime: number) {
const { shards } = await executeV4Function((tc4) =>
tc4.getBlockByUtime(utime)
);

return shards.find((s) => s.workchain === -1)!.seqno;
}

function statusFromAccount(details: Account) {
console.log(details);
if (details.state.type === "uninit") {
return "uninit";
} else if (details.state.type === "active") {
return "active";
} else if (details.state.type === "frozen") {
if (details.storageStat) {
return "frozen";
} else {
throw new Error("Frozen account without storageStat");
}
}
}

export async function findUnfreezeBlock(
lastKnownAccountDetails: AccountDetails,
account: Address,
overrideBlock: number | undefined,
safetyNumber: number = 0
address: Address,
initialBlockNumber?: number
): Promise<{
unfreezeBlock: number;
lastPaid: number;
activeAccountDetails: AccountDetails;
}> {
if (safetyNumber === 30) {
throw new Error(
"Reached 30 iterations searching for active seqno. Aborting."
);
let safety = 130;

const initialDetails = await accountAtLatestBlock(address);
if (initialDetails[0].state.type === "active") {
throw new Error("Account is already active");
}

let nextSeqno: number;
let blockNumber = initialBlockNumber ?? (await latestBlock());

if (!overrideBlock) {
// Shards from all chains at timestamp
const { shards: shardLastPaid } = await executeV4Function((tc4) =>
tc4.getBlockByUtime(lastKnownAccountDetails.storageStat!.lastPaid)
);
console.log("blockToStartSearchFrom", blockNumber);

// Get masterchain seqno (which we need to query v4)
nextSeqno = shardLastPaid.find((s) => s.workchain === -1)!.seqno - 1;
} else {
nextSeqno = overrideBlock;
}
let account: Account | undefined;

// From this -> https://github.com/ton-community/ton-api-v4/blob/main/src/api/handlers/handleAccountGetLite.ts#L21
// we understand that v4 is always to be queried by a masterchain seqno
const { account: accountDetails } = await executeV4Function((tc4) =>
tc4.getAccount(nextSeqno, account)
);
let lastKnownActiveBlock = Number.MIN_SAFE_INTEGER;
let lastKnownInactiveBlock = Number.MAX_SAFE_INTEGER;

while (safety-- > 0) {
console.log("Inspecting block", blockNumber);

// From this -> https://github.com/ton-community/ton-api-v4/blob/main/src/api/handlers/handleAccountGetLite.ts#L21
// we understand that v4 is always to be queried by a masterchain seqno
account = await accountAtBlock(address, blockNumber);
const status = statusFromAccount(account);

// Account is active so go forward to
if (status === "active") {
// See if we can find a newer block at which the account is active
lastKnownActiveBlock = Math.max(lastKnownActiveBlock, blockNumber);
blockNumber++;
} else if (status === "uninit") {
throw new Error("Account is uninit");
} else if (status === "frozen") {
// See if we can find an older block at which the account is frozen
lastKnownInactiveBlock = Math.min(lastKnownInactiveBlock, blockNumber);
blockNumber = Math.min(
await blockFromUtime(account.storageStat!.lastPaid!),
blockNumber - 1
);
}

console.log("Status", {
status,
lastKnownActiveBlock,
lastKnownInactiveBlock,
});

if (
status == "active" &&
lastKnownInactiveBlock - lastKnownActiveBlock === 1
) {
console.log("Found active account at seqno: ", lastKnownActiveBlock);
blockNumber = lastKnownActiveBlock;
break;
}
}

if (accountDetails.state.type !== "active" && !!accountDetails.storageStat) {
return findUnfreezeBlock(
accountDetails,
account,
overrideBlock,
safetyNumber + 1
);
} else if (
!accountDetails.storageStat &&
accountDetails.state.type === "uninit"
) {
throw new Error(
"Reached uninint block at seqno: " +
nextSeqno +
". Unable to detect active seqno."
);
if (account?.state.type !== "active") {
throw new Error("Unable to find unfreeze block");
}

return {
unfreezeBlock: nextSeqno,
lastPaid: lastKnownAccountDetails.storageStat!.lastPaid,
activeAccountDetails: accountDetails,
unfreezeBlock: blockNumber,
lastPaid: account.storageStat?.lastPaid ?? -1,
activeAccountDetails: account!,
};
}
22 changes: 16 additions & 6 deletions src/lib/getClientV4.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import { TonClient4 } from "ton";
import { getHttpV4Endpoint } from "@orbs-network/ton-access";
import { withRetry } from "./retry";

const clients: TonClient4[] = [];

export async function getClientsV4() {
if (clients.length === 0) {
const tonAccessEndpoint = await getHttpV4Endpoint();
clients.push(
...[
new TonClient4({ endpoint: "https://mainnet-v4.tonhubapi.com" }),
new TonClient4({ endpoint: tonAccessEndpoint }),
]
new TonClient4({ endpoint: "https://mainnet-v4.tonhubapi.com" })
);

try {
const tonAccessEndpoint = await getHttpV4Endpoint();
clients.push(new TonClient4({ endpoint: tonAccessEndpoint }));
} catch (e) {
console.error("Failed to get TonAccess endpoint", e);
}
}

return clients;
Expand All @@ -21,5 +25,11 @@ export async function executeV4Function<T>(
func: (tc: TonClient4) => Promise<T>
) {
const clients = await getClientsV4();
return Promise.any(clients.map(func));

return withRetry(
() => Promise.any(clients.map(func)),
() => ({
retries: 5,
})
);
}
53 changes: 53 additions & 0 deletions src/lib/retry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import promiseRetry from "promise-retry";
import { OperationOptions } from "retry";

export const stringifyErrSliced = (e: unknown, length = 500) =>
JSON.stringify(e, Object.getOwnPropertyNames(e)).slice(0, length);

// todo log warn
export async function withRetry<T>(
fn: () => Promise<T>,
opts: () => OperationOptions,
shouldRetryFn?: (e: unknown) => boolean
): Promise<T> {
let retriesUsed = 0;
let errors: string[] = [];
let lastError: unknown | undefined;

return promiseRetry(
(retry) =>
(async () => {
if (lastError) {
retriesUsed++;

console.log(
`Retrying. Attempt ${retriesUsed} - ${stringifyErrSliced(
lastError,
100
)}`
);
}

return fn();
})().catch((e) => {
if (shouldRetryFn && !shouldRetryFn(e)) {
throw e;
}

lastError = e;

errors = [...errors, stringifyErrSliced(e)];

return retry(e);
}),
opts()
).finally(() => {
if (retriesUsed > 0) {
console.log(`Retried ${retriesUsed} times`, {
errors,
retriesUsed,
retryOpts: opts(),
});
}
});
}
5 changes: 5 additions & 0 deletions src/lib/temp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { TonClient } from "ton";
const tc = new TonClient({
endpoint:
"https://ton.access.orbs.network/4410c0ff5Bd3F8B62C092Ab4D238bEE463E64410/1/mainnet/toncenter-api-v2/jsonRPC",
});
Loading
Loading