IMplemented one side bridge, eth => bnc

This commit is contained in:
Kirill Fedoseev 2019-07-04 12:32:01 +03:00
parent 7defc1e814
commit 8b50b4dced
50 changed files with 19842 additions and 3294 deletions

4
.gitignore vendored
View File

@ -5,3 +5,7 @@ node_modules/
**/signature
**/params
src/tss/multi-party-ecdsa/
data/
demo/validator*/
demo/ganache_data/
src/deploy*/build/

6
.gitmodules vendored Normal file
View File

@ -0,0 +1,6 @@
[submodule "src/deploy/contracts/openzeppelin-solidity"]
path = src/deploy/contracts/openzeppelin-solidity
url = https://github.com/OpenZeppelin/openzeppelin-solidity.git
[submodule "src/deploy-test/contracts/openzeppelin-solidity"]
path = src/deploy-test/contracts/openzeppelin-solidity
url = https://github.com/OpenZeppelin/openzeppelin-solidity.git

15
demo/clean.sh Executable file
View File

@ -0,0 +1,15 @@
#!/bin/bash
set -e
cd $(dirname "$0")
for (( I = 1; I < 4; ++I )); do
DIRNAME="validator$I"
rm -r "$DIRNAME/db"
rm -r "$DIRNAME/queue"
rm -r "$DIRNAME/keys"
mkdir "$DIRNAME/db"
mkdir "$DIRNAME/queue"
mkdir "$DIRNAME/keys"
done

14
demo/full-demo.sh Executable file
View File

@ -0,0 +1,14 @@
#!/bin/bash
set -e
DCU_FLAGS="--build --force-recreate"
NAME="validator$N"
cd $(dirname "$0")
echo "Starting $NAME"
mkdir -p "$NAME"
cd "$NAME"
docker-compose -p "$NAME" -f ../../src/oracle/docker-compose.yml up ${DCU_FLAGS}

29
demo/start-environment.sh Executable file
View File

@ -0,0 +1,29 @@
#!/bin/bash
set -e
cd $(dirname "$0")
echo "Starting blockchain"
rm -r ./ganache_data
mkdir ganache_data
kill $(sudo lsof -t -i:7545)
ganache-cli --db ./ganache_data -p 7545 -m "shrug dwarf easily blade trigger lucky reopen cage lake scatter desk boat" -i 33 -q &
sleep 3
echo "Deploying erc20"
cd ../src/deploy-test
truffle deploy --network development --reset > /dev/null
echo "Deploying main part"
cd ../deploy
truffle deploy --network development --reset > /dev/null

784
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +0,0 @@
{
"name": "bridge",
"version": "0.0.1",
"dependencies": {
"dotenv": "8.0.0",
"truffle-hdwallet-provider": "1.0.10",
"web3": "1.0.0-beta.55"
}
}

7
src/deploy-test/.env Normal file
View File

@ -0,0 +1,7 @@
RPC_URL=https://sokol.poa.network
RPC_URL_DEV=http://127.0.0.1:7545
PRIVATE_KEY=e49fe947f224ae8e126c41b1be3e52be701509c2366e835ea8c08651f91030e0
PRIVATE_KEY_DEV=e2aeb24eaa63102d0c0821717c3b6384abdabd7af2ad4ec8e650dce300798b27
SHARED_DB_ADDRESS=0xED3B25004A77de5dE38850a7D148315537C15572

View File

@ -0,0 +1,24 @@
pragma solidity ^0.5.9;
contract Migrations {
address public owner;
uint public last_completed_migration;
modifier restricted() {
if (msg.sender == owner) _;
}
constructor() public {
owner = msg.sender;
}
function setCompleted(uint completed) restricted public {
last_completed_migration = completed;
}
function upgrade(address new_address) restricted public {
Migrations upgraded = Migrations(new_address);
upgraded.setCompleted(last_completed_migration);
}
}

@ -0,0 +1 @@
Subproject commit c9f328ef66251db7fac7c704dd6c5523fc53b0ab

View File

@ -0,0 +1,5 @@
const Migrations = artifacts.require('Migrations')
module.exports = deployer => {
deployer.deploy(Migrations)
}

View File

@ -0,0 +1,8 @@
const TokenContract = artifacts.require('ERC20Mintable')
module.exports = async (deployer, network, accounts) => {
await deployer.deploy(TokenContract)
const instance = await TokenContract.deployed()
await instance.mint(accounts[0], 1000000)
}

View File

@ -0,0 +1,29 @@
require('dotenv').config()
const PrivateKeyProvider = require('truffle-hdwallet-provider')
const { RPC_URL, PRIVATE_KEY, RPC_URL_DEV, PRIVATE_KEY_DEV } = process.env
module.exports = {
networks: {
development: {
provider: new PrivateKeyProvider(PRIVATE_KEY_DEV, RPC_URL_DEV),
network_id: '33'
},
staging: {
provider: new PrivateKeyProvider(PRIVATE_KEY, RPC_URL),
network_id: '77'
}
},
compilers: {
solc: {
version: '0.5.9',
settings: {
optimizer: {
enabled: true,
runs: 3
}
}
}
}
}

View File

@ -2,7 +2,16 @@ RPC_URL=https://sokol.poa.network
RPC_URL_DEV=http://127.0.0.1:7545
PRIVATE_KEY=e49fe947f224ae8e126c41b1be3e52be701509c2366e835ea8c08651f91030e0
PRIVATE_KEY_DEV=dc8f77f02a4aba01c3ff0aa22ff13b7c9fd47da936db8d7f0a69abd3198a8c7e
PRIVATE_KEY_DEV=e2aeb24eaa63102d0c0821717c3b6384abdabd7af2ad4ec8e650dce300798b27
THRESHOLD=2
PARTIES=6
TOKEN_ADDRESS=0x44c158FE850821ae69DaF37AADF5c539e9d0025B
VALIDATOR_ADDRESS_1=0x99Eb3D86663c6Db090eFFdBC20510Ca9f836DCE3
VALIDATOR_ADDRESS_2=0xAa006899B0EC407De930bA8A166DEfe59bBfd3DC
VALIDATOR_ADDRESS_3=0x6352e3e6038e05b9da00C84AE851308f9774F883
VALIDATOR_ADDRESS_4=0x4dB6b4bD0a3fdc03B027A60f1c48f05C572312aa
VALIDATOR_ADDRESS_5=0xf7Ca4aED1795E424433498CEf43f6a3825C88731
VALIDATOR_ADDRESS_6=0xAd6c8127143032D843A260c5D379D8d9b3D51F15
THRESHOLD=1
PARTIES=3

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,20 @@
pragma solidity ^0.5.0;
import './openzeppelin-solidity/contracts/token/ERC20/IERC20.sol';
contract Bridge {
IERC20 public tokenContract;
event ReceivedTokens(address from, string recipient, uint value);
constructor(address _tokenContract) public {
tokenContract = IERC20(_tokenContract);
}
function requestAffirmation(uint value, string memory recipient) public {
tokenContract.transfer(address(this), value);
emit ReceivedTokens(msg.sender, recipient, value);
}
}

