Fixed starting new epoch
This commit is contained in:
parent
8a528e631c
commit
ac13be46a0
|
@ -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```
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@ async function fetchNewTransactions () {
|
|||
}
|
||||
})
|
||||
.then(res => res.data.tx)
|
||||
.catch(console.log)
|
||||
}
|
||||
|
||||
function getLastForeignAddress () {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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/
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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()))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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')) {
|
||||
|
|
Loading…
Reference in New Issue