Skip to content

Commit

Permalink
Add relay service listeners and handlers (#56)
Browse files Browse the repository at this point in the history
* Add relay service listeners and handlers

* Correct db value get function
  • Loading branch information
mwaa authored Oct 29, 2020
1 parent a35b68f commit 2c005ac
Show file tree
Hide file tree
Showing 16 changed files with 3,046 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
node_modules/
packages/childchain/node_modules/
packages/parent-bridge/tests/target/*
lerna-debug.log*
npm-debug.log*
build
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Side-Chain Framework

A framework that easily allows launch of a sidechain on nervos network
3 changes: 3 additions & 0 deletions packages/common/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Common Functions

House all common helper functions used in the framework
1 change: 1 addition & 0 deletions packages/relayer/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
address.json
58 changes: 58 additions & 0 deletions packages/relayer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Bridge

Functionality to listen for events and relay them across chains

The flow is listen and relay.
- We make use of redis queues to do the relay across chains
- All relays are queued then processed by the handle function in either ckb.ts or evm.ts

### Lock Flow
- User deposits to bridge on nervos network
- Listen function on ckb.ts filters out the deposit transaction
- `_relayLock` function formats transaction and adds to queue
- Handle function on evm.ts reads the queue and retrieves transaction
- `_collectLock` function sends transaction to childchain with validator signature

### Unlock
- User sends amount to childchain bridge address
- Listen function on evm.ts retrieves events and we check for Burn event
- `_collectUnLock` function sends transaction to childchain with validator signature
- Listen function on evm.ts retrieves events and we check for BurnQuorom event
- `_relayUnLock` function adds event data to queue
- Handle function on ckb.ts reads the queue and retrieves transaction
- `_processUnLockRelay` creates a nervos transactions sends transaction to nervos network


## Launch Service
To start the service on console run `npm run start`

*First time run `npm run deploy` copy address values to config file then `npm run start`*

## Config

```json
{
"bridgeAddress":"0x49D7858Cb3b598d79de772B0821af8a67e424c0c",
"bridgeHash": "BridgeScriptHash",
"redis": {
"host": "localhost",
"port": 6379
}
}
```

We use a config.json file to store bridge address (evm bridge) and bridge hash (nervos bridge).
Config file will be built through start up script in future.

To get the bridge address run `npm run deploy`
Copy the address to the config file.

*TODO get bridge hash and save to config file*

## Redis v6.0.8
Environment should have redis running in the background on port 6379.

We use redis to track two things
- Keep count of last processed block
- QueueRunner that relays event data across the listeners
- We have two queues on ckb side using bridgeHash and evm side using bridge address
8 changes: 8 additions & 0 deletions packages/relayer/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"address":"0xe5cBb4784e2541aF2deDAE945043e67C65C4F123",
"bridgeHash": "BridgeScriptHash",
"redis": {
"host": "localhost",
"port": 6379
}
}
35 changes: 35 additions & 0 deletions packages/relayer/deploy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { web3 } from './src/utils/web3';
import * as fs from 'fs';
// TODO:: handle if not available
import BridgeContract from '../childchain/build/contracts/Bridge.json';
const Contract = require('web3-eth-contract');

// set provider for all later instances to use
Contract.setProvider('ws://localhost:8546')

const deployContract = async () => {
let accounts = await web3.eth.getAccounts();
let contractAddress = '';

const contract = new Contract(BridgeContract.abi);
contract.deploy({
data: BridgeContract.bytecode,
arguments: [accounts]
})
.send({
from: accounts[0],
gas: 1500000,
gasPrice: '30000000000000'
})
.then(function (newContractInstance: any) {
contractAddress = newContractInstance.options.address;
const jsonString = JSON.stringify({ address: contractAddress });
fs.writeFileSync('./address.json', jsonString);
process.exit();
});
}

deployContract();

// TODO:: can be the entry point to build all config values
// e.g fetching bridge hash. deploying new contract if new childchain etc
22 changes: 22 additions & 0 deletions packages/relayer/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "bridge",
"version": "1.0.0",
"description": "framework relayer",
"main": "index.js",
"author": "LeapDAO",
"scripts": {
"deploy": "npx ts-node deploy.ts",
"dummy:unlock": "npx ts-node src/utils/transaction.ts",
"start": "npx ts-node start.ts"
},
"devDependencies": {
"@tsconfig/recommended": "^1.0.1",
"ts-node": "^9.0.0",
"typescript": "^4.0.5"
},
"dependencies": {
"redis": "^3.0.2",
"rsmq-promise": "^1.0.4",
"web3": "^1.3.0"
}
}
63 changes: 63 additions & 0 deletions packages/relayer/src/ckb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { LockReceipt } from './utils/types';
import { web3 } from './utils/web3';
import Config from '../config.json';

class CKBRelay {

queueRunner: any;
validatorAddress: string;
bridgeAddress: string = Config.address;
bridgeHash: string = Config.bridgeHash;

constructor(queueRunner: any, validator: string) {
this.queueRunner = queueRunner;
this.validatorAddress = validator;
}

/**
* Unlock event transfers funds from bridge to user
*/
async _processUnLockRelay(message: any)
{
// Structure of message.message will be json string of UnlockReceipt

const result = true; // use helper functions call withdraw
if (result) {
await this.queueRunner.deleteMessage({ qname: this.bridgeHash, id: message.id });
}
}

async _relayLock(receipt: LockReceipt) {
return await this.queueRunner.sendMessage({
qname: this.bridgeAddress, // relay to EVM queue
message: JSON.stringify(receipt)
});
}

async listen() {
// Filter through indexer for deposits on the bridge
// build receipt object and relay
// TODO:: replace test data below with actual deposits
this._relayLock({
isLock: true,
user: this.validatorAddress,
txHash: "testTxHash",
amount: web3.utils.toHex('200000000000000000')
});
}

/**
* Process messages from queue
* At this stage queue will always contain unlock events from evm
*/
async handle() {
let message = await this.queueRunner.receiveMessage({ qname: this.bridgeHash });

while (Object.keys(message).length) {
await this._processUnLockRelay(message);
message = await this.queueRunner.receiveMessage({ qname: this.bridgeHash });
}
}
}