View File

@ -1,47 +1,143 @@
pragma solidity ^0.5.0;
import './openzeppelin-solidity/contracts/token/ERC20/IERC20.sol';
contract SharedDB {
uint32 threshold;
uint32 parties;
uint32 signupKeygenID;
uint32 signupKeygenCurrent;
uint32 signupSignID;
uint32 signupSignCurrent;
mapping(bytes32 => string) public db;
event SignupKeygen(address indexed from, uint32 uuid, uint32 number);
event SignupSign(address indexed from, uint32 uuid, uint32 number);
constructor(uint32 _threshold, uint32 _parties) public {
threshold = _threshold;
parties = _parties;
signupKeygenID = 1;
signupSignID = 0x80000000;
struct Validator {
address addr;
uint partyId;
bytes32 next;
}
function set(bytes32 key, string memory value) public {
db[key] = value;
event NewEpoch(uint indexed epoch);
event KeygenCompleted(uint indexed epoch, uint x, uint y);
event Signup(address indexed from, bytes32 indexed hash, uint epoch, uint partyId);
Validator validator;
mapping(bytes32 => Validator) public dbValidator;
mapping(bytes32 => bytes) public dbKeygen;
mapping(bytes32 => uint) public confirmationsCount;
mapping(bytes32 => bytes) public dbSign;
mapping(bytes32 => uint) public signupsCount;
mapping(bytes32 => bool) public confirmations;
mapping(bytes32 => uint) public dbSignups;
uint public x;
uint public y;
bool public ready;
mapping(uint => uint) public threshold;
mapping(uint => uint) public parties;
uint public epoch;
constructor(uint32 _threshold, uint32 _parties, address[] memory validators, address _tokenContract) public {
require(_parties > 0);
require(_threshold < _parties);
require(validators.length == _parties);
tokenContract = IERC20(_tokenContract);
epoch = 1;
ready = false;
threshold[epoch] = _threshold;
parties[epoch] = _parties;
// First validator
validator = Validator(validators[0], 1, 0);
setValidator(validators[0], validator);
// Other validators
for (uint i = 1; i < _parties; i++) {
setValidator(validators[i], Validator(validators[i], i + 1, 0));
// Link to prev one
Validator storage v = getValidator(validators[i - 1]);
v.next = keccak256(abi.encodePacked(epoch, validators[i]));
}
emit NewEpoch(epoch);
}
function signupKeygen() public {
if (signupKeygenCurrent < parties) {
signupKeygenCurrent++;
}
else {
signupKeygenID++;
signupKeygenCurrent = 1;
}
emit SignupKeygen(msg.sender, signupKeygenID, signupKeygenCurrent);
IERC20 public tokenContract;
event ReceivedTokens(address from, string recipient, uint value);
function requestAffirmation(uint value, string memory recipient) public {
tokenContract.transferFrom(msg.sender, address(this), value);
emit ReceivedTokens(msg.sender, recipient, value);
}
function signupSign() public {
if (signupSignCurrent < threshold + 1) {
signupSignCurrent++;
function confirm(uint _x, uint _y) public {
Validator storage v = getValidator(msg.sender);
require(v.partyId != 0);
require(!confirmations[keccak256(abi.encodePacked(epoch, v.partyId, _x, _y))]);
confirmations[keccak256(abi.encodePacked(epoch, v.partyId, _x, _y))] = true;
if (++confirmationsCount[keccak256(abi.encodePacked(epoch, _x, _y))] == parties[epoch]) {
x = _x;
y = _y;
ready = true;
emit KeygenCompleted(epoch, x, y);
}
else {
signupSignID++;
signupSignCurrent = 1;
}
emit SignupSign(msg.sender, signupSignID, signupSignCurrent);
}
function setKeygenData(bytes32 key, bytes memory data) public {
Validator storage v = getValidator(msg.sender);
require(v.partyId != 0);
require(!ready);
dbKeygen[keccak256(abi.encodePacked(epoch, key, v.partyId))] = data;
}
function getKeygenData(uint fromPartyId, bytes32 key) view public returns (bytes memory) {
return dbKeygen[keccak256(abi.encodePacked(epoch, key, fromPartyId))];
}
function signupSign(bytes32 hash) public {
signupSign(hash, epoch);
}
function signupSign(bytes32 hash, uint _epoch) public {
Validator storage v = getValidator(msg.sender, _epoch);
require(v.partyId != 0);
require(ready);
require(signupsCount[keccak256(abi.encodePacked(_epoch, hash))] <= threshold[_epoch], "Already enough signers");
//require(confirmationsCount[keccak256(abi.encodePacked(_epoch, x, y))] == parties[_epoch]); == ready
dbSignups[keccak256(abi.encodePacked(_epoch, hash, v.partyId))] = ++signupsCount[keccak256(abi.encodePacked(_epoch, hash))];
emit Signup(msg.sender, hash, _epoch, signupsCount[keccak256(abi.encodePacked(_epoch, hash))]);
}
function setSignData(bytes32 hash, bytes32 key, bytes memory data) public {
Validator storage v = getValidator(msg.sender);
require(v.partyId != 0);
require(ready);
uint signupId = dbSignups[keccak256(abi.encodePacked(epoch, hash, v.partyId))];
require(signupId != 0);
dbSign[keccak256(abi.encodePacked(epoch, hash, signupId, key))] = data;
}
function getSignData(uint signupId, bytes32 hash, bytes32 key) view public returns (bytes memory) {
//uint id = dbSignups[keccak256(abi.encodePacked(epoch, hash, fromPartyId))];
return dbSign[keccak256(abi.encodePacked(epoch, hash, signupId, key))];
}
function setValidator(address a, Validator memory v) private {
dbValidator[keccak256(abi.encodePacked(epoch, a))] = v;
}
function getValidator(address a) view private returns (Validator storage) {
return getValidator(a, epoch);
}
function getValidator(address a, uint kv) view private returns (Validator storage) {
return dbValidator[keccak256(abi.encodePacked(kv, a))];
}
function getPartyId() view public returns (uint) {
return getValidator(msg.sender).partyId;
}
}

@ -0,0 +1 @@
Subproject commit c9f328ef66251db7fac7c704dd6c5523fc53b0ab

View File

@ -1,9 +1,25 @@
require('dotenv').config()
const Migrations = artifacts.require('SharedDB')
const SharedDB = artifacts.require('SharedDB')
const { THRESHOLD, PARTIES } = process.env
const {
THRESHOLD, PARTIES, VALIDATOR_ADDRESS_1, VALIDATOR_ADDRESS_2, VALIDATOR_ADDRESS_3, VALIDATOR_ADDRESS_4,
VALIDATOR_ADDRESS_5, VALIDATOR_ADDRESS_6, TOKEN_ADDRESS
} = process.env
module.exports = deployer => {
deployer.deploy(Migrations, THRESHOLD, PARTIES)
deployer.deploy(
SharedDB,
THRESHOLD,
PARTIES,
[
VALIDATOR_ADDRESS_1,
VALIDATOR_ADDRESS_2,
VALIDATOR_ADDRESS_3,
//VALIDATOR_ADDRESS_4,
//VALIDATOR_ADDRESS_5,
//VALIDATOR_ADDRESS_6
],
TOKEN_ADDRESS
)
}

View File

@ -1,9 +0,0 @@
const Web3 = require('web3')
const web3 = new Web3('https://sokol.poa.network')
async function main () {
console.log(await web3.eth.getBalance('0x48138BEC745673Fe2CE28C62c9944Ab0Fa56b495'))
}
main()

View File

@ -1,14 +1,25 @@
THRESHOLD=2
PARTIES=6
THRESHOLD=1
PARTIES=3
KEY_FILE=keys.store
#RPC_URL=https://sokol.poa.network
RPC_URL=http://host.docker.internal:7545
RPC_URL_DEV=http://127.0.0.1:7545
SHARED_DB_ADDRESS=0x213b9cF674ef08E1E30A55f679Dfa55a16494975
SHARED_DB_ADDRESS=0x94b40CC641Ed7db241A1f04C8896ba6f6cC36b85
TOKEN_ADDRESS=0x44c158FE850821ae69DaF37AADF5c539e9d0025B
#VALIDATOR_PRIVATE_KEY=d1e7b8ff274e517e1a332f2bc0ac051e30db196ba31c68c2efcd022e8ec358f1
VALIDATOR_PRIVATE_KEY=72c16a6812258f999190ccb11ff8bfc1e9f07438c8411f8840dbbe18137785e2
VALIDATOR_PRIVATE_KEY=2be3f252e16541bf1bb2d4a517d2bf173e6d09f2d765d32c64dc50515aec63ea
#SSM_URL=http://127.0.0.1:8001
LOCAL=true
#MESSAGE=fb2487601395e1cabad725e2e006ae16dd134a536ea1e30919b86e7aa572584d
DEPLOY_PRIVATE_KEY=e2aeb24eaa63102d0c0821717c3b6384abdabd7af2ad4ec8e650dce300798b27
FOREIGN_URL=https://testnet-dex.binance.org/
FOREIGN_CHAIN_ID=Binance-Chain-Nile

View File

@ -1,135 +0,0 @@
version: '3.7'
services:
proxy1:
image: blockchain-proxy
build: ./proxy
environment:
- RPC_URL
- SHARED_DB_ADDRESS
- VALIDATOR_PRIVATE_KEY=e2c1349f1d13f22cd011a9cfef7007388b8df16a6e1f318517cb93dbadcd9c2e
volumes:
- '../deploy/build/contracts:/proxy/contracts_data'
networks:
- keygen-proxy-net1
proxy2:
image: blockchain-proxy
build: ./proxy
environment:
- RPC_URL
- SHARED_DB_ADDRESS
- VALIDATOR_PRIVATE_KEY=7562a3ea655c2aad92a819069d334e7cf6edc9d2ae0fcc9a6498bd38842fafd4
volumes:
- '../deploy/build/contracts:/proxy/contracts_data'
networks:
- keygen-proxy-net2
proxy3:
image: blockchain-proxy
build: ./proxy
environment:
- RPC_URL
- SHARED_DB_ADDRESS
- VALIDATOR_PRIVATE_KEY=955b1f58a207160459e3be344e8a9cae2c3db47a0cb8275eb771a51da84de244
volumes:
- '../deploy/build/contracts:/proxy/contracts_data'
networks:
- keygen-proxy-net3
proxy4:
image: blockchain-proxy
build: ./proxy
environment:
- RPC_URL
- SHARED_DB_ADDRESS
- VALIDATOR_PRIVATE_KEY=a1c08300192fdd27aa92cd836ca98601c35184319c6873b1d1478f21cdb1c867
volumes:
- '../deploy/build/contracts:/proxy/contracts_data'
networks:
- keygen-proxy-net4
proxy5:
image: blockchain-proxy
build: ./proxy
environment:
- RPC_URL
- SHARED_DB_ADDRESS
- VALIDATOR_PRIVATE_KEY=3aef900c61246653308ed0770f3bd67a1c21342cf2a7dbf0ee5bcc787b627de0
volumes:
- '../deploy/build/contracts:/proxy/contracts_data'
networks:
- keygen-proxy-net5
proxy6:
image: blockchain-proxy
build: ./proxy
environment:
- RPC_URL
- SHARED_DB_ADDRESS
- VALIDATOR_PRIVATE_KEY=abe7f999209549463e7da719eddd6bb3e00fe77832f5166c1e57d067452954b6
volumes:
- '../deploy/build/contracts:/proxy/contracts_data'
networks:
- keygen-proxy-net6
keygen1:
image: keygen-client
build: ./tss-keygen
command: 'http://proxy1:8001'
volumes:
- './keys1.store:/tss/keys.store'
- './params:/tss/params'
networks:
- keygen-proxy-net1
keygen2:
image: keygen-client
build: ./tss-keygen
command: 'http://proxy2:8001'
volumes:
- './keys2.store:/tss/keys.store'
- './params:/tss/params'
networks:
- keygen-proxy-net2
keygen3:
image: keygen-client
build: ./tss-keygen
command: 'http://proxy3:8001'
volumes:
- './keys3.store:/tss/keys.store'
- './params:/tss/params'
networks:
- keygen-proxy-net3
keygen4:
image: keygen-client
build: ./tss-keygen
command: 'http://proxy4:8001'
environment:
- SKIP_SIGN=true
volumes:
- './keys4.store:/tss/keys.store'
- './params:/tss/params'
networks:
- keygen-proxy-net4
keygen5:
image: keygen-client
build: ./tss-keygen
command: 'http://proxy5:8001'
environment:
- SKIP_SIGN=true
volumes:
- './keys5.store:/tss/keys.store'
- './params:/tss/params'
networks:
- keygen-proxy-net5
keygen6:
image: keygen-client
build: ./tss-keygen
command: 'http://proxy6:8001'
environment:
- SKIP_SIGN=true
volumes:
- './keys6.store:/tss/keys.store'
- './params:/tss/params'
networks:
- keygen-proxy-net6
networks:
keygen-proxy-net1:
keygen-proxy-net2:
keygen-proxy-net3:
keygen-proxy-net4:
keygen-proxy-net5:
keygen-proxy-net6:

View File

@ -1,24 +0,0 @@
version: '3.7'
services:
proxy:
image: blockchain-proxy
build: ./proxy
environment:
- RPC_URL
- SHARED_DB_ADDRESS
- VALIDATOR_PRIVATE_KEY
volumes:
- '../deploy/build/contracts:/proxy/contracts_data'
networks:
- keygen-proxy-net
keygen:
image: keygen-client
build: ./tss-keygen
command: 'http://proxy:8001'
volumes:
- './${KEY_FILE}:/tss/keys.store'
- './params:/tss/params'
networks:
- keygen-proxy-net
networks:
keygen-proxy-net:

View File

@ -1,7 +1,78 @@
version: '3.7'
services:
sign-client:
proxy:
image: blockchain-proxy
build: ./proxy
environment:
- RPC_URL
- SHARED_DB_ADDRESS
- VALIDATOR_PRIVATE_KEY
volumes:
- '../deploy/build/contracts:/proxy/contracts_data'
networks:
- sign-proxy-net
- keygen-proxy-net
keygen:
image: keygen-client
build: ./tss-keygen
environment:
- 'RABBITMQ_URL=amqp://rabbitmq:5672'
- 'PROXY_URL=http://proxy:8001'
volumes:
- '${PWD}/keys:/keys'
networks:
- keygen-proxy-net
- rabbit-keygen-net
- redis-keygen-net
signer:
image: sign-client
build:
context: .
dockerfile:
build: ./tss-sign
environment:
- 'RABBITMQ_URL=amqp://rabbitmq:5672'
- 'PROXY_URL=http://proxy:8001'
- FOREIGN_CHAIN_ID
- FOREIGN_URL
volumes:
- '${PWD}/keys:/keys'
networks:
- sign-proxy-net
- rabbit-signer-net
- redis-signer-net
redis:
image: redis:5.0.5-alpine
volumes:
- '${PWD}/db:/data'
networks:
- redis-signer-net
- redis-keygen-net
- redis-watcher-net
rabbitmq:
hostname: rabbit
image: rabbitmq:3.7.15-alpine
volumes:
- '${PWD}/queue:/var/lib/rabbitmq/mnesia'
networks:
- rabbit-signer-net
- rabbit-keygen-net
- rabbit-watcher-net
eth-watcher:
build: ./watcher
image: eth-watcher
environment:
- 'HOME_RPC_URL=${RPC_URL}'
- 'HOME_BRIDGE_ADDRESS=${SHARED_DB_ADDRESS}'
- 'RABBITMQ_URL=amqp://rabbitmq:5672'
volumes:
- '../deploy/build/contracts:/watcher/contracts_data'
networks:
- rabbit-watcher-net
- redis-watcher-net
networks:
sign-proxy-net:
keygen-proxy-net:
rabbit-signer-net:
rabbit-keygen-net:
rabbit-watcher-net:
redis-keygen-net:
redis-signer-net:
redis-watcher-net:

View File

@ -1,32 +0,0 @@
#!/bin/bash
set -e
cd $(dirname "$0")
set -o allexport
source ./.env
set +o allexport
echo Writing params
echo \{\"parties\":\""$PARTIES"\",\"threshold\":\""$THRESHOLD"\"\} > ./params
if [[ -z "$LOCAL" ]]; then
echo Building tss source from git
docker build -t tss ../tss > /dev/null
else
echo Building tss local source
docker build -t tss -f ../tss/Dockerfile-local ../tss > /dev/null
fi
echo Building tss keygen client
docker build -t tss-keygen-client ./tss-keygen > /dev/null
for (( i = 1 ; i <= $PARTIES ; i++ )) do
touch keys"$i".store
done
docker-compose -f ./docker-compose-keygen-all.yml kill $(eval echo proxy{1..${PARTIES}})
echo Running keygen
docker-compose -f ./docker-compose-keygen-all.yml up $@

View File

@ -1,30 +0,0 @@
#!/bin/bash
set -e
cd $(dirname "$0")
set -o allexport
source ./.env
set +o allexport
echo Writing params
echo \{\"parties\":\""$PARTIES"\",\"threshold\":\""$THRESHOLD"\"\} > ./params
if [[ -z "$LOCAL" ]]; then
echo Building tss source from git
docker build -t tss ../tss > /dev/null
else
echo Building tss local source
docker build -t tss -f ../tss/Dockerfile-local ../tss > /dev/null
fi
echo Building tss keygen client
docker build -t tss-keygen-client ./tss-keygen > /dev/null
touch "$KEY_FILE"
docker-compose -f ./docker-compose-keygen.yml kill proxy
echo Running keygen
docker-compose -f ./docker-compose-keygen.yml up $@

View File

@ -1,5 +1,8 @@
const express = require('express')
const Web3 = require('web3')
const fs = require('fs')
const BN = require('bignumber.js')
const ethers = require('ethers')
const { RPC_URL, SHARED_DB_ADDRESS, VALIDATOR_PRIVATE_KEY } = process.env
const abi = require('./contracts_data/SharedDB.json').abi
@ -15,14 +18,22 @@ app.use(express.json())
app.use(express.urlencoded({ extended: true }))
app.post('/get', get)
app.post('/set', set)
app.post('/signupkeygen', signupKeygen)
app.post('/signupsign', signupSign)
app.get('/params', params)
app.post('/confirm', confirm)
async function main () {
validatorNonce = await web3.eth.getTransactionCount(validatorAddress)
try {
fs.mkdirSync('/generated_data')
} catch (e) {
}
app.listen(8001, () => {
console.log('Listening on port 8001')
})
@ -32,26 +43,41 @@ function Ok (data) {
return { Ok: data }
}
function hash (key) {
return web3.utils.sha3(JSON.stringify(key))
function Err (data) {
return { Err: data }
}
async function get (req, res) {
console.log('Get call')
while (true) {
const result = await contract.methods.db(hash(req.body.key)).call()
if (result !== '') {
res.send(Ok({ key: req.body.key, value: result }))
break
}
const uuid = req.body.key.third
const from = parseInt(req.body.key.first)
const to = Number(req.body.key.fourth)
const key = web3.utils.sha3(`${req.body.key.second}_${to}`)
const data = await (uuid.startsWith('k')
? contract.methods.getKeygenData(from, key).call()
: contract.methods.getSignData(from, uuid, key).call())
const result = web3.utils.hexToUtf8(data)
if (result.length)
res.send(Ok({ key: req.body.key, value: result }))
else {
setTimeout(() => res.send(Err(null)), 1000)
}
console.log('Get end')
}
async function set (req, res) {
console.log('Set call')
const query = contract.methods.set(hash(req.body.key), req.body.value)
const uuid = req.body.key.third
const to = Number(req.body.key.fourth)
const key = web3.utils.sha3(`${req.body.key.second}_${to}`)
const query = uuid.startsWith('k') ? contract.methods.setKeygenData(key, web3.utils.utf8ToHex(req.body.value))
: contract.methods.setSignData(uuid, key, web3.utils.utf8ToHex(req.body.value))
await sendQuery(query)
fs.writeFileSync(`/generated_data/${req.body.key.first}_${req.body.key.second}_${req.body.key.third}_${req.body.key.fourth}.json`, req.body.value)
res.send(Ok(null))
console.log('Set end')
@ -59,44 +85,54 @@ async function set (req, res) {
async function signupKeygen (req, res) {
console.log('SignupKeygen call')
const query = contract.methods.signupKeygen()
const receipt = await sendQuery(query)
const epoch = (await contract.methods.epoch().call()).toNumber()
const partyId = (await contract.methods.getPartyId().call({ from: validatorAddress })).toNumber()
while (true) {
const events = await contract.getPastEvents('SignupKeygen', {
filter: { from: validatorAddress },
fromBlock: receipt.blockNumber,
toBlock: receipt.blockNumber
})
const event = events[0]
if (event) {
res.send(Ok({ uuid: event.returnValues.uuid.toString(), number: event.returnValues.number }))
break
}
}
res.send(Ok({ uuid: `k${epoch}`, number: partyId }))
console.log('SignupKeygen end')
}
async function signupSign (req, res) {
console.log('SignupSign call')
const query = contract.methods.signupSign()
console.log(req.body.third)
const hash = web3.utils.sha3(`0x${req.body.third}`)
const query = contract.methods.signupSign(hash)
const receipt = await sendQuery(query)
while (true) {
const events = await contract.getPastEvents('SignupSign', {
filter: { from: validatorAddress },
const events = await contract.getPastEvents('Signup', {
filter: { from: validatorAddress, hash },
fromBlock: receipt.blockNumber,
toBlock: receipt.blockNumber
})
const event = events[0]
if (event) {
res.send(Ok({ uuid: event.returnValues.uuid.toString(), number: event.returnValues.number }))
res.send(Ok({ uuid: hash, number: event.returnValues.partyId.toNumber() }))
break
}
}
console.log('SignupSign call')
console.log('SignupSign end')
}
async function confirm (req, res) {
console.log('Confirm call')
const { x, y } = req.body[5]
const query = contract.methods.confirm(`0x${x}`, `0x${y}`)
await sendQuery(query)
//const addr = `0x${web3.utils.sha3(`0x${x}${y}`).substring(26)}`
//console.log(addr)
res.send()
console.log('Confirm end')
}
async function params (req, res) {
console.log('Params call')
const epoch = parseInt(req.query.epoch)
const parties = (await contract.methods.parties(epoch).call()).toNumber().toString()
const threshold = (await contract.methods.threshold(epoch).call()).toNumber().toString()
res.send({ parties, threshold })
console.log('Params end')
}
async function sendQuery (query) {
@ -108,7 +144,7 @@ async function sendQuery (query) {
nonce: validatorNonce++,
chainId: 33
}
tx.gas = await query.estimateGas(tx)
tx.gas = Math.min(Math.ceil(await query.estimateGas(tx) * 1.5), 6721975)
const signedTx = await web3.eth.accounts.signTransaction(tx, VALIDATOR_PRIVATE_KEY)
const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction)

View File

@ -4,6 +4,8 @@
"dependencies": {
"web3": "1.0.0-beta.55",
"dotenv": "8.0.0",
"express": "4.17.1"
"express": "4.17.1",
"bignumber.js": "9.0.0",
"ethers": "4.0.31"
}
}

42
src/oracle/testApprove.js Normal file
View File

@ -0,0 +1,42 @@
require('dotenv').config()
const Web3 = require('web3')
const { RPC_URL_DEV, SHARED_DB_ADDRESS, DEPLOY_PRIVATE_KEY, TOKEN_ADDRESS } = process.env
const web3 = new Web3(RPC_URL_DEV, null, { transactionConfirmationBlocks: 1 })
const abiBridge = require('../deploy/build/contracts/SharedDB').abi
const abiToken = require('../deploy/build/contracts/IERC20').abi
const bridge = new web3.eth.Contract(abiBridge, SHARED_DB_ADDRESS)
const token = new web3.eth.Contract(abiToken, TOKEN_ADDRESS)
const query1 = token.methods.approve(SHARED_DB_ADDRESS, 1)
const query2 = bridge.methods.requestAffirmation(1, 'tbnb1h3nmmqukrtjc0prmtdts0kxlgmw8rend4zfasn')
let nonce
const deployAddress = web3.eth.accounts.privateKeyToAccount(`0x${DEPLOY_PRIVATE_KEY}`).address
async function main () {
console.log(deployAddress)
nonce = await web3.eth.getTransactionCount(deployAddress)
await sendQuery(query1, TOKEN_ADDRESS)
await sendQuery(query2, SHARED_DB_ADDRESS)
}
async function sendQuery (query, to) {
const encodedABI = query.encodeABI()
const tx = {
data: encodedABI,
from: deployAddress,
to,
nonce: nonce++,
chainId: 33
}
tx.gas = Math.min(Math.ceil(await query.estimateGas(tx) * 1.5), 6721975)
const signedTx = await web3.eth.accounts.signTransaction(tx, DEPLOY_PRIVATE_KEY)
const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction)
return receipt
}
main()

View File

@ -0,0 +1,55 @@
const Bnc = require('@binance-chain/javascript-sdk')
const axios = require('axios')
const Transaction = require('../oracle/tss-sign/tx')
const crypto = require('crypto')
const ecc = require('tiny-secp256k1')
const privKey = 'b92a59209e28149e5cee8e54dfceb80a08ea08e654261bdb9d264b15dee2525c'
const asset = 'BNB'
const amount = 2.5
const addressTo = process.argv[2]
const addressFrom = Bnc.crypto.getAddressFromPrivateKey(privKey)
const message = 'A note to you'
const api = 'https://testnet-dex.binance.org/'
const httpClient = axios.create({ baseURL: api })
httpClient
.get(`/api/v1/account/${addressFrom}`)
.then((res) => {
const { sequence } = res.data
const tx = new Transaction('tbnb1h3nmmqukrtjc0prmtdts0kxlgmw8rend4zfasn', 674629, sequence, process.argv[2], amount, 'BNB', 'test')
const hash = crypto.createHash('sha256').update(tx.getSignBytes()).digest('hex')
console.log(tx.getSignBytes().toString('hex'))
console.log(hash)
const signature = ecc.sign(Buffer.from(hash, 'hex'), Buffer.from('b92a59209e28149e5cee8e54dfceb80a08ea08e654261bdb9d264b15dee2525c', 'hex'))
const sig = {
r: signature.toString('hex').substr(0, 64),
s: signature.toString('hex').substr(64, 64)
}
console.log(sig)
const publicKey = {
x: 'b32b5ea8698156239ea7092ef8a44a4b711ea29525da34a8233bdc0dd3af7f1a',
y: '6b5b77f2e925f93cae7fc894ff50bafcb7b6e6e96e339c96e41663ccaf0a4d68'
}
return tx.addSignature(publicKey, sig)
})
.then(signed => {
console.log('sending')
return httpClient.post(`/api/v1/broadcast?sync=true`, signed, {
headers: {
'content-type': 'text/plain'
}
})
})
.then((result) => {
if (result.status === 200) {
console.log('success', result.data)
} else {
console.error('error', result)
}
})
.catch((error) => {
console.error('error', error)
})

View File

@ -1,12 +0,0 @@
FROM ubuntu:19.10
WORKDIR /tss
RUN apt-get update && \
apt-get install -y libssl1.1 libssl-dev curl
COPY params /tss/
COPY --from=tss /tss/target/release/gg18_sign_client /tss/
ENTRYPOINT ["./gg18_sign_client"]

View File

@ -1,13 +0,0 @@
{
"redis": {
"port": 6379,
"host": "127.0.0.1",
"family": 4,
"password": "auth",
"db": 0
},
"eth": {
"rpcUrl": "https://sokol.poa.network",
"pollingInterval": 5000
}
}

View File

@ -1,22 +0,0 @@
import Web3 from 'web3'
import { eth } from 'config'
import {get, set} from 'db'
const {rpcUrl, pollingInterval} = eth
const web3 = new Web3(rpcUrl)
async function main () {
const lastProcessedBlock = await get('lastProcessedBlock')
try {
const block = await web3.eth.getBlock(lastProcessedBlock + 1)
block.transactions.forEach(transaction => {
})
} catch (e) {
}
}
setInterval(main, pollingInterval)

View File

@ -1,32 +0,0 @@
#!/bin/bash
set -e
cd $(dirname "$0")
if [[ -z "$SKIP_ENV" ]]; then
set -o allexport
source ../.env
set +o allexport
fi
echo Writing params
echo \{\"parties\":\""$PARTIES"\",\"threshold\":\""$THRESHOLD"\"\} > ./params
if [[ -z "$LOCAL" ]]; then
echo Building tss source from git
docker build -t tss ../../tss > /dev/null
else
echo Building tss local source
docker build -t tss -f ../../tss/Dockerfile-local ../../tss > /dev/null
fi
echo Building tss sign client
docker build -t tss-sign-client . > /dev/null
touch signature
echo Signing message using ssm server at "$SSM_URL"
docker run --rm -v "$(cd ..; pwd)/$KEY_FILE:/tss/keys.store" -v "$(pwd)/signature:/tss/signature" --network host tss-sign-client "$SSM_URL" keys.store "$1"
echo Signed message

View File

@ -1,12 +1,18 @@
FROM ubuntu:19.10
FROM node:10.16.0-slim
WORKDIR /tss
RUN apt-get update && \
apt-get install -y libssl1.1 libssl-dev curl
COPY keygen-entrypoint.sh /tss/
COPY package.json /tss/
COPY --from=tss /tss/target/release/gg18_keygen_client /tss/target/release/gg18_sign_client /tss/
RUN npm install
ENTRYPOINT ["./keygen-entrypoint.sh"]
COPY keygen-entrypoint.sh keygen.js /tss/
COPY --from=tss /tss/target/release/gg18_keygen_client /tss/
RUN mkdir /keys
ENTRYPOINT ["node", "keygen.js"]

View File

@ -8,20 +8,16 @@ until curl "$1" > /dev/null 2>&1; do
sleep 1;
done
#curl "$1"
echo "Fetching current tss params"
curl -X GET "$1/params?epoch=$3" -o ./params > /dev/null 2>&1
echo "Generating key using server $1"
./gg18_keygen_client "$1" keys.store
./gg18_keygen_client "$1" "$2"
echo "Generated keys for all parties"
echo "Signing message"
echo "Sending confirmation"
if [[ -z "$SKIP_SIGN" ]]; then
./gg18_sign_client "$1" keys.store some_message
echo "Signed message"
fi
curl -X POST -H "Content-Type: application/json" -d @"$2" "$1/confirm" > /dev/null 2>&1

View File

@ -0,0 +1,52 @@
const exec = require('child_process')
const fs = require('fs')
const crypto = require('crypto')
const bech32 = require('bech32')
const amqp = require('amqplib')
const { RABBITMQ_URL, PROXY_URL } = process.env
async function main () {
console.log('Connecting to RabbitMQ server')
const connection = await connectRabbit(RABBITMQ_URL)
console.log('Connecting to epoch events queue')
const channel = await connection.createConfirmChannel()
const queue = await channel.assertQueue('epochQueue')
channel.prefetch(1)
channel.consume(queue.queue, msg => {
const data = JSON.parse(msg.content)
console.log(`Consumed new epoch event, starting keygen for epoch ${data.epoch}`)
const keysFile = `/keys/keys${data.epoch}.store`
console.log('Running ./keygen-entrypoint.sh')
const cmd = exec.execFile('./keygen-entrypoint.sh', [PROXY_URL, keysFile, data.epoch], () => {
console.log('Finished keygen')
const publicKey = JSON.parse(fs.readFileSync(keysFile).toString())[5]
console.log(`Generated multisig account in binance chain: ${publicKeyToAddress(publicKey)}`)
channel.ack(msg)
})
cmd.stdout.on('data', data => console.log(data.toString()))
cmd.stderr.on('data', data => console.error(data.toString()))
})
}
main()
async function connectRabbit (url) {
return amqp.connect(url).catch(() => {
console.log('Failed to connect, reconnecting')
return new Promise(resolve =>
setTimeout(() => resolve(connectRabbit(url)), 1000)
)
})
}
function publicKeyToAddress({x, y}) {
const compact = (parseInt(y[63], 16) % 2 ? '03' : '02') + x
const sha256Hash = crypto.createHash('sha256').update(Buffer.from(compact, 'hex')).digest('hex')
const hash = crypto.createHash('ripemd160').update(Buffer.from(sha256Hash, 'hex')).digest('hex')
const words = bech32.toWords(Buffer.from(hash, 'hex'))
return bech32.encode('tbnb', words)
}

View File

@ -1,40 +0,0 @@
#!/bin/bash
set -e
cd $(dirname "$0")
set -o allexport
source ../.env
set +o allexport
echo Writing params
echo \{\"parties\":\""$PARTIES"\",\"threshold\":\""$THRESHOLD"\"\} > ./params
if [[ -z "$LOCAL" ]]; then
echo Building tss source from git
docker build -t tss ../../tss > /dev/null
else
echo Building tss local source
docker build -t tss -f ../../tss/Dockerfile-local ../../tss > /dev/null
fi
echo Building tss keygen client
docker build -t tss-keygen-client . > /dev/null
touch ../"$KEY_FILE"
echo Generating keys using ssm server at "$SSM_URL"
docker run --rm -v "$(cd ..; pwd)/$KEY_FILE:/tss/keys.store" --network host tss-keygen-client "$SSM_URL"
echo ==========================================================
echo All keys generated, ready to test sign
if [[ -z "$SKIP_SIGN" ]]; then
sleep 3
SKIP_ENV=true ../tss-client/sign.sh test_message
echo Signed successful
fi

View File

@ -0,0 +1,8 @@
{
"name": "tss-keygen",
"version": "0.0.1",
"dependencies": {
"amqplib": "0.5.3",
"bech32": "1.1.3"
}
}

View File

@ -0,0 +1,17 @@
FROM node:10.16.0-slim
WORKDIR /tss
RUN apt-get update && \
apt-get install -y libssl1.1 libssl-dev curl python make g++ libudev-dev libusb-dev usbutils
#apk packages: libssl1.1 eudev-dev libressl-dev curl build-base python linux-headers libusb-dev
COPY package.json /tss/
RUN npm install --no-optional
COPY sign-entrypoint.sh signer.js tx.js /tss/
COPY --from=tss /tss/target/release/gg18_sign_client /tss/
ENTRYPOINT ["node", "signer.js"]

View File

@ -0,0 +1,9 @@
{
"name": "tss-sign",
"version": "0.0.1",
"dependencies": {
"@binance-chain/javascript-sdk": "2.13.9",
"amqplib": "0.5.3",
"axios": "0.19.0"
}
}

View File

@ -0,0 +1,19 @@
#!/bin/bash
set -e
echo "Connecting to $1"
until curl "$1" > /dev/null 2>&1; do
sleep 1;
done
echo "Fetching current tss params"
curl -X GET "$1/params?epoch=$3" -o ./params > /dev/null 2>&1
echo "Signing message using server $1"
./gg18_sign_client "$1" "$2" "$4"
echo "Signed message"

View File

@ -0,0 +1,186 @@
const exec = require('child_process')
const fs = require('fs')
const amqp = require('amqplib')
const crypto = require('crypto')
const bech32 = require('bech32')
const { RABBITMQ_URL, FOREIGN_URL, PROXY_URL } = process.env
const Transaction = require('./tx')
const axios = require('axios')
const httpClient = axios.create({ baseURL: FOREIGN_URL })
async function main () {
console.log('Connecting to RabbitMQ server')
const connection = await connectRabbit(RABBITMQ_URL)
console.log('Connecting to signature events queue')
const channel = await connection.createConfirmChannel()
const queue = await channel.assertQueue('signQueue')
channel.prefetch(1)
channel.consume(queue.queue, async msg => {
const data = JSON.parse(msg.content)
console.log('Consumed sign event')
console.log(data)
const { recipient, value, nonce, epoch } = data
const keysFile = `/keys/keys${epoch}.store`
console.log(`Reading ${keysFile}`)
const { address, publicKey } = await getAccountFromFile(keysFile)
console.log(`Tx from ${address}`)
console.log('Getting account data')
const account = await getAccount(address)
console.log(`Building corresponding transaction, nonce ${nonce}, recipient ${recipient}`)
const tx = new Transaction(address, account.account_number, nonce, recipient, value, 'BNB')
const hash = crypto.createHash('sha256').update(tx.getSignBytes()).digest('hex')
console.log(`Starting signature generation for transaction hash ${hash}`)
const cmd = exec.execFile('./sign-entrypoint.sh', [PROXY_URL, keysFile, epoch, hash], async () => {
console.log('Finished signature generation')
const signature = JSON.parse(fs.readFileSync('signature'))
console.log(signature)
console.log('Building signed transaction')
const signedTx = tx.addSignature(publicKey, { r: signature[1], s: signature[3] })
console.log('Sending transaction')
console.log(signedTx)
await sendTx(signedTx)
channel.ack(msg)
})
cmd.stdout.on('data', data => console.log(data.toString()))
cmd.stderr.on('data', data => console.error(data.toString()))
})
}
main()
async function connectRabbit (url) {
return amqp.connect(url).catch(() => {
console.log('Failed to connect, reconnecting')
return new Promise(resolve =>
setTimeout(() => resolve(connectRabbit(url)), 1000)
)
})
}
async function getAccountFromFile (file) {
while (!fs.existsSync(file)) {
console.log('Waiting for needed epoch key')
await new Promise(resolve => setTimeout(resolve, 1000))
}
const publicKey = JSON.parse(fs.readFileSync(file))[5]
return {
address: publicKeyToAddress(publicKey),
publicKey: publicKey
}
}
async function getAccount (address) {
return httpClient
.get(`/api/v1/account/${address}`)
.then(res => res.data)
}
async function sendTx (tx) {
return httpClient
.post(`/api/v1/broadcast?sync=true`, tx, {
headers: {
'Content-Type': 'text/plain'
}
})
.then(x => console.log(x.response), x => console.log(x.response))
}
function buildTx (acc, nonce, to, value) {
const tx = new Transaction({
account_number: acc.account_number,
chain_id: 'Binance-Chain-Nile',
memo: '',
msg: {
'inputs': [
{
'coins': [
{
'denom': 'BNB',
'amount': value.toString()
}
],
'address': acc.address
}
],
'outputs': [
{
'address': to,
'coins': [
{
'denom': 'BNB',
'amount': value.toString()
}
]
}
]
},
type: 'MsgSend',
sequence: nonce
})
return tx.getSignBytes(tx.msgs[0])
}
function buildSignedTx (acc, publicKey, nonce, to, value, signature) {
const tx = new Transaction({
account_number: acc.account_number,
chain_id: 'Binance-Chain-Nile',
memo: '',
msg: {
'inputs': [
{
'coins': [
{
'denom': 'BNB',
'amount': value.toString()
}
],
'address': acc.address
}
],
'outputs': [
{
'address': to,
'coins': [
{
'denom': 'BNB',
'amount': value.toString()
}
]
}
]
},
type: 'MsgSend',
sequence: nonce
})
tx.signatures = [{
pub_key: Buffer.from(publicKey, 'hex', 38),
signature: Buffer.concat([Buffer.from(signature[1], 'hex', 32), Buffer.from(signature[3], 'hex', 32)]),
account_number: acc.account_number,
sequence: nonce
}]
return tx
}
function encodePublicKey ({ x, y }) {
return 'eb5ae98721' + (parseInt(y[63], 16) % 2 ? '03' : '02') + x
}
function publicKeyToAddress ({ x, y }) {
const compact = (parseInt(y[63], 16) % 2 ? '03' : '02') + x
const sha256Hash = crypto.createHash('sha256').update(Buffer.from(compact, 'hex')).digest('hex')
const hash = crypto.createHash('ripemd160').update(Buffer.from(sha256Hash, 'hex')).digest('hex')
const words = bech32.toWords(Buffer.from(hash, 'hex'))
return bech32.encode('tbnb', words)
}

88
src/oracle/tss-sign/tx.js Normal file
View File

@ -0,0 +1,88 @@
const TransactionBnc = require('@binance-chain/javascript-sdk/lib/tx').default
const { crypto } = require('@binance-chain/javascript-sdk')
const BN = require('bn.js')
const { FOREIGN_CHAIN_ID } = process.env
class Transaction {
constructor (fromAddress, accountNumber, sequence, toAddress, amount, asset, memo = 'test') {
const accCode = crypto.decodeAddress(fromAddress)
const toAccCode = crypto.decodeAddress(toAddress)
amount *= 10 ** 8
const coin = {
denom: asset,
amount: amount,
}
const msg = {
inputs: [{
address: accCode,
coins: [coin]
}],
outputs: [{
address: toAccCode,
coins: [coin]
}],
msgType: 'MsgSend'
}
this.signMsg = {
inputs: [{
address: fromAddress,
coins: [{
amount: amount,
denom: asset
}]
}],
outputs: [{
address: toAddress,
coins: [{
amount: amount,
denom: asset
}]
}]
}
const options = {
account_number: accountNumber,
chain_id: FOREIGN_CHAIN_ID,
memo: memo,
msg,
sequence,
type: msg.msgType,
}
this.tx = new TransactionBnc(options)
}
getSignBytes () {
return this.tx.getSignBytes(this.signMsg)
}
addSignature (publicKey, signature) {
const yLast = parseInt(publicKey.y[publicKey.y.length - 1], 16)
const n = new BN('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141', 16)
const s = new BN(signature.s, 16)
if (s.gt(n.divn(2))) {
console.log('Normalizing s')
signature.s = n.sub(s).toString(16)
}
const publicKeyEncoded = Buffer.from('eb5ae98721' + (yLast % 2 ? '03' : '02') + padZeros(publicKey.x, 64), 'hex')
this.tx.signatures = [{
pub_key: publicKeyEncoded,
signature: Buffer.from(padZeros(signature.r, 64) + padZeros(signature.s, 64), 'hex'),
account_number: this.tx.account_number,
sequence: this.tx.sequence,
}]
return this.tx.serialize()
}
}
function padZeros (s, len) {
while (s.length < len)
s = '0' + s
return s
}
module.exports = Transaction

View File

@ -0,0 +1,14 @@
FROM node:10.16.0-alpine
WORKDIR /watcher
RUN apk update && \
apk add libssl1.1 libressl-dev curl
COPY package.json /watcher/
RUN npm install
COPY ethWatcher.js db.js /watcher/
ENTRYPOINT ["node", "ethWatcher.js"]

View File

@ -0,0 +1,20 @@
{
"sideChain": {
"rpcUrl": "http://host.docker.internal:7545"
},
"redis": {
"port": 6379,
"host": "127.0.0.1",
"family": 4,
"password": "password",
"db": 0
},
"home": {
"rpcUrl": "http://host.docker.internal:7545",
"pollingInterval": 1000
},
"foreign": {
"api": "https://testnet-dex.binance.org/",
"pollingInterval": 1000
}
}

View File

@ -1,9 +1,14 @@
import Redis from 'ioredis'
import { redis as redisConfig } from 'config'
const Redis = require('ioredis')
console.log('Connecting to redis')
const redis = Redis(redisConfig)
const redis = new Redis({
port: 6379,
host: 'redis',
family: 4,
password: 'password',
db: 0
})
redis.on('connect', () => {
console.log('Connected to redis')
@ -13,5 +18,4 @@ redis.on('error', () => {
console.log('Redis error')
})
export const get = redis.get
export const set = redis.set
module.exports = redis

View File

@ -0,0 +1,126 @@
const amqp = require('amqplib')
const Web3 = require('web3')
const redis = require('./db')
const bridgeAbi = require('./contracts_data/SharedDB.json').abi
const { HOME_RPC_URL, HOME_BRIDGE_ADDRESS, RABBITMQ_URL } = process.env
const web3Home = new Web3(HOME_RPC_URL)
const homeBridge = new web3Home.eth.Contract(bridgeAbi, HOME_BRIDGE_ADDRESS)
let channel
let signQueue
let epochQueue
let blockNumber
let foreignNonce = []
let epoch
async function connectRabbit (url) {
return amqp.connect(url).catch(() => {
console.log('Failed to connect, reconnecting')
return new Promise(resolve =>
setTimeout(() => resolve(connectRabbit(url)), 1000)
)
})
}
async function initialize () {
const connection = await connectRabbit(RABBITMQ_URL)
channel = await connection.createChannel()
signQueue = await channel.assertQueue('signQueue')
epochQueue = await channel.assertQueue('epochQueue')
const events = await homeBridge.getPastEvents('KeygenCompleted', {
fromBlock: 1
})
epoch = events.length ? events[events.length - 1].returnValues.epoch.toNumber() : 0
console.log(`Current epoch ${epoch}`)
const dbEpoch = parseInt(await redis.get('epoch')) // number, or NaN if empty
if (epoch !== dbEpoch) {
console.log('Current epoch is outdated, starting from new epoch and block number')
blockNumber = events.length ? events[events.length - 1].blockNumber : 1
await redis.multi()
.set('epoch', epoch)
.set('homeBlock', blockNumber - 1)
.set(`foreignNonce${epoch}`, 0)
.exec()
foreignNonce[epoch] = 0
} else {
console.log('Restoring epoch and block number from local db')
blockNumber = (parseInt(await redis.get('homeBlock')) + 1) || 1
foreignNonce[epoch] = parseInt(await redis.get(`foreignNonce${epoch}`)) || 0
}
}
// By design, epoch change needs last epoch to be confirmed
// Each transfer needs last epoch to be confirmed too
async function main () {
console.log(`Watching events in block #${blockNumber}`)
if (await web3Home.eth.getBlock(blockNumber) === null) {
await new Promise(r => setTimeout(r, 1000))
return
}
const events = await homeBridge.getPastEvents('allEvents', {
fromBlock: blockNumber,
toBlock: blockNumber
})
const epochEvents = events.filter(x => x.event === 'NewEpoch')
const transferEvents = events.filter(x => x.event === 'ReceivedTokens')
epochEvents.forEach(event => {
const newEpoch = event.returnValues.epoch.toNumber()
const oldEpoch = newEpoch - 1
channel.sendToQueue(epochQueue.queue, Buffer.from(JSON.stringify({
epoch: newEpoch
})), {
persistent: true
})
console.log('Sent new epoch event')
if (oldEpoch > 0) {
channel.sendToQueue(signQueue.queue, Buffer.from(JSON.stringify({
epoch: oldEpoch,
nonce: foreignNonce[oldEpoch],
})), {
persistent: true
})
console.log('Sent new epoch sign event')
foreignNonce[oldEpoch]++
redis.incr(`foreignNonce${oldEpoch}`)
}
redis.multi()
.incr('epoch')
.set(`foreignNonce${newEpoch}`, 0)
.exec()
foreignNonce[newEpoch] = 0
epoch++
}
)
transferEvents.forEach(event => {
channel.sendToQueue(signQueue.queue, Buffer.from(JSON.stringify({
recipient: event.returnValues.recipient,
value: event.returnValues.value.toNumber(),
epoch,
nonce: foreignNonce[epoch]
})), {
persistent: true
})
console.log('Sent new sign event')
redis.incr(`foreignNonce${epoch}`)
foreignNonce[epoch]++
}
)
await redis.incr('homeBlock')
blockNumber++
}
initialize().then(async () => {
while (true) {
await main()
}
})

View File

@ -0,0 +1,11 @@
{
"name": "watcher",
"version": "0.0.1",
"dependencies": {
"ioredis": "4.10.0",
"amqplib": "0.5.3",
"web3": "1.0.0-beta.55"
}
}