diff --git a/services/watcher/src/api/Transaction.ts b/services/watcher/src/api/Transaction.ts index 2ad616f7..15f9e3e2 100644 --- a/services/watcher/src/api/Transaction.ts +++ b/services/watcher/src/api/Transaction.ts @@ -155,14 +155,14 @@ export class Transaction { }; returnPermitTx = async ( + height: number, RWTCount: bigint, permitBox: wasm.ErgoBox, repoBox: wasm.ErgoBox, widBox: wasm.ErgoBox, - wid: string + wid: string, + feeBox: wasm.ErgoBox | undefined ): Promise<{ tx: wasm.Transaction; remainingRwt: bigint }> => { - const height = await ErgoNetwork.getHeight(); - const R4 = repoBox.register_value(4); const R5 = repoBox.register_value(5); const R6 = repoBox.register_value(6); @@ -189,9 +189,20 @@ export class Transaction { .to_base16_bytes(), BigInt(Transaction.minBoxValue.as_i64().to_str()), {}, - (box) => - Buffer.from(box.register_value(4)?.to_js()).toString('hex') == wid + (box) => { + if (!box.register_value(4)) { + logger.debug('Skipping collateral box without wid information'); + return false; + } + const collateralWid = Buffer.from( + box.register_value(4)?.to_js() + ).toString('hex'); + logger.debug(`Collateral is found for wid: [${collateralWid}]`); + return collateralWid == wid; + } ); + if (collateralBoxes.boxes.length == 0) + throw Error('Collateral box for this wid is not found'); inputBoxes.push(collateralBoxes.boxes[0]); usersOut.splice(widIndex, 1); usersCountOut.splice(widIndex, 1); @@ -212,7 +223,7 @@ export class Transaction { ).toString(), usersOut, usersCountOut, - repoBox.register_value(6)!, + R6, widIndex ) ); @@ -249,7 +260,9 @@ export class Transaction { } else { // All tokens should be unlocked and no need to create a new permit box // But it already has some permits so needs the wid token - logger.debug(`Creating a new wid box permit rwts are all returned`); + logger.debug( + `Creating a new wid box for other permits, all permits in the existing box are returned` + ); outputBoxes.push( Transaction.boxes.createWIDBox( height, @@ -259,6 +272,7 @@ export class Transaction { ) ); } + if (feeBox) inputBoxes.push(feeBox); const totalErgIn = inputBoxes .map((item) => BigInt(item.value().as_i64().to_str())) .reduce((a, b) => a + b, 0n); @@ -269,9 +283,11 @@ export class Transaction { BigInt(Transaction.fee.as_i64().to_str()) + BigInt(Transaction.minBoxValue.as_i64().to_str()); if (totalErgOut > totalErgIn) { + const existingBoxIds = [widBox.box_id().to_str()]; + if (feeBox) existingBoxIds.push(feeBox.box_id().to_str()); const userBoxes = await Transaction.boxes.getUserPaymentBox( totalErgOut - totalErgIn, - [widBox.box_id().to_str()] + existingBoxIds ); userBoxes.forEach((box) => inputBoxes.push(box)); } @@ -351,6 +367,8 @@ export class Transaction { const permitBoxes = await Transaction.boxes.getPermits(WID, RWTCount); let repoBox = await Transaction.boxes.getRepoBox(); let widBox = await Transaction.boxes.getWIDBox(WID); + const height = await ErgoNetwork.getHeight(); + if (widBox.tokens().get(0).id().to_str() != WID) { try { await DetachWID.detachWIDtx( @@ -372,7 +390,8 @@ export class Transaction { } try { let tx: wasm.Transaction, - remainingRwt = RWTCount; + remainingRwt = RWTCount, + feeBox: wasm.ErgoBox | undefined = undefined; const unlockTxIds: Array = []; for (const permitBox of permitBoxes) { const permitRwt = BigInt( @@ -380,21 +399,24 @@ export class Transaction { ); const unlockingRwt = RWTCount > permitRwt ? permitRwt : RWTCount; logger.debug( - `Unlocking ${unlockingRwt} locked in permitBox: [${ - permitBox.box_id().to_str - }], using widBox: [${widBox + `Unlocking ${unlockingRwt} locked in permitBox: [${permitBox + .box_id() + .to_str()}], using widBox: [${widBox .box_id() .to_str()}] and repoBox: [${repoBox.box_id().to_str()}]` ); ({ tx, remainingRwt } = await this.returnPermitTx( + height, unlockingRwt, permitBox, repoBox, widBox, - WID + WID, + feeBox )); repoBox = tx.outputs().get(0); widBox = tx.outputs().get(1); + feeBox = tx.outputs().len() > 3 ? tx.outputs().get(2) : undefined; unlockTxIds.push(tx.id().to_str()); } const isAlreadyWatcher = remainingRwt > 0; @@ -406,7 +428,7 @@ export class Transaction { status: 200, }; } catch (e) { - logger.warn('Unlock operation exited incomplete'); + logger.warn(`Unlock operation exited incomplete by error: ${e.message}`); if (e instanceof NotEnoughFund) { return { response: `Not enough ERG to complete the unlock operation`, diff --git a/services/watcher/src/ergo/boxes.ts b/services/watcher/src/ergo/boxes.ts index 0fedf9ac..a5085061 100644 --- a/services/watcher/src/ergo/boxes.ts +++ b/services/watcher/src/ergo/boxes.ts @@ -19,6 +19,9 @@ import { NotEnoughFund, NoWID } from '../errors/errors'; import { getConfig } from '../config/config'; import { AddressBalance } from './interfaces'; import { JsonBI } from './network/parser'; +import WinstonLogger from '@rosen-bridge/winston-logger'; + +const logger = WinstonLogger.getInstance().getLogger(import.meta.url); export class Boxes { dataBase: WatcherDataBase; @@ -479,6 +482,9 @@ export class Boxes { ); repoBuilder.set_register_value(6, R6); R7 && repoBuilder.set_register_value(7, wasm.Constant.from_i32(R7)); + const boxVal = repoBuilder.calc_min_box_value(); + logger.debug(`calculated value for repo: [${boxVal.as_i64().to_str()}]`); + if (boxVal > this.minBoxValue) repoBuilder.set_value(boxVal); return repoBuilder.build(); }; diff --git a/services/watcher/src/ergo/network/ergoNetwork.ts b/services/watcher/src/ergo/network/ergoNetwork.ts index 7b2e7773..e47d616e 100644 --- a/services/watcher/src/ergo/network/ergoNetwork.ts +++ b/services/watcher/src/ergo/network/ergoNetwork.ts @@ -15,6 +15,7 @@ import { ergoTreeToBase58Address } from '../../utils/utils'; import { ConnectionError } from '../../errors/errors'; import { getConfig } from '../../config/config'; import { ExplorerBox, ErgoAssetInfo } from '../network/types'; +import { MAX_API_LIMIT } from '../../config/constants'; import WinstonLogger from '@rosen-bridge/winston-logger'; const logger = WinstonLogger.getInstance().getLogger(import.meta.url); @@ -125,13 +126,18 @@ export class ErgoNetwork { return tokenRemain + bigIntMax(amount, 0n) > 0; }; while (offset < total && remaining()) { - const boxes = await this.getBoxesForAddress(tree, offset, 10); + const boxes = await this.getBoxesForAddress(tree, offset, MAX_API_LIMIT); const ergoBoxes = wasm.ErgoBoxes.from_boxes_json( boxes.items.map((box) => JsonBI.stringify(box).toString()) ); + logger.debug( + `total boxes: ${total}, offset: ${offset}, number of current boxes: ${boxes.items.length}` + ); for (let i = 0; i < ergoBoxes.len(); i++) { const box = ergoBoxes.get(i); + logger.debug(`processing box with boxId: [${box.box_id().to_str()}]`); if (filter(box)) { + logger.debug(`added box with boxId: [${box.box_id().to_str()}]`); res.push(box); amount -= BigInt(box.value().as_i64().to_str()); if (box.tokens().len() > 0) { @@ -148,7 +154,7 @@ export class ErgoNetwork { if (!remaining()) break; } } - offset += 10; + offset += MAX_API_LIMIT; } return { boxes: res, diff --git a/services/watcher/src/utils/watcherUtils.ts b/services/watcher/src/utils/watcherUtils.ts index a0b39a78..aec84fe6 100644 --- a/services/watcher/src/utils/watcherUtils.ts +++ b/services/watcher/src/utils/watcherUtils.ts @@ -306,6 +306,11 @@ class TransactionUtils { height, requestId ); + logger.debug( + `tx submitted to the queue successfully: [${Buffer.from( + tx.sigma_serialize_bytes() + ).toString('hex')}]` + ); }; } diff --git a/services/watcher/tests/ergo/objects/axios.ts b/services/watcher/tests/ergo/objects/axios.ts index e056d7fe..8e409e80 100644 --- a/services/watcher/tests/ergo/objects/axios.ts +++ b/services/watcher/tests/ergo/objects/axios.ts @@ -2,6 +2,7 @@ import MockAdapter from 'axios-mock-adapter'; import { explorerApi, nodeClient } from '../../../src/ergo/network/ergoNetwork'; import { mockedResponseBody } from './mockedResponseBody'; import { validBox0Token, validBox1Token } from '../../database/mockedData'; +import { MAX_API_LIMIT } from '../../../src/config/constants'; const mockedExplorer = new MockAdapter(explorerApi); const mockedNodeClient = new MockAdapter(nodeClient); @@ -33,7 +34,7 @@ export const initMockedAxios = (vector = 0) => { mockedExplorer .onGet( '/api/v1/boxes/unspent/byErgoTree/10130400040004040400040204000e20a40b86c663fbbfefa243c9c6ebbc5690fc4e385f15b44c49ba469c91c5af0f480404040004000400010104020400040004000e20872fee02938af6c93dff43049ec61b379e75c059b05f39304b3f1ce50cf3ad9305020101d807d601b2a5730000d6028cb2db6308a773010001d603aeb5b4a57302b1a5d901036391b1db630872037303d9010363aedb63087203d901054d0e938c7205017202d604e4c6a7041ad605b2a5730400d606db63087205d607ae7206d901074d0e938c720701720295938cb2db63087201730500017306d196830301ef7203938cb2db6308b2a473070073080001b2720473090095720796830201938cb27206730a0001720293c27205c2a7730bd801d608c2a7d196830501ef720393c27201720893e4c67201041a7204938cb2db6308b2a4730c00730d0001b27204730e00957207d801d609b27206730f0096830701938c720901720293cbc272057310e6c67205051ae6c67205060e93e4c67205070ecb720893e4c67205041a7204938c72090273117312', - { params: { offset: 0, limit: 10 } } + { params: { offset: 0, limit: MAX_API_LIMIT } } ) .reply(200, [mockedResponseBody.watcherPermitLast10Boxes]); @@ -47,7 +48,7 @@ export const initMockedAxios = (vector = 0) => { mockedExplorer .onGet( '/api/v1/boxes/unspent/byErgoTree/101c040204000e20a6ac381e6fa99929fd1477b3ba9499790a775e91d4c14c5aa86e9a118dfac8530101040204000402040404040400040004020402040204000400040004000e2013fe3ae277a195b83048e3e268529118fa4c18cca0931e3b48a8f5fccec75bc9040404000400040204020400040004000400d801d601b2a473000095938cb2db63087201730100017302d17303d811d602db6308a7d603b27202730400d6048c720302d605b2a5730500d606db63087205d607b27206730600d6088c720702d609e4c6a70511d60ab17209d60be4c672050511d60cb1720bd60de4c6a70611d60eb27206730700d60f8c720e02d610b27202730800d6118c721002d6129683050193c27205c2a793e4c672050611720d938cb27206730900018cb27202730a0001938c7207018c720301938c720e018c721001959172047208d806d613e4c67205041ad6149a720a730bd61599720c730cd616c5a7d6179972047208d618b2a5730d00d196830c01721293b17213721493b47213730e7215e4c6a7041a93b27213721500721693720c721493b4720b730f7215720993b2720b7215007217939c7217b2720d73100099720f7211938cb2db6308721873110002721793cbc27218731293e4c67218041a83010e7216938cb2db6308b2a5731300731400017216d804d613e4c6a7041ad614e4c672050704d6159972087204d616b27209721400d19683040172129383010eb27213721400e4c67201041a939c7215b2720d731500997211720f959172167215968302019372169ab2720b7214007215937213e4c67205041ad803d617e4c67205041ad6189a72147316d61999720a731796830501937216721593b4721373187214b472177319721493b472137218720ab472177214721993b47209731a7214b4720b731b721493b472097218720ab4720b72147219', - { params: { offset: 0, limit: 10 } } + { params: { offset: 0, limit: MAX_API_LIMIT } } ) .reply(200, [mockedResponseBody.repoLast10Boxes]); @@ -64,7 +65,7 @@ export const initMockedAxios = (vector = 0) => { mockedExplorer .onGet( '/api/v1/boxes/unspent/byErgoTree/10130400040004040400040204000e20a40b86c663fbbfefa243c9c6ebbc5690fc4e385f15b44c49ba469c91c5af0f480404040004000400010104020400040004000e20872fee02938af6c93dff43049ec61b379e75c059b05f39304b3f1ce50cf3ad9305020101d807d601b2a5730000d6028cb2db6308a773010001d603aeb5b4a57302b1a5d901036391b1db630872037303d9010363aedb63087203d901054d0e938c7205017202d604e4c6a7041ad605b2a5730400d606db63087205d607ae7206d901074d0e938c720701720295938cb2db63087201730500017306d196830301ef7203938cb2db6308b2a473070073080001b2720473090095720796830201938cb27206730a0001720293c27205c2a7730bd801d608c2a7d196830501ef720393c27201720893e4c67201041a7204938cb2db6308b2a4730c00730d0001b27204730e00957207d801d609b27206730f0096830701938c720901720293cbc272057310e6c67205051ae6c67205060e93e4c67205070ecb720893e4c67205041a7204938c72090273117312', - { params: { offset: 0, limit: 10 } } + { params: { offset: 0, limit: MAX_API_LIMIT } } ) .reply(200, [mockedResponseBody.thirdWatcherLast10PermitBox]); @@ -78,7 +79,7 @@ export const initMockedAxios = (vector = 0) => { mockedExplorer .onGet( '/api/v1/boxes/unspent/byErgoTree/101c040204000e20a6ac381e6fa99929fd1477b3ba9499790a775e91d4c14c5aa86e9a118dfac8530101040204000402040404040400040004020402040204000400040004000e2013fe3ae277a195b83048e3e268529118fa4c18cca0931e3b48a8f5fccec75bc9040404000400040204020400040004000400d801d601b2a473000095938cb2db63087201730100017302d17303d811d602db6308a7d603b27202730400d6048c720302d605b2a5730500d606db63087205d607b27206730600d6088c720702d609e4c6a70511d60ab17209d60be4c672050511d60cb1720bd60de4c6a70611d60eb27206730700d60f8c720e02d610b27202730800d6118c721002d6129683050193c27205c2a793e4c672050611720d938cb27206730900018cb27202730a0001938c7207018c720301938c720e018c721001959172047208d806d613e4c67205041ad6149a720a730bd61599720c730cd616c5a7d6179972047208d618b2a5730d00d196830c01721293b17213721493b47213730e7215e4c6a7041a93b27213721500721693720c721493b4720b730f7215720993b2720b7215007217939c7217b2720d73100099720f7211938cb2db6308721873110002721793cbc27218731293e4c67218041a83010e7216938cb2db6308b2a5731300731400017216d804d613e4c6a7041ad614e4c672050704d6159972087204d616b27209721400d19683040172129383010eb27213721400e4c67201041a939c7215b2720d731500997211720f959172167215968302019372169ab2720b7214007215937213e4c67205041ad803d617e4c67205041ad6189a72147316d61999720a731796830501937216721593b4721373187214b472177319721493b472137218720ab472177214721993b47209731a7214b4720b731b721493b472097218720ab4720b72147219', - { params: { offset: 0, limit: 10 } } + { params: { offset: 0, limit: MAX_API_LIMIT } } ) .reply(200, [mockedResponseBody.thirdRepoBox]); break; @@ -159,7 +160,7 @@ export const initMockedAxios = (vector = 0) => { mockedExplorer .onGet( '/api/v1/boxes/unspent/byErgoTree/0008cd034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa', - { params: { offset: 0, limit: 10 } } + { params: { offset: 0, limit: MAX_API_LIMIT } } ) .reply(200, mockedResponseBody.thirdWatcherLastUnspentBox); @@ -173,7 +174,7 @@ export const initMockedAxios = (vector = 0) => { mockedExplorer .onGet( '/api/v1/boxes/unspent/byErgoTree/0008cd03c880d703131f301badf289ceb9b7f86d674e8cbe390461f66e844f507571a1d6', - { params: { offset: 0, limit: 10 } } + { params: { offset: 0, limit: MAX_API_LIMIT } } ) .reply(200, [mockedResponseBody.firstWatcherLast10UnspentBoxes]); @@ -187,7 +188,7 @@ export const initMockedAxios = (vector = 0) => { mockedExplorer .onGet( '/api/v1/boxes/unspent/byErgoTree/0008cd03c29ad59831be2e5baded45a03ce9a7d4c2e83d683e11c79790e76f640d0d3e30', - { params: { offset: 0, limit: 10 } } + { params: { offset: 0, limit: MAX_API_LIMIT } } ) .reply(200, [mockedResponseBody.secondWatcherLast10UnspentBox]);