diff --git a/config.json b/config.json index 7917ba87c..70601b820 100644 --- a/config.json +++ b/config.json @@ -147,4 +147,4 @@ } } ] -} \ No newline at end of file +} diff --git a/src/components/c2d/compute_engine_docker.ts b/src/components/c2d/compute_engine_docker.ts index 9d7966800..2983f77cc 100644 --- a/src/components/c2d/compute_engine_docker.ts +++ b/src/components/c2d/compute_engine_docker.ts @@ -131,7 +131,7 @@ export class C2DEngineDocker extends C2DEngine { envConfig.fees[feeChain][i].prices.length > 0 ) { if (!envConfig.fees[feeChain][i].feeToken) { - const tokenAddress = await getOceanTokenAddressForChain(parseInt(feeChain)) + const tokenAddress = getOceanTokenAddressForChain(parseInt(feeChain)) if (tokenAddress) { envConfig.fees[feeChain][i].feeToken = tokenAddress tmpFees.push(envConfig.fees[feeChain][i]) @@ -937,7 +937,7 @@ export class C2DEngineDocker extends C2DEngine { } // resources are now available, let's start the job const { algorithm } = job - if (algorithm.meta.container && algorithm.meta.container.dockerfile) { + if (algorithm?.meta.container && algorithm?.meta.container.dockerfile) { job.status = C2DStatusNumber.BuildImage job.statusText = C2DStatusText.BuildImage this.buildImage(job, null) @@ -1040,7 +1040,7 @@ export class C2DEngineDocker extends C2DEngine { containerInfo.HostConfig.IpcMode = advancedConfig.IpcMode if (advancedConfig.ShmSize) containerInfo.HostConfig.ShmSize = advancedConfig.ShmSize - if (job.algorithm.meta.container.entrypoint) { + if (job.algorithm?.meta.container.entrypoint) { const newEntrypoint = job.algorithm.meta.container.entrypoint.replace( '$ALGO', 'data/transformations/algorithm' @@ -1338,7 +1338,7 @@ export class C2DEngineDocker extends C2DEngine { // console.error('Container not found! ' + e.message) } try { - const volume = await this.docker.getVolume(job.jobId + '-volume') + const volume = this.docker.getVolume(job.jobId + '-volume') if (volume) { try { await volume.remove() @@ -1349,7 +1349,7 @@ export class C2DEngineDocker extends C2DEngine { } catch (e) { // console.error('Container volume not found! ' + e.message) } - if (job.algorithm.meta.container && job.algorithm.meta.container.dockerfile) { + if (job.algorithm?.meta.container && job.algorithm?.meta.container.dockerfile) { const image = getAlgorithmImage(job.algorithm, job.jobId) if (image) { try { diff --git a/src/components/core/utils/escrow.ts b/src/components/core/utils/escrow.ts index ed775692f..b6658d3c7 100644 --- a/src/components/core/utils/escrow.ts +++ b/src/components/core/utils/escrow.ts @@ -5,6 +5,7 @@ import { EscrowAuthorization, EscrowLock } from '../../../@types/Escrow.js' import { getOceanArtifactsAdressesByChainId } from '../../../utils/address.js' import { RPCS } from '../../../@types/blockchain.js' import { create256Hash } from '../../../utils/crypt.js' +import { sleep } from '../../../utils/util.js' export class Escrow { private networks: RPCS private claimDurationTimeout: number @@ -14,7 +15,7 @@ export class Escrow { } // eslint-disable-next-line require-await - async getEscrowContractAddressForChain(chainId: number): Promise { + getEscrowContractAddressForChain(chainId: number): string | null { const addresses = getOceanArtifactsAdressesByChainId(chainId) if (addresses && addresses.Escrow) return addresses.Escrow return null @@ -24,9 +25,17 @@ export class Escrow { return maxJobDuration + this.claimDurationTimeout } - async getPaymentAmountInWei(cost: number, chain: number, token: string) { + async getPaymentAmountInWei( + cost: number, + chain: number, + token: string, + existingChain?: Blockchain + ) { const { rpc, network, chainId, fallbackRPCs } = this.networks[chain] - const blockchain = new Blockchain(rpc, network, chainId, fallbackRPCs) + let blockchain: Blockchain = existingChain + if (!blockchain) { + blockchain = new Blockchain(rpc, network, chainId, fallbackRPCs) + } const provider = blockchain.getProvider() const decimalgBigNumber = await getDatatokenDecimals(token, provider) @@ -46,11 +55,8 @@ export class Escrow { } // eslint-disable-next-line require-await - async getContract( - chainId: number, - signer: ethers.Signer - ): Promise { - const address = await this.getEscrowContractAddressForChain(chainId) + getContract(chainId: number, signer: ethers.Signer): ethers.Contract | null { + const address = this.getEscrowContractAddressForChain(chainId) if (!address) return null return new ethers.Contract(address, EscrowJson.abi, signer) } @@ -58,12 +64,16 @@ export class Escrow { async getUserAvailableFunds( chain: number, payer: string, - token: string + token: string, + existingChain?: Blockchain ): Promise { const { rpc, network, chainId, fallbackRPCs } = this.networks[chain] - const blockchain = new Blockchain(rpc, network, chainId, fallbackRPCs) + let blockchain: Blockchain = existingChain + if (!blockchain) { + blockchain = new Blockchain(rpc, network, chainId, fallbackRPCs) + } const signer = blockchain.getSigner() - const contract = await this.getContract(chainId, signer) + const contract = this.getContract(chainId, signer) try { const funds = await contract.getUserFunds(payer, token) return funds.available @@ -81,7 +91,7 @@ export class Escrow { const { rpc, network, chainId, fallbackRPCs } = this.networks[chain] const blockchain = new Blockchain(rpc, network, chainId, fallbackRPCs) const signer = blockchain.getSigner() - const contract = await this.getContract(chainId, signer) + const contract = this.getContract(chainId, signer) try { return await contract.getLocks(token, payer, payee) } catch (e) { @@ -93,12 +103,16 @@ export class Escrow { chain: number, token: string, payer: string, - payee: string + payee: string, + existingChain?: Blockchain ): Promise { const { rpc, network, chainId, fallbackRPCs } = this.networks[chain] - const blockchain = new Blockchain(rpc, network, chainId, fallbackRPCs) + let blockchain: Blockchain = existingChain + if (!blockchain) { + blockchain = new Blockchain(rpc, network, chainId, fallbackRPCs) + } const signer = blockchain.getSigner() - const contract = await this.getContract(chainId, signer) + const contract = this.getContract(chainId, signer) try { return await contract.getAuthorizations(token, payer, payee) } catch (e) { @@ -118,24 +132,43 @@ export class Escrow { const { rpc, network, chainId, fallbackRPCs } = this.networks[chain] const blockchain = new Blockchain(rpc, network, chainId, fallbackRPCs) const signer = blockchain.getSigner() - const contract = await this.getContract(chainId, signer) + const contract = this.getContract(chainId, signer) if (!contract) throw new Error(`Failed to initialize escrow contract`) - const wei = await this.getPaymentAmountInWei(amount, chain, token) - const userBalance = await this.getUserAvailableFunds(chain, payer, token) + const wei = await this.getPaymentAmountInWei(amount, chain, token, blockchain) + const userBalance = await this.getUserAvailableFunds(chain, payer, token, blockchain) if (BigInt(userBalance.toString()) < BigInt(wei)) { // not enough funds throw new Error(`User ${payer} does not have enough funds`) } - const auths = await this.getAuthorizations( - chain, - token, - payer, - await signer.getAddress() - ) + const signerAddress = await signer.getAddress() + + let retries = 2 + let auths: EscrowAuthorization[] = [] + while (retries > 0) { + auths = await this.getAuthorizations(chain, token, payer, signerAddress, blockchain) + if (!auths || auths.length !== 1) { + console.log( + `No escrow auths found for: chain=${chain}, token=${token}, payer=${payer}, nodeAddress=${signerAddress}. Found ${ + auths?.length || 0 + } authorizations. ${retries > 0 ? 'Retrying..' : ''}` + ) + } else if (auths && auths.length === 1) { + break + } + if (retries > 1) { + await sleep(1000) + } + retries-- + } if (!auths || auths.length !== 1) { - throw new Error(`No escrow auths found`) + throw new Error( + `No escrow auths found for: chain=${chain}, token=${token}, payer=${payer}, nodeAddress=${signerAddress}. Found ${ + auths?.length || 0 + } authorizations.` + ) } + if ( BigInt(auths[0].currentLockedAmount.toString()) + BigInt(wei) > BigInt(auths[0].maxLockedAmount.toString()) @@ -173,8 +206,8 @@ export class Escrow { const { rpc, network, chainId, fallbackRPCs } = this.networks[chain] const blockchain = new Blockchain(rpc, network, chainId, fallbackRPCs) const signer = blockchain.getSigner() - const contract = await this.getContract(chainId, signer) - const wei = await this.getPaymentAmountInWei(amount, chain, token) + const contract = this.getContract(chainId, signer) + const wei = await this.getPaymentAmountInWei(amount, chain, token, blockchain) const jobId = create256Hash(job) if (!contract) return null try { @@ -216,7 +249,7 @@ export class Escrow { const blockchain = new Blockchain(rpc, network, chainId, fallbackRPCs) const signer = blockchain.getSigner() const jobId = create256Hash(job) - const contract = await this.getContract(chainId, signer) + const contract = this.getContract(chainId, signer) if (!contract) return null try { diff --git a/src/utils/address.ts b/src/utils/address.ts index 605f634d5..9760ed3b5 100644 --- a/src/utils/address.ts +++ b/src/utils/address.ts @@ -30,7 +30,6 @@ export function getOceanArtifactsAdresses(): any { */ export function getOceanArtifactsAdressesByChainId(chain: number): any { try { - // eslint-disable-next-line security/detect-non-literal-fs-filename const data = getOceanArtifactsAdresses() if (data) { const networks = Object.keys(data) @@ -53,7 +52,7 @@ export function getOceanArtifactsAdressesByChainId(chain: number): any { } // eslint-disable-next-line require-await -export function getOceanTokenAddressForChain(chainId: number): Promise { +export function getOceanTokenAddressForChain(chainId: number): string | null { const addresses = getOceanArtifactsAdressesByChainId(chainId) if (addresses && addresses.Ocean) return addresses.Ocean return null diff --git a/src/utils/blockchain.ts b/src/utils/blockchain.ts index 38da1abd5..03b19fdad 100644 --- a/src/utils/blockchain.ts +++ b/src/utils/blockchain.ts @@ -26,6 +26,8 @@ const MUMBAI_NETWORK_ID = 80001 const SEPOLIA_NETWORK_ID = 11155111 export class Blockchain { + private static signers: Map = new Map() + private static providers: Map = new Map() private signer: Signer private provider: JsonRpcApiProvider private chainId: number @@ -46,12 +48,30 @@ export class Blockchain { } this.network = new ethers.Network(chainName, chainId) // this.provider = new ethers.JsonRpcProvider(rpc, this.network) - this.provider = new ethers.JsonRpcProvider(rpc, null, { - staticNetwork: ethers.Network.from(chainId) - }) + const providerKey = `${chainId}-${rpc}` + if (Blockchain.providers.has(providerKey)) { + this.provider = Blockchain.providers.get(providerKey) + } else { + this.provider = new ethers.JsonRpcProvider(rpc, null, { + staticNetwork: ethers.Network.from(chainId) + }) + Blockchain.providers.set(providerKey, this.provider) + } + this.registerForNetworkEvents() // always use this signer, not simply provider.getSigner(0) for instance (as we do on many tests) - this.signer = new ethers.Wallet(process.env.PRIVATE_KEY, this.provider) + const signerKey = `${chainId}-${process.env.PRIVATE_KEY}` + if (Blockchain.signers.has(signerKey)) { + let cachedSigner = Blockchain.signers.get(signerKey) + if (cachedSigner.provider !== this.provider) { + cachedSigner = (cachedSigner as ethers.Wallet).connect(this.provider) + Blockchain.signers.set(signerKey, cachedSigner) + } + this.signer = cachedSigner + } else { + this.signer = new ethers.Wallet(process.env.PRIVATE_KEY, this.provider) + Blockchain.signers.set(signerKey, this.signer) + } } public getSigner(): Signer {