Skip to content

Commit

Permalink
Adds pegout tracker tool
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremy-then committed Feb 13, 2024
1 parent 62c846d commit ebfa8c7
Show file tree
Hide file tree
Showing 5 changed files with 457 additions and 3 deletions.
96 changes: 96 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,102 @@ const newParams = {
monitor.reset(newParams);

```

## Pegout Tracker

You can use the `tool/pegout-tracker/pegout-tracker.js` to track a pegout request, from start to finish.

To use it, you only need a pegout transaction hash and a network. Then, you can call `PegoutTarcker::trackPegout` and subscribe to the `PEGOUT_TRACKER_EVENTS.pegoutStagesFound` event, like this:

```js
const util = require('util');
const PegoutTracker = require('./pegout-tracker');
const { PEGOUT_TRACKER_EVENTS } = require('./pegout-tracker-utils');

const pegoutTxHash = '0x...'; // Needs to be the tx hash that the user got when sending funds to the bridge to request a pegout.
const network = 'mainnet'; // Can be 'mainnet' or 'testnet'

const pegoutTracker = new PegoutTracker();

// This will be executed once all the pegout information has been gathered.
pegoutTracker.on(PEGOUT_TRACKER_EVENTS.pegoutStagesFound, bridgeTxDetails => {
console.info('pegoutStagesFound: ')
console.info(util.inspect(bridgeTxDetails, {depth: null, colors: true}));
});

pegoutTracker.trackPegout(pegoutTxHash, network);

```

If no `network` is provided, `mainnet` will be the default.

Or, you can subscribe to all the stages events, like this:

```js

const util = require('util');
const PegoutTracker = require('./pegout-tracker');
const { PEGOUT_TRACKER_EVENTS } = require('./pegout-tracker-utils');

const pegoutTxHash = '0x...'; // Needs to be the tx hash that the user got when sending funds to the bridge to request a pegout.
const network = 'mainnet'; // Can be 'mainnet' or 'testnet'

const pegoutTracker = new PegoutTracker();

pegoutTracker.on(PEGOUT_TRACKER_EVENTS.releaseRequestRejectedEventFound, bridgeTxDetails => {
console.info('Pegout stage 1 (pegout request rejected) transaction found: ');
console.info(util.inspect(bridgeTxDetails, {depth: null, colors: true}));
});

pegoutTracker.on(PEGOUT_TRACKER_EVENTS.releaseRequestReceivedEventFound, bridgeTxDetails => {
console.info('Pegout stage 1 (pegout request) transaction found: ');
console.info(util.inspect(bridgeTxDetails, {depth: null, colors: true}));
});

pegoutTracker.on(PEGOUT_TRACKER_EVENTS.releaseRequestedEventFound, bridgeTxDetails => {
console.info('Pegout stage 2 (pegout created) transaction found: ');
console.info(util.inspect(bridgeTxDetails, {depth: null, colors: true}));
});

pegoutTracker.on(PEGOUT_TRACKER_EVENTS.batchPegoutCreatedEventFound, bridgeTxDetails => {
console.info('Pegout stage 2 (batch pegout created) transaction found: ');
console.info(util.inspect(bridgeTxDetails, {depth: null, colors: true}));
});

pegoutTracker.on(PEGOUT_TRACKER_EVENTS.pegoutConfirmedEventFound, bridgeTxDetails => {
console.info('Pegout stage 3 (confirmations) transaction found: ');
console.info(util.inspect(bridgeTxDetails, {depth: null, colors: true}));
});

pegoutTracker.on(PEGOUT_TRACKER_EVENTS.addSignatureEventFound, bridgeTxDetails => {
console.info('Pegout stage 4 (signatures) transaction found: ');
console.info(util.inspect(bridgeTxDetails, {depth: null, colors: true}));
});

pegoutTracker.on(PEGOUT_TRACKER_EVENTS.releaseBtcEventFound, bridgeTxDetails => {
console.info('Pegout stage 4 (release) transaction found: ');
console.info(util.inspect(bridgeTxDetails, {depth: null, colors: true}));
});

pegoutTracker.on(PEGOUT_TRACKER_EVENTS.pegoutStagesFound, bridgeTxDetails => {
console.info('pegoutStagesFound: ')
console.info(util.inspect(bridgeTxDetails, {depth: null, colors: true}));
});

pegoutTracker.trackPegout(pegoutTxHash, network);

```

Or, you can use the `cli-pegout-tracker` tool, like this:

```sh
node tool/pegout-tracker/cli-pegout-tracker.js --pegoutTxHash=0xa6397d264cae18a1b3ace7a33b24580fd759c075ee27feb7eb9e6d19f50ff3ee --network=mainnet
```

If no `--network` is provided, `mainnet` will be the default.

Note: Before using the tool to find a pegout information, make sure the pegout has already been completed. Because the tool will try skiping blocks, will go to future blocks based on `nextPegoutHeight` and the amount of confirmations required for each network. If it tries to go to a non existing block, then the tool will throw an error.

## Contributing

Any comments or suggestions feel free to contribute or reach out at our [open slack](https://dev.rootstock.io//slack).
64 changes: 64 additions & 0 deletions tool/pegout-tracker/cli-pegout-tracker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
const util = require('util');
const PegoutTracker = require('./pegout-tracker');
const { PEGOUT_TRACKER_EVENTS } = require('./pegout-tracker-utils');

const getParsedParams = () => {
const params = process.argv.filter(param => param.startsWith('--'))
.reduce((params, param) => {
if(param.startsWith('--pegoutTxHash')) {
params.pegoutTxHash = param.slice(param.indexOf('=') + 1);
} else if(param.startsWith('--network')) {
params.network = param.slice(param.indexOf('=') + 1);
}
return params;
}, {});
return params;
};

const params = getParsedParams();

const pegoutTracker = new PegoutTracker();

const { pegoutTxHash, network } = params;

pegoutTracker.on(PEGOUT_TRACKER_EVENTS.releaseRequestRejectedEventFound, bridgeTxDetails => {
console.info('Pegout stage 1 (pegout request rejected) transaction found: ');
console.info(util.inspect(bridgeTxDetails, {depth: null, colors: true}));
});

pegoutTracker.on(PEGOUT_TRACKER_EVENTS.releaseRequestReceivedEventFound, bridgeTxDetails => {
console.info('Pegout stage 1 (pegout request) transaction found: ');
console.info(util.inspect(bridgeTxDetails, {depth: null, colors: true}));
});

pegoutTracker.on(PEGOUT_TRACKER_EVENTS.releaseRequestedEventFound, bridgeTxDetails => {
console.info('Pegout stage 2 (pegout created) transaction found: ');
console.info(util.inspect(bridgeTxDetails, {depth: null, colors: true}));
});

pegoutTracker.on(PEGOUT_TRACKER_EVENTS.batchPegoutCreatedEventFound, bridgeTxDetails => {
console.info('Pegout stage 2 (batch pegout created) transaction found: ');
console.info(util.inspect(bridgeTxDetails, {depth: null, colors: true}));
});

pegoutTracker.on(PEGOUT_TRACKER_EVENTS.pegoutConfirmedEventFound, bridgeTxDetails => {
console.info('Pegout stage 3 (confirmations) transaction found: ');
console.info(util.inspect(bridgeTxDetails, {depth: null, colors: true}));
});

pegoutTracker.on(PEGOUT_TRACKER_EVENTS.addSignatureEventFound, bridgeTxDetails => {
console.info('Pegout stage 4 (signatures) transaction found: ');
console.info(util.inspect(bridgeTxDetails, {depth: null, colors: true}));
});

pegoutTracker.on(PEGOUT_TRACKER_EVENTS.releaseBtcEventFound, bridgeTxDetails => {
console.info('Pegout stage 4 (release) transaction found: ');
console.info(util.inspect(bridgeTxDetails, {depth: null, colors: true}));
});

pegoutTracker.on(PEGOUT_TRACKER_EVENTS.pegoutStagesFound, bridgeTxDetails => {
console.info('pegoutStagesFound: ')
console.info(util.inspect(bridgeTxDetails, {depth: null, colors: true}));
});

pegoutTracker.trackPegout(pegoutTxHash, network);
20 changes: 20 additions & 0 deletions tool/pegout-tracker/pegout-tracker-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const PEGOUT_TRACKER_EVENTS = {
releaseRequestRejectedEventFound: 'releaseRequestRejectedEventFound',
releaseRequestReceivedEventFound: 'releaseRequetReceivedEventFound',
releaseRequestedEventFound: 'releaseRequestedEventFound',
batchPegoutCreatedEventFound: 'batchPegoutCreatedEventFound',
pegoutConfirmedEventFound: 'pegoutConfirmedEventFound',
addSignatureEventFound: 'addSignatureEventFound',
releaseBtcEventFound: 'releaseBtcEventFound',
pegoutStagesFound: 'pegoutStagesFound',
};

const NETWORK_REQUIRED_CONFIRMATIONS = {
mainnet: 4000,
testnet: 10,
};

module.exports = {
PEGOUT_TRACKER_EVENTS,
NETWORK_REQUIRED_CONFIRMATIONS,
};
175 changes: 175 additions & 0 deletions tool/pegout-tracker/pegout-tracker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@

const Web3 = require('web3');
const BridgeTransactionParser = require('../../index');
const EventEmitter = require('node:events');
const LiveMonitor = require('../live-monitor/live-monitor');
const { MONITOR_EVENTS } = require('../live-monitor/live-monitor-utils');

const {
isPegoutRequestRejectedTx,
isPegoutRequestReceivedTx,
isPegoutCreatedTx,
isPegoutConfirmedTx,
isAddSignatureTx,
isReleaseBtcTx,
getBridgeStorageValueDecodedToNumber,
bridgeStateKeysToStorageIndexMap,
getBridgeStorageAtBlock,
} = require('../../utils');
const networkParser = require('../network-parser');

const {
PEGOUT_TRACKER_EVENTS,
NETWORK_REQUIRED_CONFIRMATIONS,
} = require('./pegout-tracker-utils');

const defaultLiveMonitorParamsValues = {
pegout: true,
network: 'https://public-node.rsk.co/',
checkEveryMilliseconds: 100,
retryOnError: true,
retryOnErrorAttempts: 3,
};

class PegoutTracker extends EventEmitter {

constructor() {
super();
this.started = false;
}

/**
*
* @param {string} pegoutTxHash
* @param {mainnet | testnet} network
*/
async trackPegout(pegoutTxHash, network = 'mainnet') {

if(!pegoutTxHash) {
throw new Error('pegoutTxHash is required');
}

if(this.started) {
console.warn(`Pegout tracker is already started. Stop before calling ${trackPegout.name} again. Ignoring...`);
return;
}

this.started = true;

pegoutTxHash = pegoutTxHash.toLowerCase();

const pegoutInfo = [];

const networkUrl = networkParser(network);

const rskClient = new Web3(networkUrl);

const bridgeTransactionParser = new BridgeTransactionParser(rskClient);

const rskTx = await bridgeTransactionParser.getBridgeTransactionByTxHash(pegoutTxHash);

if(isPegoutRequestRejectedTx(rskTx)) {
this.emit(PEGOUT_TRACKER_EVENTS.releaseRequestRejectedEventFound, rskTx);
return;
}

if(!isPegoutRequestReceivedTx(rskTx)) {
throw new Error(`Transaction ${pegoutTxHash} is not a release request received transaction`);
}

pegoutInfo.push(rskTx);

this.emit(PEGOUT_TRACKER_EVENTS.releaseRequestReceivedEventFound, rskTx);

const nextPegoutCreationHeightRlpEncoded = await getBridgeStorageAtBlock(rskClient, bridgeStateKeysToStorageIndexMap.nextPegoutHeight.bytes, rskTx.blockNumber);
const nextPegoutCreationHeight = getBridgeStorageValueDecodedToNumber(nextPegoutCreationHeightRlpEncoded);

const latestBlockNumber = await rskClient.eth.getBlockNumber();

if(nextPegoutCreationHeight > latestBlockNumber) {
throw new Error(`Next pegout creation height ${nextPegoutCreationHeight} is greater than latest block number ${latestBlockNumber}`);
}

const params = { ...defaultLiveMonitorParamsValues, network: networkUrl, fromBlock: nextPegoutCreationHeight };

let stage = 2;

const liveMonitor = new LiveMonitor(params);

let stage2TxHash;
let stage2BlockNumber;
let stage2BtcTxHash;

this.on('stop', () => {
liveMonitor.stop();
this.started = false;
});

liveMonitor.on(MONITOR_EVENTS.filterMatched, async bridgeTxDetails => {
switch(stage) {
case 2:
if(isPegoutCreatedTx(bridgeTxDetails)) {
const batchPegoutCreatedEventArguments = bridgeTxDetails.events[2].arguments;
if(batchPegoutCreatedEventArguments.releaseRskTxHashes.includes(pegoutTxHash.slice(2))) {
stage = 3;
pegoutInfo.push(bridgeTxDetails);
stage2TxHash = bridgeTxDetails.txHash;
stage2BlockNumber = bridgeTxDetails.blockNumber;
stage2BtcTxHash = batchPegoutCreatedEventArguments.btcTxHash;

const afterConfirmationBlock = stage2BlockNumber + NETWORK_REQUIRED_CONFIRMATIONS[network];

const latestBlockNumber = await rskClient.eth.getBlockNumber();

if(afterConfirmationBlock > latestBlockNumber) {
throw new Error(`Expected after confirmation height ${afterConfirmationBlock} is greater than latest block number ${latestBlockNumber}`);
}

const newParams = { ...params, fromBlock: stage2BlockNumber + NETWORK_REQUIRED_CONFIRMATIONS[network] };
liveMonitor.reset(newParams);
this.emit(PEGOUT_TRACKER_EVENTS.releaseRequestedEventFound, bridgeTxDetails);
}
}
break;
case 3:
if(isPegoutConfirmedTx(bridgeTxDetails)) {
const pegoutConfirmedEventArguments = bridgeTxDetails.events[1].arguments;
if(pegoutConfirmedEventArguments.btcTxHash === stage2BtcTxHash) {
stage = 4;
pegoutInfo.push(bridgeTxDetails);
this.emit(PEGOUT_TRACKER_EVENTS.pegoutConfirmedEventFound, bridgeTxDetails);
}
}
break;
case 4:
if(isAddSignatureTx(bridgeTxDetails)) {
const pegoutAddSignatureEventArguments = bridgeTxDetails.events[0].arguments;
if(pegoutAddSignatureEventArguments.releaseRskTxHash === stage2TxHash) {
pegoutInfo.push(bridgeTxDetails);
this.emit(PEGOUT_TRACKER_EVENTS.addSignatureEventFound, bridgeTxDetails);
}
} else if(isReleaseBtcTx(bridgeTxDetails)) {
const pegoutReleaseBtcEventArguments = bridgeTxDetails.events[1].arguments;
if(pegoutReleaseBtcEventArguments.releaseRskTxHash === stage2TxHash) {
pegoutInfo.push(bridgeTxDetails);
this.emit(PEGOUT_TRACKER_EVENTS.releaseBtcEventFound, bridgeTxDetails);
this.emit(PEGOUT_TRACKER_EVENTS.pegoutStagesFound, pegoutInfo);
this.started = false;
liveMonitor.stop();
}
}
break;
}
});

liveMonitor.start(params);

}

stop() {
this.emit('stop');
}

}

module.exports = PegoutTracker;
Loading

0 comments on commit ebfa8c7

Please sign in to comment.