Skip to content

Commit

Permalink
Merge branch 'dev' into 'master'
Browse files Browse the repository at this point in the history
Dev

See merge request ergo/rosen-bridge/watcher!198
  • Loading branch information
vorujack committed Dec 12, 2023
2 parents 4cad1d6 + 6e2851b commit abe2afc
Show file tree
Hide file tree
Showing 9 changed files with 210 additions and 103 deletions.
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion services/watcher/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
"dependencies": {
"@emurgo/cip14-js": "^3.0.1",
"@rosen-bridge/address-extractor": "^3.0.0",
"@rosen-bridge/health-check": "^3.0.0",
"@rosen-bridge/health-check": "^3.0.1",
"@rosen-bridge/minimum-fee": "^0.1.13",
"@rosen-bridge/observation-extractor": "^4.0.0",
"@rosen-bridge/scanner": "^3.0.0",
Expand Down
252 changes: 170 additions & 82 deletions services/watcher/src/api/Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import WinstonLogger from '@rosen-bridge/winston-logger';
const logger = WinstonLogger.getInstance().getLogger(import.meta.url);

export type ApiResponse = {
response: string;
response: string | string[];
status: number;
};

Expand Down Expand Up @@ -154,71 +154,33 @@ export class Transaction {
}
};

/**
* generating returning permit transaction and send it to the network
* @param RWTCount
*/
returnPermit = async (RWTCount: bigint): Promise<ApiResponse> => {
const activePermitTxs =
await Transaction.watcherDatabase.getActivePermitTransactions();
if (activePermitTxs.length !== 0) {
return {
response: `permit transaction [${activePermitTxs[0].txId}] is in queue`,
status: 400,
};
}

if (!Transaction.watcherPermitState) {
return { response: 'No permit found', status: 400 };
}
const WID = Transaction.watcherWID!;
const height = await ErgoNetwork.getHeight();

const permitBoxes = await Transaction.boxes.getPermits(WID, RWTCount);
const repoBox = await Transaction.boxes.getRepoBox();
const widBox = await Transaction.boxes.getWIDBox(WID);
if (widBox.tokens().get(0).id().to_str() != WID) {
try {
await DetachWID.detachWIDtx(
Transaction.txUtils,
Transaction.boxes,
WID,
widBox
);
return {
response: `WID box is not in valid format (WID token is not the first token), please wait for the correction transaction`,
status: 400,
};
} catch (e) {
return {
response: `WID box is not in valid format, but an error in creating correction transaction: ${e}`,
status: 500,
};
}
}

returnPermitTx = async (
height: number,
RWTCount: bigint,
permitBox: wasm.ErgoBox,
repoBox: wasm.ErgoBox,
widBox: wasm.ErgoBox,
wid: string,
feeBox: wasm.ErgoBox | undefined
): Promise<{ tx: wasm.Transaction; remainingRwt: bigint }> => {
const R4 = repoBox.register_value(4);
const R5 = repoBox.register_value(5);
const R6 = repoBox.register_value(6);

// This couldn't happen
if (!R4 || !R5 || !R6) {
return {
response: 'one of registers (4, 5, 6) of repo box is not set',
status: 500,
};
throw Error('one of registers (4, 5, 6) of repo box is not set');
}

const users = R4.to_coll_coll_byte();

const usersCount: Array<string> | undefined = R5.to_i64_str_array();

const widIndex = users.map((user) => uint8ArrayToHex(user)).indexOf(WID);
const inputBoxes = [repoBox, permitBoxes[0], widBox];
const totalRWT = BigInt(usersCount[widIndex]);
const widIndex = users.map((user) => uint8ArrayToHex(user)).indexOf(wid);
const inputBoxes = [repoBox, permitBox, widBox];
const totalRwt = BigInt(usersCount[widIndex]);
const usersOut = [...users];
const usersCountOut = [...usersCount];
if (totalRWT === RWTCount) {
if (totalRwt === RWTCount) {
// need to add collateral
const collateralBoxes =
await ErgoNetwork.getCoveringErgAndTokenForAddress(
Expand All @@ -227,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);
Expand All @@ -238,10 +211,6 @@ export class Transaction {
BigInt(usersCountOut[widIndex]) - RWTCount
).toString();
}
const inputRWTCount = permitBoxes
.map((box) => BigInt(box.tokens().get(0).amount().as_i64().to_str()))
.reduce((a, b) => a + b, 0n);
permitBoxes.slice(1).forEach((box) => inputBoxes.push(box));
const outputBoxes: Array<ErgoBoxCandidate> = [];
outputBoxes.push(
await Transaction.boxes.createRepo(
Expand All @@ -254,30 +223,56 @@ export class Transaction {
).toString(),
usersOut,
usersCountOut,
repoBox.register_value(6)!,
R6,
widIndex
)
);
let burnToken: { [tokenId: string]: bigint } = {};
if (inputRWTCount > RWTCount) {
const inputRwtCount = BigInt(
permitBox.tokens().get(0).amount().as_i64().to_str()
);
if (RWTCount == totalRwt) {
// Should burn the wid and no need for any new box
logger.debug(`Burning the wid token: [${wid}]`);
burnToken = { [wid]: -1n };
} else if (inputRwtCount > RWTCount) {
// Should create a change permit box and a new wid box
logger.debug(
`Creating a new wid box and permit with rwtCount= [${
inputRwtCount - RWTCount
}]`
);
outputBoxes.push(
await Transaction.boxes.createPermit(
height,
inputRWTCount - RWTCount,
Buffer.from(WID, 'hex')
inputRwtCount - RWTCount,
Buffer.from(wid, 'hex')
)
);
outputBoxes.push(
Transaction.boxes.createWIDBox(
height,
WID,
wid,
Transaction.minBoxValue.as_i64().to_str(),
Transaction.userAddressContract
)
);
} else {
burnToken = { [WID]: -1n };
// 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 for other permits, all permits in the existing box are returned`
);
outputBoxes.push(
Transaction.boxes.createWIDBox(
height,
wid,
Transaction.minBoxValue.as_i64().to_str(),
Transaction.userAddressContract
)
);
}
if (feeBox) inputBoxes.push(feeBox);
const totalErgIn = inputBoxes
.map((item) => BigInt(item.value().as_i64().to_str()))
.reduce((a, b) => a + b, 0n);
Expand All @@ -288,19 +283,13 @@ export class Transaction {
BigInt(Transaction.fee.as_i64().to_str()) +
BigInt(Transaction.minBoxValue.as_i64().to_str());
if (totalErgOut > totalErgIn) {
const userBoxes = await ErgoNetwork.getCoveringErgAndTokenForAddress(
Transaction.userAddressContract.ergo_tree().to_base16_bytes(),
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,
existingBoxIds
);
if (!userBoxes.covered) {
return {
response: `Not enough erg. required [${
totalErgOut - totalErgIn
}] more Ergs`,
status: 400,
};
}
userBoxes.boxes.forEach((box) => inputBoxes.push(box));
userBoxes.forEach((box) => inputBoxes.push(box));
}
// create change box
outputBoxes.push(
Expand Down Expand Up @@ -348,11 +337,110 @@ export class Transaction {
txInputBoxes
);
await Transaction.txUtils.submitTransaction(signedTx, TxType.PERMIT);
const isAlreadyWatcher = totalRWT > RWTCount;
Transaction.watcherUnconfirmedWID = isAlreadyWatcher
? Transaction.watcherWID
: '';
return { response: signedTx.id().to_str(), status: 200 };
logger.info(
`Unlock transaction for ${RWTCount} RWT with txId [${signedTx
.id()
.to_str()}] submitted to the queue`
);
return { tx: signedTx, remainingRwt: totalRwt - inputRwtCount };
};

/**
* generating returning permit transaction and send it to the network
* @param RWTCount
*/
returnPermit = async (RWTCount: bigint): Promise<ApiResponse> => {
const activePermitTxs =
await Transaction.watcherDatabase.getActivePermitTransactions();
if (activePermitTxs.length !== 0) {
return {
response: `permit transaction [${activePermitTxs[0].txId}] is in queue`,
status: 400,
};
}

if (!Transaction.watcherPermitState) {
return { response: 'No permit found', status: 400 };
}
const WID = Transaction.watcherWID!;

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(
Transaction.txUtils,
Transaction.boxes,
WID,
widBox
);
return {
response: `WID box is not in valid format (WID token is not the first token), please wait for the correction transaction`,
status: 400,
};
} catch (e) {
return {
response: `WID box is not in valid format, but an error in creating correction transaction: ${e}`,
status: 500,
};
}
}
try {
let tx: wasm.Transaction,
remainingRwt = RWTCount,
feeBox: wasm.ErgoBox | undefined = undefined;
const unlockTxIds: Array<string> = [];
for (const permitBox of permitBoxes) {
const permitRwt = BigInt(
permitBox.tokens().get(0).amount().as_i64().to_str()
);
const unlockingRwt = RWTCount > permitRwt ? permitRwt : RWTCount;
logger.debug(
`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,
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;
Transaction.watcherUnconfirmedWID = isAlreadyWatcher
? Transaction.watcherWID
: '';
return {
response: unlockTxIds,
status: 200,
};
} catch (e) {
logger.warn(`Unlock operation exited incomplete by error: ${e.message}`);
if (e instanceof NotEnoughFund) {
return {
response: `Not enough ERG to complete the unlock operation`,
status: 400,
};
} else {
return {
response: e.message,
status: 500,
};
}
}
};

/**
Expand Down
2 changes: 1 addition & 1 deletion services/watcher/src/api/permit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ permitRouter.post(
BigInt(RWTCount)
);
if (response.status === 200) {
res.status(200).send({ txId: response.response });
res.status(200).send({ txIds: response.response });
} else {
res.status(response.status).send({ message: response.response });
}
Expand Down
Loading

0 comments on commit abe2afc

Please sign in to comment.