-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add relay service listeners and handlers (#56)
* Add relay service listeners and handlers * Correct db value get function
- Loading branch information
Showing
16 changed files
with
3,046 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
address.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"address":"0xe5cBb4784e2541aF2deDAE945043e67C65C4F123", | ||
"bridgeHash": "BridgeScriptHash", | ||
"redis": { | ||
"host": "localhost", | ||
"port": 6379 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
Oops, something went wrong.