Handle account sequence mismatch
error (#13)
All checks were successful
Lint / lint (20.x) (push) Successful in 4m56s
All checks were successful
Lint / lint (20.x) (push) Successful in 4m56s
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75) - Handle failed txs due to `account sequence mismatch` error by creating a wrapper for all tx methods and retry the tx if `account sequence mismatch` error occurs Co-authored-by: IshaVenikar <ishavenikar7@gmail.com> Reviewed-on: #13
This commit is contained in:
parent
3d9aedeb7e
commit
3fa60f3cdf
@ -15,7 +15,7 @@ import {
|
|||||||
ApplicationDeploymentRemovalRequest
|
ApplicationDeploymentRemovalRequest
|
||||||
} from './entity/Deployment';
|
} from './entity/Deployment';
|
||||||
import { AppDeploymentRecord, AppDeploymentRemovalRecord, AuctionParams, DeployerRecord } from './types';
|
import { AppDeploymentRecord, AppDeploymentRemovalRecord, AuctionParams, DeployerRecord } from './types';
|
||||||
import { getConfig, getRepoDetails, sleep } from './utils';
|
import { getConfig, getRepoDetails, registryTransactionWithRetry, sleep } from './utils';
|
||||||
|
|
||||||
const log = debug('snowball:registry');
|
const log = debug('snowball:registry');
|
||||||
|
|
||||||
@ -108,7 +108,8 @@ export class Registry {
|
|||||||
|
|
||||||
const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees);
|
const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees);
|
||||||
|
|
||||||
const result = await this.registry.setRecord(
|
const result = await registryTransactionWithRetry(() =>
|
||||||
|
this.registry.setRecord(
|
||||||
{
|
{
|
||||||
privateKey: this.registryConfig.privateKey,
|
privateKey: this.registryConfig.privateKey,
|
||||||
record: applicationRecord,
|
record: applicationRecord,
|
||||||
@ -116,6 +117,7 @@ export class Registry {
|
|||||||
},
|
},
|
||||||
this.registryConfig.privateKey,
|
this.registryConfig.privateKey,
|
||||||
fee
|
fee
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
log(`Published application record ${result.id}`);
|
log(`Published application record ${result.id}`);
|
||||||
@ -126,33 +128,39 @@ export class Registry {
|
|||||||
log(`Setting name: ${lrn} for record ID: ${result.id}`);
|
log(`Setting name: ${lrn} for record ID: ${result.id}`);
|
||||||
|
|
||||||
await sleep(SLEEP_DURATION);
|
await sleep(SLEEP_DURATION);
|
||||||
await this.registry.setName(
|
await registryTransactionWithRetry(() =>
|
||||||
|
this.registry.setName(
|
||||||
{
|
{
|
||||||
cid: result.id,
|
cid: result.id,
|
||||||
lrn
|
lrn
|
||||||
},
|
},
|
||||||
this.registryConfig.privateKey,
|
this.registryConfig.privateKey,
|
||||||
fee
|
fee
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
await sleep(SLEEP_DURATION);
|
await sleep(SLEEP_DURATION);
|
||||||
await this.registry.setName(
|
await registryTransactionWithRetry(() =>
|
||||||
|
this.registry.setName(
|
||||||
{
|
{
|
||||||
cid: result.id,
|
cid: result.id,
|
||||||
lrn: `${lrn}@${applicationRecord.app_version}`
|
lrn: `${lrn}@${applicationRecord.app_version}`
|
||||||
},
|
},
|
||||||
this.registryConfig.privateKey,
|
this.registryConfig.privateKey,
|
||||||
fee
|
fee
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
await sleep(SLEEP_DURATION);
|
await sleep(SLEEP_DURATION);
|
||||||
await this.registry.setName(
|
await registryTransactionWithRetry(() =>
|
||||||
|
this.registry.setName(
|
||||||
{
|
{
|
||||||
cid: result.id,
|
cid: result.id,
|
||||||
lrn: `${lrn}@${applicationRecord.repository_ref}`
|
lrn: `${lrn}@${applicationRecord.repository_ref}`
|
||||||
},
|
},
|
||||||
this.registryConfig.privateKey,
|
this.registryConfig.privateKey,
|
||||||
fee
|
fee
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -183,7 +191,8 @@ export class Registry {
|
|||||||
const auctionConfig = config.auction;
|
const auctionConfig = config.auction;
|
||||||
|
|
||||||
const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees);
|
const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees);
|
||||||
const auctionResult = await this.registry.createProviderAuction(
|
const auctionResult = await registryTransactionWithRetry(() =>
|
||||||
|
this.registry.createProviderAuction(
|
||||||
{
|
{
|
||||||
commitFee: auctionConfig.commitFee,
|
commitFee: auctionConfig.commitFee,
|
||||||
commitsDuration: auctionConfig.commitsDuration,
|
commitsDuration: auctionConfig.commitsDuration,
|
||||||
@ -196,6 +205,7 @@ export class Registry {
|
|||||||
this.registryConfig.privateKey,
|
this.registryConfig.privateKey,
|
||||||
fee
|
fee
|
||||||
)
|
)
|
||||||
|
);
|
||||||
|
|
||||||
if (!auctionResult.auction) {
|
if (!auctionResult.auction) {
|
||||||
throw new Error('Error creating auction');
|
throw new Error('Error creating auction');
|
||||||
@ -208,7 +218,8 @@ export class Registry {
|
|||||||
type: APP_DEPLOYMENT_AUCTION_RECORD_TYPE,
|
type: APP_DEPLOYMENT_AUCTION_RECORD_TYPE,
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await this.registry.setRecord(
|
const result = await registryTransactionWithRetry(() =>
|
||||||
|
this.registry.setRecord(
|
||||||
{
|
{
|
||||||
privateKey: this.registryConfig.privateKey,
|
privateKey: this.registryConfig.privateKey,
|
||||||
record: applicationDeploymentAuction,
|
record: applicationDeploymentAuction,
|
||||||
@ -216,6 +227,7 @@ export class Registry {
|
|||||||
},
|
},
|
||||||
this.registryConfig.privateKey,
|
this.registryConfig.privateKey,
|
||||||
fee
|
fee
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
log(`Application deployment auction created: ${auctionResult.auction.id}`);
|
log(`Application deployment auction created: ${auctionResult.auction.id}`);
|
||||||
@ -274,7 +286,8 @@ export class Registry {
|
|||||||
|
|
||||||
const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees);
|
const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees);
|
||||||
|
|
||||||
const result = await this.registry.setRecord(
|
const result = await registryTransactionWithRetry(() =>
|
||||||
|
this.registry.setRecord(
|
||||||
{
|
{
|
||||||
privateKey: this.registryConfig.privateKey,
|
privateKey: this.registryConfig.privateKey,
|
||||||
record: applicationDeploymentRequest,
|
record: applicationDeploymentRequest,
|
||||||
@ -282,6 +295,7 @@ export class Registry {
|
|||||||
},
|
},
|
||||||
this.registryConfig.privateKey,
|
this.registryConfig.privateKey,
|
||||||
fee
|
fee
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
log(`Application deployment request record published: ${result.id}`);
|
log(`Application deployment request record published: ${result.id}`);
|
||||||
@ -322,12 +336,14 @@ export class Registry {
|
|||||||
auctionId: string
|
auctionId: string
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees);
|
const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees);
|
||||||
const auction = await this.registry.releaseFunds(
|
const auction = await registryTransactionWithRetry(() =>
|
||||||
|
this.registry.releaseFunds(
|
||||||
{
|
{
|
||||||
auctionId
|
auctionId
|
||||||
},
|
},
|
||||||
this.registryConfig.privateKey,
|
this.registryConfig.privateKey,
|
||||||
fee
|
fee
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
return auction;
|
return auction;
|
||||||
@ -424,7 +440,8 @@ export class Registry {
|
|||||||
|
|
||||||
const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees);
|
const fee = parseGasAndFees(this.registryConfig.fee.gas, this.registryConfig.fee.fees);
|
||||||
|
|
||||||
const result = await this.registry.setRecord(
|
const result = await registryTransactionWithRetry(() =>
|
||||||
|
this.registry.setRecord(
|
||||||
{
|
{
|
||||||
privateKey: this.registryConfig.privateKey,
|
privateKey: this.registryConfig.privateKey,
|
||||||
record: applicationDeploymentRemovalRequest,
|
record: applicationDeploymentRemovalRequest,
|
||||||
@ -432,6 +449,7 @@ export class Registry {
|
|||||||
},
|
},
|
||||||
this.registryConfig.privateKey,
|
this.registryConfig.privateKey,
|
||||||
fee
|
fee
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
log(`Application deployment removal request record published: ${result.id}`);
|
log(`Application deployment removal request record published: ${result.id}`);
|
||||||
|
@ -120,3 +120,24 @@ export const getRepoDetails = async (
|
|||||||
repoUrl
|
repoUrl
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wrapper method for registry txs to retry once if 'account sequence mismatch' occurs
|
||||||
|
export const registryTransactionWithRetry = async (
|
||||||
|
txMethod: () => Promise<any>
|
||||||
|
): Promise<any> => {
|
||||||
|
try {
|
||||||
|
return await txMethod();
|
||||||
|
} catch (error: any) {
|
||||||
|
if (!error.message.includes('account sequence mismatch')) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(`Transaction failed due to account sequence mismatch. Retrying...`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await txMethod();
|
||||||
|
} catch (retryError: any) {
|
||||||
|
throw new Error(`Transaction failed again after retry: ${retryError.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import ConfirmDialog, {
|
import ConfirmDialog, {
|
||||||
ConfirmDialogProps,
|
ConfirmDialogProps,
|
||||||
} from 'components/shared/ConfirmDialog';
|
} from 'components/shared/ConfirmDialog';
|
||||||
import { ArrowRightCircleFilledIcon, LoadingIcon } from 'components/shared/CustomIcon';
|
import {
|
||||||
|
ArrowRightCircleFilledIcon,
|
||||||
|
LoadingIcon,
|
||||||
|
} from 'components/shared/CustomIcon';
|
||||||
|
|
||||||
interface DeleteDeploymentDialogProps extends ConfirmDialogProps {
|
interface DeleteDeploymentDialogProps extends ConfirmDialogProps {
|
||||||
isConfirmButtonLoading?: boolean;
|
isConfirmButtonLoading?: boolean;
|
||||||
@ -20,7 +23,11 @@ export const DeleteDeploymentDialog = ({
|
|||||||
dialogTitle="Delete deployment?"
|
dialogTitle="Delete deployment?"
|
||||||
handleCancel={handleCancel}
|
handleCancel={handleCancel}
|
||||||
open={open}
|
open={open}
|
||||||
confirmButtonTitle={isConfirmButtonLoading ? "Deleting deployment" : "Yes, delete deployment"}
|
confirmButtonTitle={
|
||||||
|
isConfirmButtonLoading
|
||||||
|
? 'Deleting deployment'
|
||||||
|
: 'Yes, delete deployment'
|
||||||
|
}
|
||||||
handleConfirm={handleConfirm}
|
handleConfirm={handleConfirm}
|
||||||
confirmButtonProps={{
|
confirmButtonProps={{
|
||||||
variant: 'danger',
|
variant: 'danger',
|
||||||
|
@ -3,7 +3,11 @@ import { useForm, Controller } from 'react-hook-form';
|
|||||||
import { FormProvider, FieldValues } from 'react-hook-form';
|
import { FormProvider, FieldValues } from 'react-hook-form';
|
||||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
import { useMediaQuery } from 'usehooks-ts';
|
import { useMediaQuery } from 'usehooks-ts';
|
||||||
import { AddEnvironmentVariableInput, AuctionParams, Deployer } from 'gql-client';
|
import {
|
||||||
|
AddEnvironmentVariableInput,
|
||||||
|
AuctionParams,
|
||||||
|
Deployer,
|
||||||
|
} from 'gql-client';
|
||||||
|
|
||||||
import { Select, MenuItem, FormControl, FormHelperText } from '@mui/material';
|
import { Select, MenuItem, FormControl, FormHelperText } from '@mui/material';
|
||||||
|
|
||||||
@ -175,13 +179,13 @@ const Configure = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const fetchDeployers = useCallback(async () => {
|
const fetchDeployers = useCallback(async () => {
|
||||||
const res = await client.getDeployers()
|
const res = await client.getDeployers();
|
||||||
setDeployers(res.deployers)
|
setDeployers(res.deployers);
|
||||||
}, [client])
|
}, [client]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchDeployers()
|
fetchDeployers();
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-7 px-4 py-6">
|
<div className="space-y-7 px-4 py-6">
|
||||||
@ -209,11 +213,18 @@ const Configure = () => {
|
|||||||
<Select
|
<Select
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(event) => onChange(event.target.value)}
|
onChange={(event) => onChange(event.target.value)}
|
||||||
size='small'
|
size="small"
|
||||||
displayEmpty
|
displayEmpty
|
||||||
|
sx={{
|
||||||
|
fontFamily: 'inherit',
|
||||||
|
'& .MuiOutlinedInput-notchedOutline': {
|
||||||
|
borderColor: '#e0e0e0',
|
||||||
|
borderRadius: '8px',
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<MenuItem value="LRN">Deployer LRN</MenuItem>
|
|
||||||
<MenuItem value="Auction">Create Auction</MenuItem>
|
<MenuItem value="Auction">Create Auction</MenuItem>
|
||||||
|
<MenuItem value="LRN">Deployer LRN</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -240,15 +251,22 @@ const Configure = () => {
|
|||||||
value={value}
|
value={value}
|
||||||
onChange={(event) => onChange(event.target.value)}
|
onChange={(event) => onChange(event.target.value)}
|
||||||
displayEmpty
|
displayEmpty
|
||||||
size='small'
|
size="small"
|
||||||
>
|
>
|
||||||
{deployers.map((deployer) => (
|
{deployers.map((deployer) => (
|
||||||
<MenuItem key={deployer.deployerLrn} value={deployer.deployerLrn}>
|
<MenuItem
|
||||||
|
key={deployer.deployerLrn}
|
||||||
|
value={deployer.deployerLrn}
|
||||||
|
>
|
||||||
{deployer.deployerLrn}
|
{deployer.deployerLrn}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
{fieldState.error && <FormHelperText>{fieldState.error.message}</FormHelperText>}
|
{fieldState.error && (
|
||||||
|
<FormHelperText>
|
||||||
|
{fieldState.error.message}
|
||||||
|
</FormHelperText>
|
||||||
|
)}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
|
Tooltip,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
|
||||||
import { Avatar } from 'components/shared/Avatar';
|
import { Avatar } from 'components/shared/Avatar';
|
||||||
@ -104,7 +105,8 @@ const DeploymentDetailsCard = ({
|
|||||||
const renderDeploymentStatus = useCallback(
|
const renderDeploymentStatus = useCallback(
|
||||||
(className?: string) => {
|
(className?: string) => {
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<Tooltip title="Click to view build logs">
|
||||||
|
<div className={className} style={{ cursor: 'pointer' }}>
|
||||||
<Tag
|
<Tag
|
||||||
leftIcon={getIconByDeploymentStatus(deployment.status)}
|
leftIcon={getIconByDeploymentStatus(deployment.status)}
|
||||||
size="xs"
|
size="xs"
|
||||||
@ -114,6 +116,7 @@ const DeploymentDetailsCard = ({
|
|||||||
{deployment.status}
|
{deployment.status}
|
||||||
</Tag>
|
</Tag>
|
||||||
</div>
|
</div>
|
||||||
|
</Tooltip>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[deployment.status, deployment.commitHash],
|
[deployment.status, deployment.commitHash],
|
||||||
|
@ -56,7 +56,7 @@ export const DeploymentMenu = ({
|
|||||||
|
|
||||||
const updateDeployment = async () => {
|
const updateDeployment = async () => {
|
||||||
const isUpdated = await client.updateDeploymentToProd(deployment.id);
|
const isUpdated = await client.updateDeploymentToProd(deployment.id);
|
||||||
if (isUpdated) {
|
if (isUpdated.updateDeploymentToProd) {
|
||||||
await onUpdate();
|
await onUpdate();
|
||||||
toast({
|
toast({
|
||||||
id: 'deployment_changed_to_production',
|
id: 'deployment_changed_to_production',
|
||||||
@ -77,7 +77,7 @@ export const DeploymentMenu = ({
|
|||||||
const redeployToProd = async () => {
|
const redeployToProd = async () => {
|
||||||
const isRedeployed = await client.redeployToProd(deployment.id);
|
const isRedeployed = await client.redeployToProd(deployment.id);
|
||||||
setConfirmButtonLoadingLoading(false);
|
setConfirmButtonLoadingLoading(false);
|
||||||
if (isRedeployed) {
|
if (isRedeployed.redeployToProd) {
|
||||||
await onUpdate();
|
await onUpdate();
|
||||||
toast({
|
toast({
|
||||||
id: 'redeployed_to_production',
|
id: 'redeployed_to_production',
|
||||||
@ -100,7 +100,7 @@ export const DeploymentMenu = ({
|
|||||||
project.id,
|
project.id,
|
||||||
deployment.id,
|
deployment.id,
|
||||||
);
|
);
|
||||||
if (isRollbacked) {
|
if (isRollbacked.rollbackDeployment) {
|
||||||
await onUpdate();
|
await onUpdate();
|
||||||
toast({
|
toast({
|
||||||
id: 'deployment_rolled_back',
|
id: 'deployment_rolled_back',
|
||||||
@ -124,7 +124,7 @@ export const DeploymentMenu = ({
|
|||||||
setIsConfirmDeleteLoading(false);
|
setIsConfirmDeleteLoading(false);
|
||||||
setDeleteDeploymentDialog((preVal) => !preVal);
|
setDeleteDeploymentDialog((preVal) => !preVal);
|
||||||
|
|
||||||
if (isDeleted) {
|
if (isDeleted.deleteDeployment) {
|
||||||
await onUpdate();
|
await onUpdate();
|
||||||
toast({
|
toast({
|
||||||
id: 'deployment_removal_requested',
|
id: 'deployment_removal_requested',
|
||||||
|
@ -105,7 +105,6 @@ export const AuctionCard = ({ project }: { project: Project }) => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div className="flex justify-between items-center mt-1">
|
<div className="flex justify-between items-center mt-1">
|
||||||
<span className="text-elements-high-em text-sm font-medium tracking-tight">
|
<span className="text-elements-high-em text-sm font-medium tracking-tight">
|
||||||
Auction Status
|
Auction Status
|
||||||
@ -131,7 +130,10 @@ export const AuctionCard = ({ project }: { project: Project }) => {
|
|||||||
Deployer Funds Status
|
Deployer Funds Status
|
||||||
</span>
|
</span>
|
||||||
<div className="ml-2">
|
<div className="ml-2">
|
||||||
<Tag size="xs" type={fundsStatus ? 'positive' : 'emphasized'}>
|
<Tag
|
||||||
|
size="xs"
|
||||||
|
type={fundsStatus ? 'positive' : 'emphasized'}
|
||||||
|
>
|
||||||
{fundsStatus ? 'RELEASED' : 'LOCKED'}
|
{fundsStatus ? 'RELEASED' : 'LOCKED'}
|
||||||
</Tag>
|
</Tag>
|
||||||
</div>
|
</div>
|
||||||
|
@ -132,7 +132,7 @@ export const project: Project = {
|
|||||||
deployerApiUrl: 'https://webapp-deployer-api.example.com',
|
deployerApiUrl: 'https://webapp-deployer-api.example.com',
|
||||||
deployerId: 'bafyreicrtgmkir4evvvysxdqxddf2ftdq2wrzuodgvwnxr4rmubi4obdfu',
|
deployerId: 'bafyreicrtgmkir4evvvysxdqxddf2ftdq2wrzuodgvwnxr4rmubi4obdfu',
|
||||||
deployerLrn: 'lrn://deployer.apps.snowballtools.com ',
|
deployerLrn: 'lrn://deployer.apps.snowballtools.com ',
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
webhooks: ['beepboop'],
|
webhooks: ['beepboop'],
|
||||||
icon: 'Icon',
|
icon: 'Icon',
|
||||||
|
Loading…
Reference in New Issue
Block a user