Fixed starting new epoch

This commit is contained in:
Kirill Fedoseev 2019-07-09 12:00:28 +03:00
parent 8a528e631c
commit ac13be46a0
12 changed files with 102 additions and 61 deletions

View File

@ -0,0 +1,13 @@
### Ethereum to Binance Chain bridge demo
https://forum.poa.network/t/ethereum-to-binance-chain-bridge/2696
#### Running demo:
1) Build tss from local source.
```docker build -t tss -f ./src/tss/Dockerfile-local ./src/tss```
2) Run test environment (home and side blockchains, contracts deployment)
```./demo/start-environment.sh```
3) Run three validators in separate terminal sessions
```N=1 ./demo/validator-demo.sh```
```N=2 ./demo/validator-demo.sh```
```N=3 ./demo/validator-demo.sh```

View File

@ -26,6 +26,7 @@ contract Bridge {
uint public nextThreshold;
uint public epoch;
uint public nextEpoch;
constructor(uint _threshold, uint _parties, address[] memory _validators, address _tokenContract) public {
require(_parties > 0);
@ -34,13 +35,14 @@ contract Bridge {
tokenContract = IERC20(_tokenContract);
epoch = 1;
epoch = 0;
nextEpoch = 1;
ready = false;
nextThreshold = _threshold;
savedNextValidators = _validators;
emit NewEpoch(epoch);
emit NewEpoch(nextEpoch);
}
IERC20 public tokenContract;
@ -66,15 +68,17 @@ contract Bridge {
function confirm(uint _x, uint _y) public {
uint partyId = getNextPartyId(msg.sender);
require(partyId != 0, "Not a next validator");
require(!confirmations[keccak256(abi.encodePacked(epoch, partyId, _x, _y))], "Already confirmed");
require(!confirmations[keccak256(abi.encodePacked(nextEpoch, partyId, _x, _y))], "Already confirmed");
confirmations[keccak256(abi.encodePacked(epoch, partyId, _x, _y))] = true;
if (++confirmationsCount[keccak256(abi.encodePacked(epoch, _x, _y))] == nextParties()) {
confirmations[keccak256(abi.encodePacked(nextEpoch, partyId, _x, _y))] = true;
if (++confirmationsCount[keccak256(abi.encodePacked(nextEpoch, _x, _y))] == nextParties()) {
confirmationsCount[keccak256(abi.encodePacked(nextEpoch, _x, _y))] = 2 ** 256 - 1;
x = _x;
y = _y;
validators = savedNextValidators;
nextValidators = savedNextValidators;
threshold = nextThreshold;
epoch = nextEpoch;
ready = true;
emit KeygenCompleted(epoch, x, y);
}
@ -116,8 +120,6 @@ contract Bridge {
return savedNextValidators;
}
// Send current epoch in votes?
function voteAddValidator(address validator) public {
require(getPartyId() != 0, "Not a current validator");
require(getNextPartyId(validator) == 0, "Already a validator");
@ -161,17 +163,17 @@ contract Bridge {
}
function voteStartEpoch(uint newEpoch) public {
require(newEpoch == epoch + 1, "Wrong epoch number");
require(newEpoch == nextEpoch + 1, "Wrong epoch number");
require(getPartyId() != 0, "Not a current validator");
require(!votes[keccak256(abi.encodePacked(uint(4), epoch, msg.sender))], "Voted already");
require(!votes[keccak256(abi.encodePacked(uint(4), newEpoch, msg.sender))], "Voted already");
votes[keccak256(abi.encodePacked(uint(4), epoch, msg.sender))] = true;
if (++votesCount[keccak256(abi.encodePacked(uint(4), epoch))] == threshold + 1) {
votes[keccak256(abi.encodePacked(uint(4), newEpoch, msg.sender))] = true;
if (++votesCount[keccak256(abi.encodePacked(uint(4), newEpoch))] == threshold + 1) {
ready = false;
epoch++;
nextEpoch = newEpoch;
savedNextValidators = nextValidators;
emit NewEpoch(epoch);
emit NewEpoch(newEpoch);
}
}
}

View File

@ -1,21 +1,12 @@
pragma solidity ^0.5.0;
contract SharedDB {
mapping(bytes32 => bytes) public dbKeygen;
mapping(bytes32 => bytes) public dbSign;
mapping(bytes32 => bytes) public db;
mapping(bytes32 => uint) public signupsCount;
mapping(bytes32 => uint) public dbSignups;
function setKeygenData(bytes32 key, bytes memory data) public {
dbKeygen[keccak256(abi.encodePacked(msg.sender, key))] = data;
}
function getKeygenData(address from, bytes32 key) view public returns (bytes memory) {
return dbKeygen[keccak256(abi.encodePacked(from, key))];
}
function signupSign(bytes32 hash) public {
require(dbSignups[keccak256(abi.encodePacked(msg.sender, hash))] == 0);
require(dbSignups[keccak256(abi.encodePacked(msg.sender, hash))] == 0, "Already signuped");
dbSignups[keccak256(abi.encodePacked(msg.sender, hash))] = ++signupsCount[hash];
}
@ -40,11 +31,11 @@ contract SharedDB {
return address(0);
}
function setSignData(bytes32 hash, bytes32 key, bytes memory data) public {
dbSign[keccak256(abi.encodePacked(msg.sender, hash, key))] = data;
function setData(bytes32 hash, bytes32 key, bytes memory data) public {
db[keccak256(abi.encodePacked(msg.sender, hash, key))] = data;
}
function getSignData(address from, bytes32 hash, bytes32 key) view public returns (bytes memory) {
return dbSign[keccak256(abi.encodePacked(from, hash, key))];
function getData(address from, bytes32 hash, bytes32 key) view public returns (bytes memory) {
return db[keccak256(abi.encodePacked(from, hash, key))];
}
}

View File

@ -60,6 +60,7 @@ async function fetchNewTransactions () {
}
})
.then(res => res.data.tx)
.catch(console.log)
}
function getLastForeignAddress () {

View File

@ -6,12 +6,15 @@ services:
environment:
- HOME_RPC_URL
- HOME_BRIDGE_ADDRESS
- HOME_TOKEN_ADDRESS
- HOME_CHAIN_ID
- SIDE_RPC_URL
- SIDE_SHARED_DB_ADDRESS
- SIDE_CHAIN_ID
- VALIDATOR_PRIVATE_KEY
- FOREIGN_URL
volumes:
- '../deploy/deploy-test/build/contracts/IERC20.json:/proxy/contracts_data/IERC20.json'
- '../deploy/deploy-home/build/contracts/Bridge.json:/proxy/contracts_data/Bridge.json'
- '../deploy/deploy-side/build/contracts/SharedDB.json:/proxy/contracts_data/SharedDB.json'
ports:

View File

@ -1,17 +1,26 @@
const express = require('express')
const Web3 = require('web3')
const AsyncLock = require('async-lock')
const crypto = require('crypto')
const bech32 = require('bech32')
const axios = require('axios')
const BN = require('bignumber.js')
const { HOME_RPC_URL, HOME_BRIDGE_ADDRESS, SIDE_RPC_URL, SIDE_SHARED_DB_ADDRESS, VALIDATOR_PRIVATE_KEY, HOME_CHAIN_ID, SIDE_CHAIN_ID } = process.env
const { HOME_RPC_URL, HOME_BRIDGE_ADDRESS, SIDE_RPC_URL, SIDE_SHARED_DB_ADDRESS, VALIDATOR_PRIVATE_KEY, HOME_CHAIN_ID, SIDE_CHAIN_ID, HOME_TOKEN_ADDRESS, FOREIGN_URL } = process.env
const abiSharedDb = require('./contracts_data/SharedDB.json').abi
const abiBridge = require('./contracts_data/Bridge.json').abi
const abiToken = require('./contracts_data/IERC20.json').abi
const homeWeb3 = new Web3(HOME_RPC_URL, null, { transactionConfirmationBlocks: 1 })
const sideWeb3 = new Web3(SIDE_RPC_URL, null, { transactionConfirmationBlocks: 1 })
const bridge = new homeWeb3.eth.Contract(abiBridge, HOME_BRIDGE_ADDRESS)
const token = new homeWeb3.eth.Contract(abiToken, HOME_TOKEN_ADDRESS)
const sharedDb = new sideWeb3.eth.Contract(abiSharedDb, SIDE_SHARED_DB_ADDRESS)
const validatorAddress = homeWeb3.eth.accounts.privateKeyToAccount(`0x${VALIDATOR_PRIVATE_KEY}`).address
const FOREIGN_ASSET = 'BNB'
const httpClient = axios.create({ baseURL: FOREIGN_URL })
const lock = new AsyncLock()
let homeValidatorNonce
@ -65,6 +74,7 @@ function Err (data) {
async function get (req, res) {
console.log('Get call')
console.log(req.body.key)
const round = req.body.key.second
const uuid = req.body.key.third
let from
@ -77,9 +87,7 @@ async function get (req, res) {
const to = Number(req.body.key.fourth) // 0 if empty
const key = homeWeb3.utils.sha3(`${round}_${to}`)
const data = await (uuid.startsWith('k')
? sharedDb.methods.getKeygenData(from, key).call()
: sharedDb.methods.getSignData(from, uuid, key).call())
const data = await sharedDb.methods.getData(from, sideWeb3.utils.sha3(uuid), key).call()
const result = homeWeb3.utils.hexToUtf8(data)
if (result.length)
@ -98,9 +106,7 @@ async function set (req, res) {
const to = Number(req.body.key.fourth)
const key = homeWeb3.utils.sha3(`${round}_${to}`)
const query = uuid.startsWith('k')
? sharedDb.methods.setKeygenData(key, sideWeb3.utils.utf8ToHex(req.body.value))
: sharedDb.methods.setSignData(uuid, key, sideWeb3.utils.utf8ToHex(req.body.value))
const query = sharedDb.methods.setData(sideWeb3.utils.sha3(uuid), key, sideWeb3.utils.utf8ToHex(req.body.value))
await sideSendQuery(query)
res.send(Ok(null))
@ -109,7 +115,7 @@ async function set (req, res) {
async function signupKeygen (req, res) {
console.log('SignupKeygen call')
const epoch = (await bridge.methods.epoch().call()).toNumber()
const epoch = (await bridge.methods.nextEpoch().call()).toNumber()
const partyId = (await bridge.methods.getNextPartyId(validatorAddress).call()).toNumber()
if (partyId === 0) {
@ -181,6 +187,10 @@ function sideSendQuery (query) {
} catch (e) {
//sideValidatorNonce--
console.log('Side tx failed', e.message)
if (e.message.includes('out of gas')) {
console.log('Out of gas, retrying')
sideSendQuery(query)
}
return null
}
})
@ -204,6 +214,10 @@ function homeSendQuery (query) {
} catch (e) {
//homeValidatorNonce--
console.log('Home tx failed', e.message)
if (e.message.includes('out of gas')) {
console.log('Out of gas, retrying')
homeSendQuery(query)
}
return null
}
})
@ -247,14 +261,20 @@ async function voteRemoveValidator (req, res) {
async function info (req, res) {
console.log('Info start')
const x = new BN(await bridge.methods.x().call()).toString(16)
const y = new BN(await bridge.methods.y().call()).toString(16)
res.send({
epoch: (await bridge.methods.epoch().call()).toNumber(),
nextEpoch: (await bridge.methods.nextEpoch().call()).toNumber(),
threshold: (await bridge.methods.threshold().call()).toNumber(),
nextThreshold: (await bridge.methods.nextThreshold().call()).toNumber(),
homeBridgeAddress: HOME_BRIDGE_ADDRESS,
foreignBridgeAddress: publicKeyToAddress({ x, y }),
validators: await bridge.methods.getValidatorsArray().call(),
nextValidators: await bridge.methods.getNextValidatorsArray().call(),
homeBalance: 0,
foreignBalance: 0
homeBalance: (await token.methods.balanceOf(HOME_BRIDGE_ADDRESS).call()).toNumber(),
foreignBalance: await getForeignBalance(publicKeyToAddress({ x, y })),
bridgeStatus: await bridge.methods.ready().call()
})
console.log('Info end')
}
@ -273,4 +293,25 @@ async function transfer (req, res) {
console.log('Transfer end')
}
function getForeignBalance(address) {
return httpClient
.get(`/api/v1/account/${address}`)
.then(res => parseFloat(res.data.balances.find(x => x.symbol === FOREIGN_ASSET).free))
.catch(err => 0)
}
function publicKeyToAddress ({ x, y }) {
const compact = (parseInt(y[y.length - 1], 16) % 2 ? '03' : '02') + padZeros(x, 64)
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)
}
function padZeros (s, len) {
while (s.length < len)
s = '0' + s
return s
}

View File

@ -3,7 +3,10 @@
"version": "0.0.1",
"dependencies": {
"web3": "1.0.0-beta.55",
"bech32": "1.1.3",
"express": "4.17.1",
"async-lock": "1.2.0"
"async-lock": "1.2.0",
"axios": "0.19.0",
"bignumber.js": "9.0.0"
}
}

View File

@ -3,7 +3,7 @@ FROM node:10.16.0-slim
WORKDIR /tss
RUN apt-get update && \
apt-get install -y libssl1.1 libssl-dev curl
apt-get install -y libssl1.1 libssl-dev curl procps
COPY package.json /tss/

View File

@ -14,6 +14,8 @@ curl -X GET "$1/next_params" -o ./params > /dev/null 2>&1
echo "Generating key using server $1"
pkill gg18_keygen || true
./gg18_keygen_client "$1" "$2"
echo "Generated keys for all parties"

View File

@ -13,27 +13,15 @@ async function main () {
const channel = await connection.createChannel()
const queue = await channel.assertQueue('epochQueue')
let prev
let cmd
channel.prefetch(2)
channel.consume(queue.queue, msg => {
if (prev) {
const t = prev
prev = msg
channel.ack(t)
}
if (cmd) {
cmd.kill()
}
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')
cmd = exec.execFile('./keygen-entrypoint.sh', [PROXY_URL, keysFile], async () => {
cmd = null
const cmd = exec.execFile('./keygen-entrypoint.sh', [PROXY_URL, keysFile], async () => {
if (fs.existsSync(keysFile)) {
console.log(`Finished keygen for epoch ${data.epoch}`)
const publicKey = JSON.parse(fs.readFileSync(keysFile))[5]
@ -42,14 +30,13 @@ async function main () {
console.log('Sending keys confirmation on first generated epoch')
await confirm(keysFile)
}
}
else {
} else {
console.log(`Keygen for epoch ${data.epoch} failed`)
}
prev = null
console.log('Ack for keygen message')
channel.ack(msg)
})
cmd.stdout.on('data', data => console.log(data.toString()))
cmd.stdout.on('data', data => console.error(data.toString()))
cmd.stderr.on('data', data => console.error(data.toString()))
})
}

View File

@ -14,6 +14,6 @@ curl -X GET "$1/current_params" -o ./params > /dev/null 2>&1
echo "Signing message using server $1"
./gg18_sign_client "$1" "$2" "$3"
rm -f signature
echo "Signed message"
./gg18_sign_client "$1" "$2" "$3"

View File

@ -83,8 +83,6 @@ async function main () {
const hash = crypto.createHash('sha256').update(tx.getSignBytes()).digest('hex')
fs.unlinkSync('signature')
console.log(`Starting signature generation for transaction hash ${hash}`)
const cmd = exec.execFile('./sign-entrypoint.sh', [PROXY_URL, prevKeysFile, hash], async () => {
if (fs.existsSync('signature')) {