export default CKBRelay;
131 changes: 131 additions & 0 deletions packages/relayer/src/evm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { web3 } from './utils/web3';
import { LockReceipt } from './utils/types';
const Contract = require('web3-eth-contract');
Contract.setProvider('ws://localhost:8546');
import Config from '../config.json';

class EVMRelay {
db: any;
queueRunner: any;
contractInstance: any;
validatorAddress: string;
bridgeAddress: string = Config.address;
bridgeHash: string = Config.bridgeHash;

constructor(queueRunner: any, db: any, contract: any, validator: string) {
this.db = db;
this.queueRunner = queueRunner;
this.validatorAddress = validator;
this.contractInstance = contract;
}

async _getDBHeight(): Promise<number> {
const result = await this.db.get('evm_height');
return result !== null ? parseInt(result, 10) : 0;
}

async _getEvmHeight(): Promise<number> {
return (await web3.eth.getBlock("latest")).number;
}

async _getSignature(receipt: LockReceipt) {
let payload = web3.eth.abi.encodeParameters(
["bool", "address", "uint256", "bytes32"],
[receipt.isLock, receipt.user, receipt.amount, receipt.txHash]
);

let sig = await web3.eth.sign(web3.utils.keccak256(payload), this.validatorAddress);

var r = `0x${sig.slice(2, 64 + 2)}`;
var s = `0x${sig.slice(64 + 2, 128 + 2)}`;
var v = `0x${sig.slice(128 + 2, 130 + 2)}`;

return { v, r, s };
}

async _collectLock(receipt: LockReceipt) {
const signature = await this._getSignature(receipt);
return await this.contractInstance.methods
.collect(receipt, signature)
.call();
}

// TODO:: check on failure of collecting signatures
async _collectUnLock(receipt: LockReceipt) {
const signature = await this._getSignature(receipt);
this.contractInstance.methods
.collect(receipt, signature)
.call();
}

async _processEvents(localHeight: number, remoteHeight: number) {
await this.contractInstance.getPastEvents({
fromBlock: localHeight,
toBlock: remoteHeight
}, (error: any, event: any) => {
event.forEach((x: any) => {
if (x.event === 'Burn') {
this._collectUnLock({
isLock: false,
user: x.returnValues.sender,
amount: x.returnValues.value,
txHash: x.transactionHash
});
} else if (x.event === 'BurnQuorom') {
this._relayUnLock({
user: x.returnValues.from,
amount: x.returnValues.amount,
txHash: x.returnValues.txHash,
sigs: x.returnValues.from
});
}
});
});
}

async _processLockRelay(message: any) {
// Structure of message.message should always be json string of LockScript
const result = await this._collectLock(JSON.parse(message.message));
if (result) { // TODO:: check if successfull
await this.queueRunner.deleteMessage({ qname: this.bridgeAddress, id: message.id });
}
}

async _relayUnLock(evmEvent: any) {
return this.queueRunner.sendMessage({
qname: this.bridgeHash, // Should be ckb queue name bridge hash
message: JSON.stringify(evmEvent)
});
}

async listen() {
console.log("Listening on childchain...");
const localHeight = await this._getDBHeight();
const remoteHeight = await this._getEvmHeight();

console.log("Blocks to process are", (remoteHeight - localHeight));
if ((remoteHeight - localHeight) > 0) {
await this._processEvents(localHeight, remoteHeight);
await this.db.set('evm_height', remoteHeight);
}

setTimeout(async () => {
await this.listen();
}, 30000)// listen again after 30 seconds
}

/**
* Process messages from queue
* At this stage queue will always contain lock events from ckb
*/
async handle() {
let message = await this.queueRunner.receiveMessage({ qname: this.bridgeAddress });

while (Object.keys(message).length) {
await this._processLockRelay(message);
message = await this.queueRunner.receiveMessage({ qname: this.bridgeAddress });
}
}
}

export default EVMRelay;
21 changes: 21 additions & 0 deletions packages/relayer/src/utils/transaction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { web3, TWO_ETH } from './web3';
import Config from '../../config.json';

// TODO:: remove once tests are up won't be needed
const simulate = async () => {
let accounts = await web3.eth.getAccounts();

for (let x = 0; x < 3; x++) {
let tx = await web3.eth.sendTransaction({
from: accounts[0],
to: Config.address,
value: TWO_ETH,
gasPrice: '20000000000'
});
const txHash = tx.transactionHash;
console.log("We have transaction", txHash);
}
process.exit();
}

simulate();
15 changes: 15 additions & 0 deletions packages/relayer/src/utils/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Data type structures needed

export interface LockReceipt {
isLock: boolean,
user: string,
amount: string,
txHash: string
}

export interface UnlockReceipt {
user: string,
amount: string,
txHash: string,
sigs: string,
}
7 changes: 7 additions & 0 deletions packages/relayer/src/utils/web3.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Web3 from 'web3';

// TODO:: read websocket url from .env
export const web3 = new Web3('ws://localhost:8546');
export const gasPrice = '20000000000';
const { BN } = require("ethereumjs-util");
export const TWO_ETH = new BN('200000000000000000', 10);
Loading

0 comments on commit 2c005ac

Please sign in to comment.