Skip to content

Commit

Permalink
Merge branch '421-arbitrary-order-defects' into 'dev'
Browse files Browse the repository at this point in the history
Resolve "Fix Arbitrary Order defects"

Closes #421

See merge request ergo/rosen-bridge/guard-service!438
  • Loading branch information
zargarzadehm committed Jan 22, 2025
2 parents ac7d827 + c1182df commit 43993e8
Show file tree
Hide file tree
Showing 10 changed files with 531 additions and 49 deletions.
5 changes: 5 additions & 0 deletions .changeset/mighty-pens-run.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'guard-service': patch
---

Fix arbitrary order defections
21 changes: 16 additions & 5 deletions src/agreement/TxAgreement.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { EventStatus, TransactionStatus } from '../utils/constants';
import {
EventStatus,
OrderStatus,
TransactionStatus,
} from '../utils/constants';
import {
CandidateTransaction,
TransactionRequest,
Expand Down Expand Up @@ -542,7 +546,7 @@ class TxAgreement extends Communicator {
tx,
GuardPkHandler.getInstance().requiredSign
);
await this.updateEventOfApprovedTx(tx);
await this.updateEventOrOrderOfApprovedTx(tx);
} else {
if (txRecord.status === TransactionStatus.invalid) {
logger.debug(
Expand Down Expand Up @@ -573,10 +577,10 @@ class TxAgreement extends Communicator {
};

/**
* updates event status for a tx
* updates event or order status for a tx
* @param tx
*/
protected updateEventOfApprovedTx = async (
protected updateEventOrOrderOfApprovedTx = async (
tx: PaymentTransaction
): Promise<void> => {
try {
Expand All @@ -590,9 +594,16 @@ class TxAgreement extends Communicator {
tx.eventId,
EventStatus.inReward
);
else if (tx.txType === TransactionType.arbitrary)
await DatabaseAction.getInstance().setOrderStatus(
tx.eventId,
OrderStatus.inProcess
);
} catch (e) {
logger.warn(
`An error occurred while setting database event [${tx.eventId}] status: ${e}`
`An error occurred while setting database ${
tx.txType === TransactionType.arbitrary ? 'order' : 'event'
} [${tx.eventId}] status: ${e}`
);
logger.warn(e.stack);
}
Expand Down
12 changes: 9 additions & 3 deletions src/api/arbitrary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,27 @@ const orderRoute = (server: FastifySeverInstance) => {
},
async (request, reply) => {
const { id, chain, orderJson } = request.body;
if (!Configs.isArbitraryOrderRequestActive)
if (!Configs.isArbitraryOrderRequestActive) {
reply.status(400).send({
message: `Arbitrary order request is disabled in config`,
});
return;
}

if (!SUPPORTED_CHAINS.includes(chain))
if (!SUPPORTED_CHAINS.includes(chain)) {
reply.status(400).send({
message: `Invalid value for chain (chain [${chain}] is not supported)`,
});
return;
}

// id should be a 32bytes hex string
if (id.length !== 64 || !id.match(/^[0-9a-f]+$/))
if (id.length !== 64 || !id.match(/^[0-9a-f]+$/)) {
reply.status(400).send({
message: `Invalid value for id (expected 32Bytes hex string, found [${id}])`,
});
return;
}

try {
// try to decode order
Expand Down
76 changes: 54 additions & 22 deletions src/db/DatabaseHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import Configs from '../configs/Configs';
import { DefaultLoggerFactory } from '@rosen-bridge/abstract-logger';
import { DuplicateOrder, DuplicateTransaction } from '../utils/errors';
import GuardsErgoConfigs from '../configs/GuardsErgoConfigs';
import { ArbitraryEntity } from './entities/ArbitraryEntity';
import { TransactionEntity } from './entities/TransactionEntity';

const logger = DefaultLoggerFactory.getInstance().getLogger(import.meta.url);

Expand All @@ -31,21 +33,35 @@ class DatabaseHandler {
.txSignSemaphore.acquire()
.then(async (release) => {
try {
const event = await DatabaseAction.getInstance().getEventById(
newTx.eventId
);
if (
event === null &&
newTx.txType !== TransactionType.coldStorage &&
newTx.txType !== TransactionType.manual
) {
throw new Error(`Event [${newTx.eventId}] not found`);
switch (newTx.txType) {
case TransactionType.payment:
case TransactionType.reward: {
const event = await DatabaseAction.getInstance().getEventById(
newTx.eventId
);
if (event === null)
throw new Error(`Event [${newTx.eventId}] not found`);
await this.insertEventOrOderTx(newTx, event, null, requiredSign);
break;
}
case TransactionType.manual: {
await this.insertManualTx(newTx, requiredSign, overwrite);
break;
}
case TransactionType.coldStorage: {
await this.insertColdStorageTx(newTx, requiredSign);
break;
}
case TransactionType.arbitrary: {
const order = await DatabaseAction.getInstance().getOrderById(
newTx.eventId
);
if (order === null)
throw new Error(`Order [${newTx.eventId}] not found`);
await this.insertEventOrOderTx(newTx, null, order, requiredSign);
break;
}
}

if (event) await this.insertEventTx(newTx, event, requiredSign);
else if (newTx.txType === TransactionType.manual)
await this.insertManualTx(newTx, requiredSign, overwrite);
else await this.insertColdStorageTx(newTx, requiredSign);
release();
} catch (e) {
release();
Expand All @@ -59,17 +75,31 @@ class DatabaseHandler {
* if already another approved tx exists, keeps the one with loser txId
* @param newTx the transaction
* @param event the event trigger
* @param order the arbitrary order
* @param requiredSign
*/
private static insertEventTx = async (
private static insertEventOrOderTx = async (
newTx: PaymentTransaction,
event: ConfirmedEventEntity,
event: ConfirmedEventEntity | null,
order: ArbitraryEntity | null,
requiredSign: number
): Promise<void> => {
const txs = await DatabaseAction.getInstance().getEventValidTxsByType(
event.id,
newTx.txType
);
let txs: TransactionEntity[];
let eventOrOrderId: string;
if (event !== null) {
txs = await DatabaseAction.getInstance().getEventValidTxsByType(
event.id,
newTx.txType
);
eventOrOrderId = event.id;
} else if (order !== null) {
txs = await DatabaseAction.getInstance().getOrderValidTxs(order.id);
eventOrOrderId = order.id;
} else {
throw new ImpossibleBehavior(
`The Order nor the event is passed while inserting tx [${newTx.txId}]`
);
}
if (txs.length > 1) {
throw new ImpossibleBehavior(
`Event [${newTx.eventId}] has already more than 1 (${txs.length}) active ${newTx.txType} tx`
Expand All @@ -94,7 +124,9 @@ class DatabaseHandler {
);
} else {
logger.warn(
`Received approval for newTx [${newTx.txId}] where its event [${event.id}] has already an advanced oldTx [${tx.txId}]`
`Received approval for newTx [${newTx.txId}] where its ${
event !== null ? `event` : `order`
} [${eventOrOrderId}] has already an advanced oldTx [${tx.txId}]`
);
}
}
Expand All @@ -103,7 +135,7 @@ class DatabaseHandler {
newTx,
event,
requiredSign,
null
order
);
};

Expand Down
26 changes: 25 additions & 1 deletion src/transaction/TransactionProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import Configs from '../configs/Configs';
import { DatabaseAction } from '../db/DatabaseAction';
import { TransactionEntity } from '../db/entities/TransactionEntity';
import ChainHandler from '../handlers/ChainHandler';
import { EventStatus, TransactionStatus } from '../utils/constants';
import {
EventStatus,
OrderStatus,
TransactionStatus,
} from '../utils/constants';
import * as TransactionSerializer from './TransactionSerializer';
import { DefaultLoggerFactory } from '@rosen-bridge/abstract-logger';
import { NotificationHandler } from '../handlers/NotificationHandler';
Expand Down Expand Up @@ -222,6 +226,15 @@ class TransactionProcessor {
logger.info(
`Tx [${tx.txId}] is confirmed. Event [${tx.event.id}] is complete`
);
} else if (tx.type === TransactionType.arbitrary) {
// set order as complete
await DatabaseAction.getInstance().setOrderStatus(
tx.order.id,
OrderStatus.completed
);
logger.info(
`Tx [${tx.txId}] is confirmed. Order [${tx.order.id}] is complete`
);
} else {
// no need to do anything about event, just log that tx confirmed
logger.info(
Expand Down Expand Up @@ -328,6 +341,17 @@ class TransactionProcessor {
`Tx [${tx.txId}] is invalid. Event [${tx.event.id}] is now waiting for reward distribution. Reason: ${invalidationDetails.reason}`
);
break;
case TransactionType.arbitrary:
await DatabaseAction.getInstance().setOrderStatus(
tx.order.id,
OrderStatus.pending,
false,
invalidationDetails?.unexpected
);
logger.info(
`Tx [${tx.txId}] is invalid. Order [${tx.order.id}] is now waiting for payment. Reason: ${invalidationDetails.reason}`
);
break;
case TransactionType.coldStorage:
logger.info(
`Cold storage tx [${tx.txId}] is invalid. Reason: ${invalidationDetails.reason}`
Expand Down
4 changes: 2 additions & 2 deletions tests/agreement/TestTxAgreement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ class TestTxAgreement extends TxAgreement {

callSetTxAsApproved = (tx: PaymentTransaction) => this.setTxAsApproved(tx);

callUpdateEventOfApprovedTx = (tx: PaymentTransaction) =>
this.updateEventOfApprovedTx(tx);
callUpdateEventOrOrderOfApprovedTx = (tx: PaymentTransaction) =>
this.updateEventOrOrderOfApprovedTx(tx);

getSigner = () => this.signer;
}
Expand Down
60 changes: 54 additions & 6 deletions tests/agreement/TxAgreement.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ import {
} from '../../src/agreement/Interfaces';
import * as EventTestData from '../event/testData';
import EventSerializer from '../../src/event/EventSerializer';
import { EventStatus, TransactionStatus } from '../../src/utils/constants';
import {
EventStatus,
OrderStatus,
TransactionStatus,
} from '../../src/utils/constants';
import { cloneDeep } from 'lodash-es';
import TransactionVerifier from '../../src/verification/TransactionVerifier';
import { CARDANO_CHAIN } from '@rosen-chains/cardano';

describe('TxAgreement', () => {
describe('addTransactionToQueue', () => {
Expand Down Expand Up @@ -2124,13 +2129,13 @@ describe('TxAgreement', () => {
});
});

describe('updateEventOfApprovedTx', () => {
describe('updateEventOrOrderOfApprovedTx', () => {
beforeEach(async () => {
await DatabaseActionMock.clearTables();
});

/**
* @target TxAgreement.updateEventOfApprovedTx should update
* @target TxAgreement.updateEventOrOrderOfApprovedTx should update
* event status from pending-payment to in-payment
* @dependencies
* - database
Expand Down Expand Up @@ -2160,7 +2165,7 @@ describe('TxAgreement', () => {

// run test
const txAgreement = new TestTxAgreement();
await txAgreement.callUpdateEventOfApprovedTx(paymentTx);
await txAgreement.callUpdateEventOrOrderOfApprovedTx(paymentTx);

// event status should be updated in db
const dbEvents = (await DatabaseActionMock.allEventRecords()).map(
Expand All @@ -2171,7 +2176,7 @@ describe('TxAgreement', () => {
});

/**
* @target TxAgreement.updateEventOfApprovedTx should update
* @target TxAgreement.updateEventOrOrderOfApprovedTx should update
* event status from pending-reward to in-reward
* @dependencies
* - database
Expand Down Expand Up @@ -2201,7 +2206,7 @@ describe('TxAgreement', () => {

// run test
const txAgreement = new TestTxAgreement();
await txAgreement.callUpdateEventOfApprovedTx(paymentTx);
await txAgreement.callUpdateEventOrOrderOfApprovedTx(paymentTx);

// event status should be updated in db
const dbEvents = (await DatabaseActionMock.allEventRecords()).map(
Expand All @@ -2210,6 +2215,49 @@ describe('TxAgreement', () => {
expect(dbEvents.length).toEqual(1);
expect(dbEvents).to.deep.contain([eventId, EventStatus.inReward]);
});

/**
* @target TxAgreement.updateEventOrOrderOfApprovedTx should update
* order status from pending to in-process
* @dependencies
* - database
* @scenario
* - mock testdata
* - insert mocked order into db
* - run test
* - check order status in db
* @expected
* - order status should be updated in db
*/
it('should update order status from pending to in-process', async () => {
// mock testdata
const orderId = 'order-id';
const orderChain = CARDANO_CHAIN;
const paymentTx = mockPaymentTransaction(
TransactionType.arbitrary,
orderChain,
orderId
);

// insert mocked order into db
await DatabaseActionMock.insertOrderRecord(
orderId,
orderChain,
`orderJson`,
OrderStatus.pending
);

// run test
const txAgreement = new TestTxAgreement();
await txAgreement.callUpdateEventOrOrderOfApprovedTx(paymentTx);

// order status should be updated in db
const dbOrders = (await DatabaseActionMock.allOrderRecords()).map(
(order) => [order.id, order.status]
);
expect(dbOrders.length).toEqual(1);
expect(dbOrders).to.deep.contain([orderId, OrderStatus.inProcess]);
});
});

describe('resendTransactionRequests', () => {
Expand Down
Loading

0 comments on commit 43993e8

Please sign in to comment.