Skip to content

Commit

Permalink
Merge branch '119-evm-improvement' into 'dev'
Browse files Browse the repository at this point in the history
Resolve "Evm Tx generation and logging improvement"

Closes #119

See merge request ergo/rosen-bridge/rosen-chains!143
  • Loading branch information
zargarzadehm committed Oct 6, 2024
2 parents 80782d2 + 8d98a09 commit 9ef5d62
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 32 deletions.
5 changes: 5 additions & 0 deletions .changeset/fifty-melons-search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rosen-chains/evm-rpc': patch
---

add CALL_EXCEPTION log info to thrown error
5 changes: 5 additions & 0 deletions .changeset/soft-panthers-learn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rosen-chains/evm': minor
---

enable transaction chaining by limitting max parallel transactions to single nonce
31 changes: 17 additions & 14 deletions packages/chains/evm/lib/EvmChain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { RosenTokens } from '@rosen-bridge/tokens';
import JSONBigInt from '@rosen-bridge/json-bigint';
import {
AbstractChain,
MaxParallelTxError,
ChainUtils,
NotEnoughAssetsError,
PaymentOrder,
Expand All @@ -16,7 +15,6 @@ import {
BlockInfo,
ImpossibleBehavior,
TransactionFormatError,
SinglePayment,
TokenInfo,
ValidityStatus,
} from '@rosen-chains/abstract-chain';
Expand Down Expand Up @@ -109,18 +107,23 @@ abstract class EvmChain extends AbstractChain<Transaction> {
let nextNonce = await this.network.getAddressNextAvailableNonce(
this.configs.addresses.lock
);
const waiting =
unsignedTransactions.filter(
(tx) => Serializer.deserialize(tx.txBytes).nonce === nextNonce
).length +
serializedSignedTransactions.filter(
(tx) =>
Serializer.deserialize(Buffer.from(tx, 'hex')).nonce === nextNonce
).length;
if (waiting > this.configs.maxParallelTx) {
throw new MaxParallelTxError(
`There are [${waiting}] transactions already in the process`
);
const nonceCount = new Map<number, number>();
unsignedTransactions.map((tx) => {
const nonce = Serializer.deserialize(tx.txBytes).nonce;
const count = nonceCount.get(nonce);
count !== undefined
? nonceCount.set(nonce, count + 1)
: nonceCount.set(nonce, 1);
});
serializedSignedTransactions.map((tx) => {
const nonce = Serializer.deserialize(Buffer.from(tx, 'hex')).nonce;
const count = nonceCount.get(nonce);
count !== undefined
? nonceCount.set(nonce, count + 1)
: nonceCount.set(nonce, 1);
});
while ((nonceCount.get(nextNonce) ?? 0) >= this.configs.maxParallelTx) {
nextNonce++;
}

const gasPrice = await this.network.getMaxFeePerGas();
Expand Down
74 changes: 58 additions & 16 deletions packages/chains/evm/tests/EvmChain.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,18 +349,27 @@ describe('EvmChain', () => {
});

/**
* @target EvmChain.generateMultipleTransactions should throw error
* @target EvmChain.generateMultipleTransactions should generate tx with next nonce
* when next available nonce was already used for maximum allowed times
* @dependencies
* @scenario
* - fill the unsigned and signed transactions lists with mock data
* - mock hasLockAddressEnoughAssets and getAddressNextNonce
* - call the function
* - check returned value
* @expected
* - throw MaxParallelTxError
* - PaymentTransaction txType, eventId and network should be as
* expected
* - extracted order of generated transaction should be the same as input
* order
* - eventId should be properly in the transaction data
* - no extra data should be found in the transaction data
* - transaction must be of type 2 and has no blobs
* - nonce must be the increamented next available nonce
* - gas limit should be as expected
*/
it('should throw error when next available nonce was already used for maximum allowed times', async () => {
const order = TestData.multipleOrders;
it('should generate tx with next nonce when next available nonce was already used for maximum allowed times', async () => {
const order = TestData.nativePaymentOrder;
const eventId = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb';
const txType = TransactionType.payment;
const unsigned = TestData.paralelTransactions.map((elem) => {
Expand All @@ -377,21 +386,54 @@ describe('EvmChain', () => {
const signed = TestData.paralelTransactions.map((elem) =>
Buffer.from(Serializer.signedSerialize(elem)).toString('hex')
);
const nonce = 53;

// mock hasLockAddressEnoughAssets, getAddressNextNonce,
// mock hasLockAddressEnoughAssets, getMaxFeePerGas,
// getGasRequired, getAddressNextNonce, getMaxPriorityFeePerGas
const requiredGas = 21000n;
testUtils.mockHasLockAddressEnoughAssets(evmChain, true);
testUtils.mockGetAddressNextAvailableNonce(network, 53);
testUtils.mockGetMaxFeePerGas(network, 10n);
testUtils.mockGetGasRequired(network, requiredGas);
testUtils.mockGetAddressNextAvailableNonce(network, nonce);
testUtils.mockGetMaxPriorityFeePerGas(network, 10n);

// run test and expect error
expect(async () => {
await evmChain.generateMultipleTransactions(
eventId,
txType,
order,
unsigned,
signed
);
}).rejects.toThrow(MaxParallelTxError);
// run test
const evmTx = await evmChain.generateMultipleTransactions(
eventId,
txType,
order,
unsigned,
signed
);

// check returned value
expect(evmTx[0].txType).toEqual(txType);
expect(evmTx[0].eventId).toEqual(eventId);
expect(evmTx[0].network).toEqual(evmChain.CHAIN);

// extracted order of generated transaction should be the same as input order
const extractedOrder = evmChain.extractTransactionOrder(evmTx[0]);
expect(extractedOrder).toEqual(order);

const tx = Serializer.deserialize(evmTx[0].txBytes);

// check eventId encoded at the end of the data
expect(tx.data.substring(2, 34)).toEqual(eventId);

// check there is no more data
expect(tx.data.length).toEqual(34);

// check transaction type
expect(tx.type).toEqual(2);

// check blobs zero
expect(tx.maxFeePerBlobGas).toEqual(null);

// check nonce
expect(tx.nonce).toEqual(nonce + 1);

// check gas limit
expect(tx.gasLimit).toEqual(requiredGas * 3n); // requiredGas * gasLimitMultiplier
});

/**
Expand Down
7 changes: 5 additions & 2 deletions packages/networks/evm-rpc/lib/EvmRpcNetwork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,13 +311,16 @@ class EvmRpcNetwork extends AbstractEvmNetwork {
return gas;
} catch (e: unknown) {
const baseError = `Failed to get required Gas from ${this.chain} RPC: `;
if (isCallException(e))
if (isCallException(e)) {
this.logger.debug(
`Gas estimation failed on chain [${
this.chain
}] due to CALL_EXCEPTION: ${JsonBigInt.stringify(e)}`
);
throw new UnexpectedApiError(baseError + `${e}`);
throw new UnexpectedApiError(
baseError + `CALL_EXCEPTION: ${JsonBigInt.stringify(e.info)}`
);
} else throw new UnexpectedApiError(baseError + `${e}`);
}
};

Expand Down

0 comments on commit 9ef5d62

Please sign in to comment.