Skip to content

Commit

Permalink
Merge pull request #11 from ton-blockchain/cell_size
Browse files Browse the repository at this point in the history
show cell size error + improve searching algo
  • Loading branch information
EmelyanenkoK authored Aug 27, 2024
2 parents 85100d2 + f75172c commit 7ba6308
Show file tree
Hide file tree
Showing 11 changed files with 7,483 additions and 122 deletions.
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

0 comments on commit 7ba6308

Please sign in to comment.