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

fix/ issue #164: Race condition in init() of connector classes #197

Merged
merged 11 commits into from
Oct 16, 2023
28 changes: 16 additions & 12 deletions src/chains/cosmos/cosmos-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { BigNumber } from 'ethers';
import { AccountData, DirectSignResponse } from '@cosmjs/proto-signing';

import { IndexedTx, setupIbcExtension } from '@cosmjs/stargate';
import { logger } from '../../services/logger';

//Cosmos
const { DirectSecp256k1Wallet } = require('@cosmjs/proto-signing');
Expand Down Expand Up @@ -58,8 +59,7 @@ export class CosmosBase {
private _tokenMap: Record<string, Token> = {};

private _ready: boolean = false;
private _initializing: boolean = false;
private _initPromise: Promise<void> = Promise.resolve();
private _initialized: Promise<boolean> = Promise.resolve(false);

public chainName;
public rpcUrl;
Expand Down Expand Up @@ -93,17 +93,21 @@ export class CosmosBase {
}

async init(): Promise<void> {
if (!this.ready() && !this._initializing) {
this._initializing = true;
this._initPromise = this.loadTokens(
this.tokenListSource,
this.tokenListType
).then(() => {
this._ready = true;
this._initializing = false;
});
await this._initialized; // Wait for any previous init() calls to complete
if (!this.ready()) {
// If we're not ready, this._initialized will be a Promise that resolves after init() completes
this._initialized = (async () => {
try {
await this.loadTokens(this.tokenListSource, this.tokenListType)
return true;
} catch (e) {
logger.error(`Failed to initialize ${this.chainName} chain: ${e}`);
return false;
}
})();
this._ready = await this._initialized; // Wait for the initialization to complete
}
return this._initPromise;
return;
}

async loadTokens(
Expand Down
48 changes: 28 additions & 20 deletions src/chains/ethereum/ethereum-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export class EthereumBase {
private _tokenMap: Record<string, TokenInfo> = {};
// there are async values set in the constructor
private _ready: boolean = false;
private _initializing: boolean = false;
private _initialized: Promise<boolean> = Promise.resolve(false);
public chainName;
public chainId;
public rpcUrl;
Expand Down Expand Up @@ -121,14 +121,22 @@ export class EthereumBase {
}

async init(): Promise<void> {
if (!this.ready() && !this._initializing) {
this._initializing = true;
await this._nonceManager.init(
async (address) => await this.provider.getTransactionCount(address)
);
await this.loadTokens(this.tokenListSource, this.tokenListType);
this._ready = true;
this._initializing = false;
await this._initialized; // Wait for any previous init() calls to complete
if (!this.ready()) {
// If we're not ready, this._initialized will be a Promise that resolves after init() completes
this._initialized = (async () => {
try {
await this._nonceManager.init(
async (address) => await this.provider.getTransactionCount(address)
);
await this.loadTokens(this.tokenListSource, this.tokenListType);
return true;
} catch (e) {
logger.error(`Failed to initialize ${this.chainName} chain: ${e}`);
return false;
}
})();
this._ready = await this._initialized; // Wait for the initialization to complete
}
return;
}
Expand Down Expand Up @@ -241,7 +249,7 @@ export class EthereumBase {
const balance: BigNumber = await contract.balanceOf(wallet.address);
logger.info(
`Raw balance of ${contract.address} for ` +
`${wallet.address}: ${balance.toString()}`
`${wallet.address}: ${balance.toString()}`
);
return { value: balance, decimals: decimals };
}
Expand All @@ -255,10 +263,10 @@ export class EthereumBase {
): Promise<TokenValue> {
logger.info(
'Requesting spender ' +
spender +
' allowance for owner ' +
wallet.address +
'.'
spender +
' allowance for owner ' +
wallet.address +
'.'
);
const allowance = await contract.allowance(wallet.address, spender);
logger.info(allowance);
Expand Down Expand Up @@ -311,12 +319,12 @@ export class EthereumBase {
): Promise<Transaction> {
logger.info(
'Calling approve method called for spender ' +
spender +
' requesting allowance ' +
amount.toString() +
' from owner ' +
wallet.address +
'.'
spender +
' requesting allowance ' +
amount.toString() +
' from owner ' +
wallet.address +
'.'
);
return this.nonceManager.provideNonce(
nonce,
Expand Down
90 changes: 49 additions & 41 deletions src/chains/injective/injective.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export interface InjectiveWallet {
export class Injective {
private static _instances: LRUCache<string, Injective>;
private _ready: boolean = false;
private _initializing: boolean = false;
private _initialized: Promise<boolean> = Promise.resolve(false);

private _network: Network;
private _chainId: ChainId;
Expand Down Expand Up @@ -152,48 +152,56 @@ export class Injective {
}

public async init(): Promise<void> {
if (!this.ready() && !this._initializing) {
this._initializing = true;
// initialize nonce manager
await this._nonceManager.init(
async (address: string) => await this.getTransactionCount(address)
);
// start updating block number
this._blockUpdateIntervalID = setInterval(async () => {
await this.updateCurrentBlockNumber();
}, 2000) as unknown as number;

// get tokens
const rawMarkets = await this._spotApi.fetchMarkets();
for (const market of rawMarkets) {
if (market.baseToken) {
const token = {
address: '',
chainId: chainIdToInt(this._chainId),
name: market.baseToken.name,
decimals: market.baseToken.decimals,
symbol: market.baseToken.symbol,
denom: market.baseDenom,
};
this._symbolToToken[market.baseToken.symbol] = token;
this._denomToToken[market.baseDenom] = token;
}
await this._initialized; // Wait for any previous init() calls to complete
if (!this.ready()) {
// If we're not ready, this._initialized will be a Promise that resolves after init() completes
this._initialized = (async () => {
try {
// initialize nonce manager
await this._nonceManager.init(
async (address: string) => await this.getTransactionCount(address)
);
// start updating block number
this._blockUpdateIntervalID = setInterval(async () => {
await this.updateCurrentBlockNumber();
}, 2000) as unknown as number;

// get tokens
const rawMarkets = await this._spotApi.fetchMarkets();
for (const market of rawMarkets) {
if (market.baseToken) {
const token = {
address: '',
chainId: chainIdToInt(this._chainId),
name: market.baseToken.name,
decimals: market.baseToken.decimals,
symbol: market.baseToken.symbol,
denom: market.baseDenom,
};
this._symbolToToken[market.baseToken.symbol] = token;
this._denomToToken[market.baseDenom] = token;
}

if (market.quoteToken) {
const token = {
address: '',
chainId: chainIdToInt(this._chainId),
name: market.quoteToken.name,
decimals: market.quoteToken.decimals,
symbol: market.quoteToken.symbol,
denom: market.quoteDenom,
};
this._symbolToToken[market.quoteToken.symbol] = token;
this._denomToToken[market.quoteDenom] = token;
if (market.quoteToken) {
const token = {
address: '',
chainId: chainIdToInt(this._chainId),
name: market.quoteToken.name,
decimals: market.quoteToken.decimals,
symbol: market.quoteToken.symbol,
denom: market.quoteDenom,
};
this._symbolToToken[market.quoteToken.symbol] = token;
this._denomToToken[market.quoteDenom] = token;
}
}
return true;
} catch (e) {
logger.error(`Failed to initialize ${this.chainName} chain: ${e}`);
return false;
}
this._ready = true;
this._initializing = false;
}
})();
this._ready = await this._initialized; // Wait for the initialization to complete
}
return;
}
Expand Down
34 changes: 20 additions & 14 deletions src/chains/near/near.base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,7 @@ export class NearBase {
private _tokenMap: Record<string, TokenInfo> = {};
// there are async values set in the constructor
private _ready: boolean = false;
private _initializing: boolean = false;
private _initPromise: Promise<void> = Promise.resolve();
private _initialized: Promise<boolean> = Promise.resolve(false);
private _keyStore: keyStores.InMemoryKeyStore;
private _connection: Near | undefined;

Expand Down Expand Up @@ -107,18 +106,25 @@ export class NearBase {
}

async init(): Promise<void> {
if (!this.ready() && !this._initializing) {
this._initializing = true;
this._connection = await this.connectProvider();
this._initPromise = this.loadTokens(
this.tokenListSource,
this.tokenListType
).then(() => {
this._ready = true;
this._initializing = false;
});
await this._initialized; // Wait for any previous init() calls to complete
if (!this.ready()) {
// If we're not ready, this._initialized will be a Promise that resolves after init() completes
this._initialized = (async () => {
try {
this._connection = await this.connectProvider();
await this.loadTokens(
this.tokenListSource,
this.tokenListType
)
return true;
} catch (e) {
logger.error(`Failed to initialize ${this.chainName} chain: ${e}`);
return false;
}
})();
this._ready = await this._initialized; // Wait for the initialization to complete
}
return this._initPromise;
return;
}

async connectProvider(): Promise<Near> {
Expand Down Expand Up @@ -260,7 +266,7 @@ export class NearBase {
}
logger.info(
`Raw balance of ${contract.contractId} for ` +
`${contract.account.accountId}: ${balance}`
`${contract.account.accountId}: ${balance}`
);
return balance;
}
Expand Down
32 changes: 19 additions & 13 deletions src/chains/tezos/tezos.base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@ export class TezosBase {
private _contractStorageMap: Record<string, any> = {};

private _ready: boolean = false;
private _initializing: boolean = false;
private _initPromise: Promise<void> = Promise.resolve();
private _initialized: Promise<boolean> = Promise.resolve(false);

public chainName: string = 'tezos';
public rpcUrl: string;
Expand Down Expand Up @@ -77,18 +76,25 @@ export class TezosBase {
}

async init(): Promise<void> {
if (!this.ready() && !this._initializing) {
this._initializing = true;
this._initPromise = this.loadTokens(
this.tokenListSource,
this.tokenListType
).then(() => {
this._ready = true;
this._initializing = false;
});
this.provider.setRpcProvider(this.rpcUrl);
await this._initialized; // Wait for any previous init() calls to complete
if (!this.ready()) {
// If we're not ready, this._initialized will be a Promise that resolves after init() completes
this._initialized = (async () => {
try {
await this.loadTokens(
this.tokenListSource,
this.tokenListType
);
this.provider.setRpcProvider(this.rpcUrl);
return true;
} catch (e) {
logger.error(`Failed to initialize ${this.chainName} chain: ${e}`);
return false;
}
})();
this._ready = await this._initialized; // Wait for the initialization to complete
}
return this._initPromise;
return;
}

private async loadTokens(
Expand Down
Loading
Loading