@atomiqlabs/chain-evm 1.0.0-dev.74 → 1.0.0-dev.76

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,6 +9,7 @@ const EVMBtcStoredHeader_1 = require("./headers/EVMBtcStoredHeader");
9
9
  const EVMFees_1 = require("../chain/modules/EVMFees");
10
10
  const BtcRelayAbi_1 = require("./BtcRelayAbi");
11
11
  const ethers_1 = require("ethers");
12
+ const promise_cache_ts_1 = require("promise-cache-ts");
12
13
  function serializeBlockHeader(e) {
13
14
  return new EVMBtcHeader_1.EVMBtcHeader({
14
15
  version: e.getVersion(),
@@ -54,8 +55,8 @@ class EVMBtcRelay extends EVMContractBase_1.EVMContractBase {
54
55
  this.maxHeadersPerTx = 100;
55
56
  this.maxForkHeadersPerTx = 50;
56
57
  this.maxShortForkHeadersPerTx = 100;
57
- this.commitHashCache = new Map;
58
- this.blockHashCache = new Map;
58
+ this.commitHashCache = new promise_cache_ts_1.PromiseLruCache(1000);
59
+ this.blockHashCache = new promise_cache_ts_1.PromiseLruCache(1000);
59
60
  this.bitcoinRpc = bitcoinRpc;
60
61
  }
61
62
  /**
@@ -153,28 +154,24 @@ class EVMBtcRelay extends EVMContractBase_1.EVMContractBase {
153
154
  return null;
154
155
  }
155
156
  getBlock(commitHash, blockHash) {
156
- if (commitHash != null && this.commitHashCache.has(commitHash)) {
157
- logger.debug("getBlock(): Returning block from commit hash cache: ", commitHash);
158
- return Promise.resolve([this.commitHashCache.get(commitHash), commitHash]);
159
- }
160
157
  const blockHashString = blockHash == null ? null : "0x" + Buffer.from([...blockHash]).reverse().toString("hex");
161
- if (blockHashString != null && this.blockHashCache.has(blockHashString)) {
162
- logger.debug("getBlock(): Returning block from block hash cache: ", blockHashString);
163
- const storedBlockheader = this.blockHashCache.get(blockHashString);
164
- return Promise.resolve([storedBlockheader, storedBlockheader.getCommitHash()]);
165
- }
166
- return this.Events.findInContractEvents(["StoreHeader", "StoreForkHeader"], [
158
+ const generator = () => this.Events.findInContractEvents(["StoreHeader", "StoreForkHeader"], [
167
159
  commitHash,
168
160
  blockHashString
169
161
  ], async (event) => {
170
162
  const txTrace = await this.Chain.Transactions.traceTransaction(event.transactionHash);
171
163
  const storedBlockheader = await this.findStoredBlockheaderInTraces(txTrace, event.args.commitHash);
172
- if (storedBlockheader != null) {
173
- this.commitHashCache.set(event.args.commitHash, storedBlockheader);
174
- this.blockHashCache.set(event.args.blockHash, storedBlockheader);
175
- return [storedBlockheader, event.args.commitHash];
176
- }
164
+ if (storedBlockheader == null)
165
+ return null;
166
+ this.commitHashCache.set(event.args.commitHash, Promise.resolve([storedBlockheader, event.args.commitHash]));
167
+ this.blockHashCache.set(event.args.blockHash, Promise.resolve([storedBlockheader, event.args.commitHash]));
168
+ return [storedBlockheader, event.args.commitHash];
177
169
  });
170
+ if (commitHash != null)
171
+ return this.commitHashCache.getOrComputeAsync(commitHash, generator);
172
+ if (blockHashString != null)
173
+ return this.blockHashCache.getOrComputeAsync(blockHashString, generator);
174
+ return null;
178
175
  }
179
176
  async getBlockHeight() {
180
177
  return Number(await this.contract.getBlockheight());
@@ -12,6 +12,7 @@ const EVMAddresses_1 = require("../chain/modules/EVMAddresses");
12
12
  const EVMSpvVaultData_1 = require("./EVMSpvVaultData");
13
13
  const EVMSpvWithdrawalData_1 = require("./EVMSpvWithdrawalData");
14
14
  const EVMFees_1 = require("../chain/modules/EVMFees");
15
+ const promise_cache_ts_1 = require("promise-cache-ts");
15
16
  function decodeUtxo(utxo) {
16
17
  const [txId, vout] = utxo.split(":");
17
18
  return {
@@ -34,7 +35,7 @@ class EVMSpvVaultContract extends EVMContractBase_1.EVMContractBase {
34
35
  super(chainInterface, contractAddress, SpvVaultContractAbi_1.SpvVaultContractAbi, contractDeploymentHeight);
35
36
  this.claimTimeout = 180;
36
37
  this.logger = (0, Utils_1.getLogger)("EVMSpvVaultContract: ");
37
- this.vaultParamsCache = new Map();
38
+ this.vaultParamsCache = new promise_cache_ts_1.PromiseLruCache(5000);
38
39
  this.btcRelay = btcRelay;
39
40
  this.bitcoinRpc = bitcoinRpc;
40
41
  }
@@ -126,8 +127,7 @@ class EVMSpvVaultContract extends EVMContractBase_1.EVMContractBase {
126
127
  }
127
128
  async getVaultData(owner, vaultId) {
128
129
  const vaultState = await this.contract.getVault(owner, vaultId);
129
- let vaultParams = this.vaultParamsCache.get(vaultState.spvVaultParametersCommitment);
130
- if (vaultParams == null) {
130
+ const vaultParams = await this.vaultParamsCache.getOrComputeAsync(vaultState.spvVaultParametersCommitment, async () => {
131
131
  const blockheight = Number(vaultState.openBlockheight);
132
132
  const events = await this.Events.getContractBlockEvents(["Opened"], [
133
133
  "0x" + owner.substring(2).padStart(64, "0"),
@@ -136,9 +136,8 @@ class EVMSpvVaultContract extends EVMContractBase_1.EVMContractBase {
136
136
  const foundEvent = events.find(event => (0, EVMSpvVaultData_1.getVaultParamsCommitment)(event.args.params) === vaultState.spvVaultParametersCommitment);
137
137
  if (foundEvent == null)
138
138
  throw new Error("Valid open event not found!");
139
- vaultParams = foundEvent.args.params;
140
- this.vaultParamsCache.set(vaultState.spvVaultParametersCommitment, vaultParams);
141
- }
139
+ return foundEvent.args.params;
140
+ });
142
141
  if (vaultParams.btcRelayContract.toLowerCase() !== this.btcRelay.contractAddress.toLowerCase())
143
142
  return null;
144
143
  return new EVMSpvVaultData_1.EVMSpvVaultData(owner, vaultId, vaultState, vaultParams);
@@ -24,5 +24,6 @@ export declare class EVMPersistentSigner extends EVMSigner {
24
24
  private startFeeBumper;
25
25
  init(): Promise<void>;
26
26
  stop(): Promise<void>;
27
+ private readonly sendTransactionQueue;
27
28
  sendTransaction(transaction: TransactionRequest, onBeforePublish?: (txId: string, rawTx: string) => Promise<void>): Promise<TransactionResponse>;
28
29
  }
@@ -6,6 +6,7 @@ const ethers_1 = require("ethers");
6
6
  const Utils_1 = require("../../utils/Utils");
7
7
  const EVMFees_1 = require("../chain/modules/EVMFees");
8
8
  const EVMSigner_1 = require("./EVMSigner");
9
+ const promise_queue_ts_1 = require("promise-queue-ts");
9
10
  const WAIT_BEFORE_BUMP = 15 * 1000;
10
11
  const MIN_FEE_INCREASE_ABSOLUTE = 1n * 1000000000n; //1GWei
11
12
  const MIN_FEE_INCREASE_PPM = 100000n; // +10%
@@ -15,6 +16,7 @@ class EVMPersistentSigner extends EVMSigner_1.EVMSigner {
15
16
  this.pendingTxs = new Map();
16
17
  this.stopped = false;
17
18
  this.saveCount = 0;
19
+ this.sendTransactionQueue = new promise_queue_ts_1.PromiseQueue();
18
20
  this.signTransaction = null;
19
21
  this.chainInterface = chainInterface;
20
22
  this.directory = directory;
@@ -71,7 +73,7 @@ class EVMPersistentSigner extends EVMSigner_1.EVMSigner {
71
73
  let _gasPrice = null;
72
74
  let _safeBlockTxCount = null;
73
75
  for (let [nonce, data] of this.pendingTxs) {
74
- if (data.lastBumped < Date.now() - this.waitBeforeBump) {
76
+ if (!data.sending && data.lastBumped < Date.now() - this.waitBeforeBump) {
75
77
  _safeBlockTxCount = await this.chainInterface.provider.getTransactionCount(this.address, this.safeBlockTag);
76
78
  this.confirmedNonce = _safeBlockTxCount;
77
79
  if (_safeBlockTxCount > nonce) {
@@ -169,39 +171,52 @@ class EVMPersistentSigner extends EVMSigner_1.EVMSigner {
169
171
  }
170
172
  return Promise.resolve();
171
173
  }
172
- async sendTransaction(transaction, onBeforePublish) {
173
- if (transaction.nonce != null) {
174
- if (transaction.nonce !== this.pendingNonce + 1)
175
- throw new Error("Invalid transaction nonce!");
176
- this.pendingNonce++;
177
- }
178
- else {
179
- this.pendingNonce++;
180
- transaction.nonce = this.pendingNonce;
181
- }
182
- const tx = {};
183
- for (let key in transaction) {
184
- if (transaction[key] instanceof Promise) {
185
- tx[key] = await transaction[key];
174
+ sendTransaction(transaction, onBeforePublish) {
175
+ return this.sendTransactionQueue.enqueue(async () => {
176
+ if (transaction.nonce != null) {
177
+ if (transaction.nonce !== this.pendingNonce + 1)
178
+ throw new Error("Invalid transaction nonce!");
179
+ this.pendingNonce++;
186
180
  }
187
181
  else {
188
- tx[key] = transaction[key];
182
+ this.pendingNonce++;
183
+ transaction.nonce = this.pendingNonce;
189
184
  }
190
- }
191
- const signedRawTx = await this.account.signTransaction(tx);
192
- const signedTx = ethers_1.Transaction.from(signedRawTx);
193
- if (onBeforePublish != null) {
185
+ const tx = {};
186
+ for (let key in transaction) {
187
+ if (transaction[key] instanceof Promise) {
188
+ tx[key] = await transaction[key];
189
+ }
190
+ else {
191
+ tx[key] = transaction[key];
192
+ }
193
+ }
194
+ const signedRawTx = await this.account.signTransaction(tx);
195
+ const signedTx = ethers_1.Transaction.from(signedRawTx);
196
+ if (onBeforePublish != null) {
197
+ try {
198
+ await onBeforePublish(signedTx.hash, signedRawTx);
199
+ }
200
+ catch (e) {
201
+ this.logger.error("sendTransaction(): Error when calling onBeforePublish function: ", e);
202
+ }
203
+ }
204
+ const pendingTxObject = { txs: [signedTx], lastBumped: Date.now(), sending: true };
205
+ this.pendingTxs.set(transaction.nonce, pendingTxObject);
206
+ this.save();
207
+ this.chainInterface.Transactions._knownTxSet.add(signedTx.hash);
194
208
  try {
195
- await onBeforePublish(signedTx.hash, signedRawTx);
209
+ const result = await this.chainInterface.provider.broadcastTransaction(signedRawTx);
210
+ pendingTxObject.sending = false;
211
+ return result;
196
212
  }
197
213
  catch (e) {
198
- this.logger.error("sendTransaction(): Error when calling onBeforePublish function: ", e);
214
+ this.chainInterface.Transactions._knownTxSet.delete(signedTx.hash);
215
+ this.pendingTxs.delete(transaction.nonce);
216
+ this.pendingNonce--;
217
+ throw e;
199
218
  }
200
- }
201
- this.pendingTxs.set(transaction.nonce, { txs: [signedTx], lastBumped: Date.now() });
202
- this.save();
203
- this.chainInterface.Transactions._knownTxSet.add(signedTx.hash);
204
- return await this.chainInterface.provider.broadcastTransaction(signedRawTx);
219
+ });
205
220
  }
206
221
  }
207
222
  exports.EVMPersistentSigner = EVMPersistentSigner;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atomiqlabs/chain-evm",
3
- "version": "1.0.0-dev.74",
3
+ "version": "1.0.0-dev.76",
4
4
  "description": "EVM specific base implementation",
5
5
  "main": "./dist/index.js",
6
6
  "types:": "./dist/index.d.ts",
@@ -26,7 +26,9 @@
26
26
  "@atomiqlabs/base": "^10.0.0-dev.10",
27
27
  "@noble/hashes": "^1.8.0",
28
28
  "@scure/btc-signer": "1.6.0",
29
- "buffer": "6.0.3"
29
+ "buffer": "6.0.3",
30
+ "promise-cache-ts": "0.0.4",
31
+ "promise-queue-ts": "1.0.0"
30
32
  },
31
33
  "peerDependencies": {
32
34
  "ethers": "^6.15.0"
@@ -10,6 +10,7 @@ import {EVMFees} from "../chain/modules/EVMFees";
10
10
  import {EVMChainInterface} from "../chain/EVMChainInterface";
11
11
  import {BtcRelayAbi} from "./BtcRelayAbi";
12
12
  import {AbiCoder, hexlify} from "ethers";
13
+ import {PromiseLruCache} from "promise-cache-ts";
13
14
 
14
15
  function serializeBlockHeader(e: BtcBlock): EVMBtcHeader {
15
16
  return new EVMBtcHeader({
@@ -191,22 +192,13 @@ export class EVMBtcRelay<B extends BtcBlock>
191
192
  return null;
192
193
  }
193
194
 
194
- private commitHashCache: Map<string, EVMBtcStoredHeader> = new Map<string, EVMBtcStoredHeader>;
195
- private blockHashCache: Map<string, EVMBtcStoredHeader> = new Map<string, EVMBtcStoredHeader>;
195
+ private commitHashCache: PromiseLruCache<string, [EVMBtcStoredHeader, string]> = new PromiseLruCache<string, [EVMBtcStoredHeader, string]>(1000);
196
+ private blockHashCache: PromiseLruCache<string, [EVMBtcStoredHeader, string]> = new PromiseLruCache<string, [EVMBtcStoredHeader, string]>(1000);
196
197
 
197
198
  private getBlock(commitHash?: string, blockHash?: Buffer): Promise<[EVMBtcStoredHeader, string] | null> {
198
- if(commitHash!=null && this.commitHashCache.has(commitHash)) {
199
- logger.debug("getBlock(): Returning block from commit hash cache: ", commitHash);
200
- return Promise.resolve([this.commitHashCache.get(commitHash), commitHash]);
201
- }
202
199
  const blockHashString = blockHash==null ? null : "0x"+Buffer.from([...blockHash]).reverse().toString("hex");
203
- if(blockHashString!=null && this.blockHashCache.has(blockHashString)) {
204
- logger.debug("getBlock(): Returning block from block hash cache: ", blockHashString);
205
- const storedBlockheader = this.blockHashCache.get(blockHashString);
206
- return Promise.resolve([storedBlockheader, storedBlockheader.getCommitHash()]);
207
- }
208
200
 
209
- return this.Events.findInContractEvents(
201
+ const generator = () => this.Events.findInContractEvents<[EVMBtcStoredHeader, string], "StoreHeader" | "StoreForkHeader">(
210
202
  ["StoreHeader", "StoreForkHeader"],
211
203
  [
212
204
  commitHash,
@@ -215,13 +207,18 @@ export class EVMBtcRelay<B extends BtcBlock>
215
207
  async (event) => {
216
208
  const txTrace = await this.Chain.Transactions.traceTransaction(event.transactionHash);
217
209
  const storedBlockheader = await this.findStoredBlockheaderInTraces(txTrace, event.args.commitHash);
218
- if(storedBlockheader!=null) {
219
- this.commitHashCache.set(event.args.commitHash, storedBlockheader);
220
- this.blockHashCache.set(event.args.blockHash, storedBlockheader);
221
- return [storedBlockheader, event.args.commitHash];
222
- }
210
+ if(storedBlockheader==null) return null;
211
+
212
+ this.commitHashCache.set(event.args.commitHash, Promise.resolve([storedBlockheader, event.args.commitHash]));
213
+ this.blockHashCache.set(event.args.blockHash, Promise.resolve([storedBlockheader, event.args.commitHash]));
214
+ return [storedBlockheader, event.args.commitHash];
223
215
  }
224
216
  );
217
+
218
+ if(commitHash!=null) return this.commitHashCache.getOrComputeAsync(commitHash, generator);
219
+ if(blockHashString!=null) return this.blockHashCache.getOrComputeAsync(blockHashString, generator);
220
+
221
+ return null;
225
222
  }
226
223
 
227
224
  private async getBlockHeight(): Promise<number> {
@@ -26,6 +26,7 @@ import {EVMSpvWithdrawalData} from "./EVMSpvWithdrawalData";
26
26
  import {EVMFees} from "../chain/modules/EVMFees";
27
27
  import {EVMBtcStoredHeader} from "../btcrelay/headers/EVMBtcStoredHeader";
28
28
  import {TypedEventLog} from "../typechain/common";
29
+ import {PromiseLruCache} from "promise-cache-ts";
29
30
 
30
31
  function decodeUtxo(utxo: string): {txHash: string, vout: bigint} {
31
32
  const [txId, vout] = utxo.split(":");
@@ -209,13 +210,12 @@ export class EVMSpvVaultContract<ChainId extends string>
209
210
  return frontingAddress;
210
211
  }
211
212
 
212
- private vaultParamsCache: Map<string, SpvVaultParametersStructOutput> = new Map();
213
+ private vaultParamsCache: PromiseLruCache<string, SpvVaultParametersStructOutput> = new PromiseLruCache<string, SpvVaultParametersStructOutput>(5000);
213
214
 
214
215
  async getVaultData(owner: string, vaultId: bigint): Promise<EVMSpvVaultData> {
215
216
  const vaultState = await this.contract.getVault(owner, vaultId);
216
217
 
217
- let vaultParams = this.vaultParamsCache.get(vaultState.spvVaultParametersCommitment);
218
- if(vaultParams==null) {
218
+ const vaultParams = await this.vaultParamsCache.getOrComputeAsync(vaultState.spvVaultParametersCommitment, async () => {
219
219
  const blockheight = Number(vaultState.openBlockheight);
220
220
  const events = await this.Events.getContractBlockEvents(
221
221
  ["Opened"],
@@ -231,9 +231,8 @@ export class EVMSpvVaultContract<ChainId extends string>
231
231
  );
232
232
  if(foundEvent==null) throw new Error("Valid open event not found!");
233
233
 
234
- vaultParams = foundEvent.args.params;
235
- this.vaultParamsCache.set(vaultState.spvVaultParametersCommitment, vaultParams);
236
- }
234
+ return foundEvent.args.params;
235
+ });
237
236
 
238
237
  if(vaultParams.btcRelayContract.toLowerCase()!==this.btcRelay.contractAddress.toLowerCase()) return null;
239
238
 
@@ -10,6 +10,7 @@ import {EVMBlockTag} from "../chain/modules/EVMBlocks";
10
10
  import {EVMChainInterface} from "../chain/EVMChainInterface";
11
11
  import {EVMFees} from "../chain/modules/EVMFees";
12
12
  import {EVMSigner} from "./EVMSigner";
13
+ import {PromiseQueue} from "promise-queue-ts";
13
14
 
14
15
  const WAIT_BEFORE_BUMP = 15*1000;
15
16
  const MIN_FEE_INCREASE_ABSOLUTE = 1n*1_000_000_000n; //1GWei
@@ -21,7 +22,8 @@ export class EVMPersistentSigner extends EVMSigner {
21
22
 
22
23
  private pendingTxs: Map<number, {
23
24
  txs: Transaction[],
24
- lastBumped: number
25
+ lastBumped: number,
26
+ sending?: boolean //Not saved
25
27
  }> = new Map();
26
28
 
27
29
  private confirmedNonce: number;
@@ -127,7 +129,7 @@ export class EVMPersistentSigner extends EVMSigner {
127
129
  let _safeBlockTxCount: number = null;
128
130
 
129
131
  for(let [nonce, data] of this.pendingTxs) {
130
- if(data.lastBumped<Date.now()-this.waitBeforeBump) {
132
+ if(!data.sending && data.lastBumped<Date.now()-this.waitBeforeBump) {
131
133
  _safeBlockTxCount = await this.chainInterface.provider.getTransactionCount(this.address, this.safeBlockTag);
132
134
  this.confirmedNonce = _safeBlockTxCount;
133
135
  if(_safeBlockTxCount > nonce) {
@@ -241,42 +243,56 @@ export class EVMPersistentSigner extends EVMSigner {
241
243
  return Promise.resolve();
242
244
  }
243
245
 
244
- async sendTransaction(transaction: TransactionRequest, onBeforePublish?: (txId: string, rawTx: string) => Promise<void>): Promise<TransactionResponse> {
245
- if(transaction.nonce!=null) {
246
- if(transaction.nonce !== this.pendingNonce + 1)
247
- throw new Error("Invalid transaction nonce!");
248
- this.pendingNonce++;
249
- } else {
250
- this.pendingNonce++;
251
- transaction.nonce = this.pendingNonce;
252
- }
246
+ private readonly sendTransactionQueue: PromiseQueue = new PromiseQueue();
253
247
 
254
- const tx: TransactionRequest = {};
255
- for(let key in transaction) {
256
- if(transaction[key] instanceof Promise) {
257
- tx[key] = await transaction[key];
248
+ sendTransaction(transaction: TransactionRequest, onBeforePublish?: (txId: string, rawTx: string) => Promise<void>): Promise<TransactionResponse> {
249
+ return this.sendTransactionQueue.enqueue(async () => {
250
+ if(transaction.nonce!=null) {
251
+ if(transaction.nonce !== this.pendingNonce + 1)
252
+ throw new Error("Invalid transaction nonce!");
253
+ this.pendingNonce++;
258
254
  } else {
259
- tx[key] = transaction[key];
255
+ this.pendingNonce++;
256
+ transaction.nonce = this.pendingNonce;
260
257
  }
261
- }
262
258
 
263
- const signedRawTx = await this.account.signTransaction(tx);
264
- const signedTx = Transaction.from(signedRawTx);
259
+ const tx: TransactionRequest = {};
260
+ for(let key in transaction) {
261
+ if(transaction[key] instanceof Promise) {
262
+ tx[key] = await transaction[key];
263
+ } else {
264
+ tx[key] = transaction[key];
265
+ }
266
+ }
265
267
 
266
- if(onBeforePublish!=null) {
267
- try {
268
- await onBeforePublish(signedTx.hash, signedRawTx);
269
- } catch (e) {
270
- this.logger.error("sendTransaction(): Error when calling onBeforePublish function: ", e);
268
+ const signedRawTx = await this.account.signTransaction(tx);
269
+ const signedTx = Transaction.from(signedRawTx);
270
+
271
+ if(onBeforePublish!=null) {
272
+ try {
273
+ await onBeforePublish(signedTx.hash, signedRawTx);
274
+ } catch (e) {
275
+ this.logger.error("sendTransaction(): Error when calling onBeforePublish function: ", e);
276
+ }
271
277
  }
272
- }
273
278
 
274
- this.pendingTxs.set(transaction.nonce, {txs: [signedTx], lastBumped: Date.now()});
275
- this.save();
279
+ const pendingTxObject = {txs: [signedTx], lastBumped: Date.now(), sending: true};
280
+ this.pendingTxs.set(transaction.nonce, pendingTxObject);
281
+ this.save();
276
282
 
277
- this.chainInterface.Transactions._knownTxSet.add(signedTx.hash);
283
+ this.chainInterface.Transactions._knownTxSet.add(signedTx.hash);
278
284
 
279
- return await this.chainInterface.provider.broadcastTransaction(signedRawTx);
285
+ try {
286
+ const result = await this.chainInterface.provider.broadcastTransaction(signedRawTx);
287
+ pendingTxObject.sending = false;
288
+ return result;
289
+ } catch (e) {
290
+ this.chainInterface.Transactions._knownTxSet.delete(signedTx.hash);
291
+ this.pendingTxs.delete(transaction.nonce);
292
+ this.pendingNonce--;
293
+ throw e;
294
+ }
295
+ });
280
296
  }
281
297
 
282
298
  }