Demo of ETH-to-BNC bridge

This commit is contained in:
Kirill Fedoseev 2019-07-07 22:58:35 +03:00
parent 8b50b4dced
commit 8a528e631c
53 changed files with 1002 additions and 20211 deletions

10
.gitignore vendored
View File

@ -4,8 +4,12 @@ node_modules/
**/keys*.store
**/signature
**/params
src/tss/multi-party-ecdsa/
data/
demo/validator*/
demo/validator*/db
demo/validator*/keys
demo/validator*/queue
demo/ganache_data/
src/deploy*/build/
demo/ganache_data_side/
src/deploy/deploy-home/build/
src/deploy/deploy-side/build/
src/deploy/deploy-test/build/

11
.gitmodules vendored
View File

@ -1,6 +1,9 @@
[submodule "src/deploy/contracts/openzeppelin-solidity"]
path = src/deploy/contracts/openzeppelin-solidity
[submodule "src/deploy/deploy-home/contracts/openzeppelin-solidity"]
path = src/deploy/deploy-home/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
[submodule "src/deploy/deploy-test/contracts/openzeppelin-solidity"]
path = src/deploy/deploy-test/contracts/openzeppelin-solidity
url = https://github.com/OpenZeppelin/openzeppelin-solidity.git
[submodule "src/tss/multi-party-ecdsa"]
path = src/tss/multi-party-ecdsa
url = https://github.com/k1rill-fedoseev/multi-party-ecdsa.git

View File

@ -4,26 +4,44 @@ set -e
cd $(dirname "$0")
echo "Starting blockchain"
echo "Starting side test blockchain"
rm -r ./ganache_data
rm -rf ./ganache_data_side
mkdir ganache_data_side
kill $(lsof -t -i:3333) > /dev/null 2>&1 || true
ganache-cli --db ./ganache_data_side -p 3333 -m "shrug dwarf easily blade trigger lucky reopen cage lake scatter desk boat" -i 33 -q &
echo "Starting home test blockchain"
rm -rf ./ganache_data
mkdir ganache_data
kill $(sudo lsof -t -i:7545)
kill $(lsof -t -i:4444) > /dev/null 2>&1 || true
ganache-cli --db ./ganache_data -p 7545 -m "shrug dwarf easily blade trigger lucky reopen cage lake scatter desk boat" -i 33 -q &
ganache-cli -a 20 --db ./ganache_data -p 4444 -m "shrug dwarf easily blade trigger lucky reopen cage lake scatter desk boat" -i 44 -q &
sleep 3
sleep 4
echo "Deploying erc20"
cd ../src/deploy-test
cd ../src/deploy/deploy-test
truffle deploy --network development --reset > /dev/null
echo "Deploying main part"
echo "Deploying home part"
cd ../deploy
cd ../deploy-home
truffle deploy --network development --reset > /dev/null
echo "Deploying side part"
cd ../deploy-side
truffle deploy --network development --reset > /dev/null
echo "Done"

15
demo/validator1/.env Normal file
View File

@ -0,0 +1,15 @@
HOME_RPC_URL=http://host.docker.internal:4444
HOME_CHAIN_ID=44
HOME_BRIDGE_ADDRESS=0x94b40CC641Ed7db241A1f04C8896ba6f6cC36b85
HOME_TOKEN_ADDRESS=0x44c158FE850821ae69DaF37AADF5c539e9d0025B
SIDE_RPC_URL=http://host.docker.internal:3333
SIDE_CHAIN_ID=33
SIDE_SHARED_DB_ADDRESS=0x44c158FE850821ae69DaF37AADF5c539e9d0025B
FOREIGN_URL=https://testnet-dex.binance.org/
FOREIGN_CHAIN_ID=Binance-Chain-Nile
VALIDATOR_PRIVATE_KEY=2be3f252e16541bf1bb2d4a517d2bf173e6d09f2d765d32c64dc50515aec63ea
VOTES_PROXY_PORT=5001

15
demo/validator2/.env Normal file
View File

@ -0,0 +1,15 @@
HOME_RPC_URL=http://host.docker.internal:4444
HOME_CHAIN_ID=44
HOME_BRIDGE_ADDRESS=0x94b40CC641Ed7db241A1f04C8896ba6f6cC36b85
HOME_TOKEN_ADDRESS=0x44c158FE850821ae69DaF37AADF5c539e9d0025B
SIDE_RPC_URL=http://host.docker.internal:3333
SIDE_CHAIN_ID=33
SIDE_SHARED_DB_ADDRESS=0x44c158FE850821ae69DaF37AADF5c539e9d0025B
FOREIGN_URL=https://testnet-dex.binance.org/
FOREIGN_CHAIN_ID=Binance-Chain-Nile
VALIDATOR_PRIVATE_KEY=e59d58c77b791f98f10187117374ae9c589d48a62720ec6a5e142b0cc134f685
VOTES_PROXY_PORT=5002

15
demo/validator3/.env Normal file
View File

@ -0,0 +1,15 @@
HOME_RPC_URL=http://host.docker.internal:4444
HOME_CHAIN_ID=44
HOME_BRIDGE_ADDRESS=0x94b40CC641Ed7db241A1f04C8896ba6f6cC36b85
HOME_TOKEN_ADDRESS=0x44c158FE850821ae69DaF37AADF5c539e9d0025B
SIDE_RPC_URL=http://host.docker.internal:3333
SIDE_CHAIN_ID=33
SIDE_SHARED_DB_ADDRESS=0x44c158FE850821ae69DaF37AADF5c539e9d0025B
FOREIGN_URL=https://testnet-dex.binance.org/
FOREIGN_CHAIN_ID=Binance-Chain-Nile
VALIDATOR_PRIVATE_KEY=afaa4d4d6e54d25b0bf0361e3fd6cef562f6311bf6200de2dd0aa4cab63ae3b5
VOTES_PROXY_PORT=5003

11
package-lock.json generated
View File

@ -78,7 +78,6 @@
"version": "4.63.2",
"resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport-node-hid/-/hw-transport-node-hid-4.63.2.tgz",
"integrity": "sha512-8sMI7KTXQk6UIVrlSfli7KcqZV+sy/BKZ4/RAzqLY1JVrV5qYt6UGPifZD9T99kJut8EgfdV0HETjhdUAy8EgA==",
"optional": true,
"requires": {
"@ledgerhq/devices": "^4.63.2",
"@ledgerhq/errors": "^4.63.2",
@ -94,7 +93,6 @@
"version": "4.63.2",
"resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport-node-hid-noevents/-/hw-transport-node-hid-noevents-4.63.2.tgz",
"integrity": "sha512-KKq3QE3/CBZkO2FpW5SMI9UJ/L3rhFwA3l1N/AQhBuzqIEdXhr/eCoxEcT9GC2Xy4TPwguTiDDcZMBwLuPOafQ==",
"optional": true,
"requires": {
"@ledgerhq/devices": "^4.63.2",
"@ledgerhq/errors": "^4.63.2",
@ -286,6 +284,11 @@
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
"integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ=="
},
"bignumber.js": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz",
"integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A=="
},
"bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
@ -3338,7 +3341,6 @@
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/usb/-/usb-1.6.0.tgz",
"integrity": "sha512-52DyWlCk9K+iw3LnvY95WXSnpHjxJoI++aGkV8HiMNPc4zmvDQlYvWAzrkbJ2JH3oUcx26XfU5sZcG4RAcVkMg==",
"optional": true,
"requires": {
"bindings": "^1.4.0",
"nan": "2.13.2",
@ -3348,8 +3350,7 @@
"nan": {
"version": "2.13.2",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz",
"integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==",
"optional": true
"integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw=="
}
}
},

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,20 +0,0 @@
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,143 +0,0 @@
pragma solidity ^0.5.0;
import './openzeppelin-solidity/contracts/token/ERC20/IERC20.sol';
contract SharedDB {
struct Validator {
address addr;
uint partyId;
bytes32 next;
}
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);
}
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 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);
}
}
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;
}
}

View File

@ -1,5 +1,5 @@
RPC_URL=https://sokol.poa.network
RPC_URL_DEV=http://127.0.0.1:7545
RPC_URL_DEV=http://127.0.0.1:4444
PRIVATE_KEY=e49fe947f224ae8e126c41b1be3e52be701509c2366e835ea8c08651f91030e0
PRIVATE_KEY_DEV=e2aeb24eaa63102d0c0821717c3b6384abdabd7af2ad4ec8e650dce300798b27

View File

@ -0,0 +1,177 @@
pragma solidity ^0.5.0;
import './openzeppelin-solidity/contracts/token/ERC20/IERC20.sol';
contract Bridge {
event NewEpoch(uint indexed epoch);
event KeygenCompleted(uint indexed epoch, uint x, uint y);
event ReceivedTokens(address from, string recipient, uint value); // pass epoch and params in this event
address[] public validators;
address[] public nextValidators;
address[] public savedNextValidators;
mapping(bytes32 => uint) public confirmationsCount;
mapping(bytes32 => bool) public confirmations;
mapping(bytes32 => uint) public dbTransferCount;
mapping(bytes32 => bool) public dbTransfer;
mapping(bytes32 => uint) public votesCount;
mapping(bytes32 => bool) public votes;
uint public x;
uint public y;
bool public ready;
uint public threshold;
uint public nextThreshold;
uint public epoch;
constructor(uint _threshold, uint _parties, address[] memory _validators, address _tokenContract) public {
require(_parties > 0);
require(_threshold < _parties);
require(_validators.length == _parties);
tokenContract = IERC20(_tokenContract);
epoch = 1;
ready = false;
nextThreshold = _threshold;
savedNextValidators = _validators;
emit NewEpoch(epoch);
}
IERC20 public tokenContract;
function requestAffirmation(uint value, string memory recipient) public {
require(ready, "Current epoch is not ready");
tokenContract.transferFrom(msg.sender, address(this), value);
emit ReceivedTokens(msg.sender, recipient, value);
}
function transfer(bytes32 hash, address to, uint value) public {
uint partyId = getPartyId();
require(partyId != 0, "Not a validator");
require(!dbTransfer[keccak256(abi.encodePacked(hash, msg.sender, to, value))], "Already voted");
dbTransfer[keccak256(abi.encodePacked(hash, msg.sender, to, value))] = true;
if (++dbTransferCount[keccak256(abi.encodePacked(hash, to, value))] == threshold + 1)
tokenContract.transfer(to, value);
}
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");
confirmations[keccak256(abi.encodePacked(epoch, partyId, _x, _y))] = true;
if (++confirmationsCount[keccak256(abi.encodePacked(epoch, _x, _y))] == nextParties()) {
x = _x;
y = _y;
validators = savedNextValidators;
nextValidators = savedNextValidators;
threshold = nextThreshold;
ready = true;
emit KeygenCompleted(epoch, x, y);
}
}
function parties() view public returns (uint) {
return validators.length;
}
function nextParties() view public returns (uint) {
return savedNextValidators.length;
}
function getPartyId() view public returns (uint) {
return getPartyId(msg.sender);
}
function getPartyId(address a) view public returns (uint) {
for (uint i = 0; i < parties(); i++) {
if (validators[i] == a)
return i + 1;
}
return 0;
}
function getNextPartyId(address a) view public returns (uint) {
for (uint i = 0; i < nextParties(); i++) {
if (savedNextValidators[i] == a)
return i + 1;
}
return 0;
}
function getValidatorsArray() view public returns (address[] memory) {
return validators;
}
function getNextValidatorsArray() view public returns (address[] memory) {
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");
require(!votes[keccak256(abi.encodePacked(uint(1), epoch, msg.sender, validator))], "Already voted");
votes[keccak256(abi.encodePacked(uint(1), epoch, msg.sender, validator))] = true;
if (++votesCount[keccak256(abi.encodePacked(uint(1), epoch, validator))] == threshold + 1) {
nextValidators.push(validator);
}
}
function voteRemoveValidator(address validator) public {
require(getPartyId() != 0, "Not a current validator");
require(getNextPartyId(validator) != 0, "Already not a validator");
require(!votes[keccak256(abi.encodePacked(uint(2), epoch, msg.sender, validator))], "Already voted");
votes[keccak256(abi.encodePacked(uint(2), epoch, msg.sender, validator))] = true;
if (++votesCount[keccak256(abi.encodePacked(uint(2), epoch, validator))] == threshold + 1) {
_removeValidator(validator);
}
}
function _removeValidator(address validator) private {
for (uint i = 0; i < nextValidators.length - 1; i++) {
if (nextValidators[i] == validator) {
nextValidators[i] = nextValidators[nextValidators.length - 1];
}
}
delete nextValidators[nextValidators.length - 1];
nextValidators.length--;
}
function voteChangeThreshold(uint _threshold) public {
require(getPartyId() != 0, "Not a current validator");
require(!votes[keccak256(abi.encodePacked(uint(3), epoch, msg.sender, threshold))], "Already voted");
votes[keccak256(abi.encodePacked(uint(3), epoch, msg.sender, _threshold))] = true;
if (++votesCount[keccak256(abi.encodePacked(uint(3), epoch, _threshold))] == threshold + 1) {
nextThreshold = _threshold;
}
}
function voteStartEpoch(uint newEpoch) public {
require(newEpoch == epoch + 1, "Wrong epoch number");
require(getPartyId() != 0, "Not a current validator");
require(!votes[keccak256(abi.encodePacked(uint(4), epoch, msg.sender))], "Voted already");
votes[keccak256(abi.encodePacked(uint(4), epoch, msg.sender))] = true;
if (++votesCount[keccak256(abi.encodePacked(uint(4), epoch))] == threshold + 1) {
ready = false;
epoch++;
savedNextValidators = nextValidators;
emit NewEpoch(epoch);
}
}
}

View File

@ -1,6 +1,6 @@
require('dotenv').config()
const SharedDB = artifacts.require('SharedDB')
const Bridge = artifacts.require('Bridge')
const {
THRESHOLD, PARTIES, VALIDATOR_ADDRESS_1, VALIDATOR_ADDRESS_2, VALIDATOR_ADDRESS_3, VALIDATOR_ADDRESS_4,
@ -9,7 +9,7 @@ const {
module.exports = deployer => {
deployer.deploy(
SharedDB,
Bridge,
THRESHOLD,
PARTIES,
[

View File

@ -8,7 +8,7 @@ module.exports = {
networks: {
development: {
provider: new PrivateKeyProvider(PRIVATE_KEY_DEV, RPC_URL_DEV),
network_id: '33'
network_id: '44'
},
staging: {
provider: new PrivateKeyProvider(PRIVATE_KEY, RPC_URL),

View File

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

View File

@ -0,0 +1,50 @@
pragma solidity ^0.5.0;
contract SharedDB {
mapping(bytes32 => bytes) public dbKeygen;
mapping(bytes32 => bytes) public dbSign;
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);
dbSignups[keccak256(abi.encodePacked(msg.sender, hash))] = ++signupsCount[hash];
}
function getSignupNumber(bytes32 hash, address[] memory validators, address validator) view public returns (uint) {
require(dbSignups[keccak256(abi.encodePacked(validator, hash))] > 0, "Have not voted yet");
uint id = 1;
for (uint i = 0; i < validators.length; i++) {
uint vid = dbSignups[keccak256(abi.encodePacked(validators[i], hash))];
if (vid > 0 && vid < dbSignups[keccak256(abi.encodePacked(validator, hash))])
id++;
}
return id;
}
function getSignupAddress(bytes32 hash, address[] memory validators, uint signupNumber) view public returns (address) {
for (uint i = 0; i < validators.length; i++) {
if (getSignupNumber(hash, validators, validators[i]) == signupNumber) {
return validators[i];
}
}
return address(0);
}
function setSignData(bytes32 hash, bytes32 key, bytes memory data) public {
dbSign[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))];
}
}

View File

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

View File

@ -0,0 +1,5 @@
RPC_URL=https://sokol.poa.network
RPC_URL_DEV=http://127.0.0.1:4444
PRIVATE_KEY=e49fe947f224ae8e126c41b1be3e52be701509c2366e835ea8c08651f91030e0
PRIVATE_KEY_DEV=e2aeb24eaa63102d0c0821717c3b6384abdabd7af2ad4ec8e650dce300798b27

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);
}
}

View File

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

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: '44'
},
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

@ -1,14 +1,12 @@
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
HOME_RPC_URL=http://127.0.0.1:4444
SIDE_RPC_URL=http://127.0.0.1:3333
HOME_CHAIN_ID=44
SIDE_CHAIN_ID=33
SHARED_DB_ADDRESS=0x94b40CC641Ed7db241A1f04C8896ba6f6cC36b85
TOKEN_ADDRESS=0x44c158FE850821ae69DaF37AADF5c539e9d0025B
HOME_BRIDGE_ADDRESS=0x94b40CC641Ed7db241A1f04C8896ba6f6cC36b85
HOME_TOKEN_ADDRESS=0x44c158FE850821ae69DaF37AADF5c539e9d0025B
SIDE_SHARED_DB_ADDRESS=0x44c158FE850821ae69DaF37AADF5c539e9d0025B
#VALIDATOR_PRIVATE_KEY=d1e7b8ff274e517e1a332f2bc0ac051e30db196ba31c68c2efcd022e8ec358f1
VALIDATOR_PRIVATE_KEY=2be3f252e16541bf1bb2d4a517d2bf173e6d09f2d765d32c64dc50515aec63ea
@ -17,9 +15,9 @@ VALIDATOR_PRIVATE_KEY=2be3f252e16541bf1bb2d4a517d2bf173e6d09f2d765d32c64dc50515a
LOCAL=true
#MESSAGE=fb2487601395e1cabad725e2e006ae16dd134a536ea1e30919b86e7aa572584d
DEPLOY_PRIVATE_KEY=e2aeb24eaa63102d0c0821717c3b6384abdabd7af2ad4ec8e650dce300798b27
FOREIGN_URL=https://testnet-dex.binance.org/
FOREIGN_CHAIN_ID=Binance-Chain-Nile
VOTES_PROXY_PORT=5000

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 bncWatcher.js db.js /watcher/
ENTRYPOINT ["node", "bncWatcher.js"]

View File

@ -0,0 +1,92 @@
const redis = require('./db')
const axios = require('axios')
const bech32 = require('bech32')
const BN = require('bignumber.js')
const fs = require('fs')
const crypto = require('crypto')
const { FOREIGN_URL, PROXY_URL } = process.env
const FOREIGN_ASSET = 'BNB'
const foreignHttpClient = axios.create({ baseURL: FOREIGN_URL })
const proxyHttpClient = axios.create({ baseURL: PROXY_URL })
async function initialize () {
if (await redis.get('foreignTime') === null) {
console.log('Set default foreign time')
await redis.set('foreignTime', 1562306990672)
}
}
async function main () {
const newTransactions = await fetchNewTransactions()
if (newTransactions === null || newTransactions.length === 0) {
await new Promise(r => setTimeout(r, 5000))
return
}
console.log(`Found ${newTransactions.length} new transactions`)
for (const tx of newTransactions.reverse()) {
if (tx.memo !== 'funding') {
await proxyHttpClient
.post('/transfer', {
to: tx.memo,
value: new BN(tx.value).integerValue(BN.ROUND_FLOOR),//(new BN(tx.value).multipliedBy(10 ** 8)).toNumber(),
hash: `0x${tx.txHash}`
})
}
await redis.set('foreignTime', Date.parse(tx.timeStamp))
}
}
async function fetchNewTransactions () {
console.log('Fetching new transactions')
const startTime = parseInt(await redis.get('foreignTime')) + 1
const address = await getLastForeignAddress()
if (address === null)
return null
console.log('Sending api transactions request')
return foreignHttpClient
.get('/api/v1/transactions', {
params: {
address,
side: 'RECEIVE',
txAsset: FOREIGN_ASSET,
txType: 'TRANSFER',
startTime,
endTime: startTime + 3 * 30 * 24 * 60 * 60 * 1000,
}
})
.then(res => res.data.tx)
}
function getLastForeignAddress () {
const epoch = Math.max(0, ...fs.readdirSync('/keys').map(x => parseInt(x.split('.')[0].substr(4))))
if (epoch === 0)
return null
const keysFile = `/keys/keys${epoch}.store`
const publicKey = JSON.parse(fs.readFileSync(keysFile))[5]
return publicKeyToAddress(publicKey)
}
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
}
initialize().then(async () => {
while (true) {
await main()
}
})

View File

@ -0,0 +1,12 @@
{
"name": "watcher",
"version": "0.0.1",
"dependencies": {
"ioredis": "4.10.0",
"axios": "0.19.0",
"bech32": "1.1.3",
"bignumber.js": "9.0.0"
}
}

View File

@ -4,14 +4,23 @@ services:
image: blockchain-proxy
build: ./proxy
environment:
- RPC_URL
- SHARED_DB_ADDRESS
- HOME_RPC_URL
- HOME_BRIDGE_ADDRESS
- HOME_CHAIN_ID
- SIDE_RPC_URL
- SIDE_SHARED_DB_ADDRESS
- SIDE_CHAIN_ID
- VALIDATOR_PRIVATE_KEY
volumes:
- '../deploy/build/contracts:/proxy/contracts_data'
- '../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:
- '${VOTES_PROXY_PORT}:8002'
networks:
- sign-proxy-net
- keygen-proxy-net
- test_network
# - sign-proxy-net
# - keygen-proxy-net
# - bncwatcher-proxy-net
keygen:
image: keygen-client
build: ./tss-keygen
@ -21,9 +30,10 @@ services:
volumes:
- '${PWD}/keys:/keys'
networks:
- keygen-proxy-net
- rabbit-keygen-net
- redis-keygen-net
- test_network
# - keygen-proxy-net
# - rabbit-keygen-net
# - redis-keygen-net
signer:
image: sign-client
build: ./tss-sign
@ -35,44 +45,68 @@ services:
volumes:
- '${PWD}/keys:/keys'
networks:
- sign-proxy-net
- rabbit-signer-net
- redis-signer-net
- test_network
# - 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
- test_network
# - redis-signer-net
# - redis-keygen-net
# - redis-ethwatcher-net
# - redis-bncwatcher-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
- test_network
# - rabbit-signer-net
# - rabbit-keygen-net
# - rabbit-ethwatcher-net
# - rabbit-bncwatcher-net
eth-watcher:
build: ./watcher
build: ethWatcher
image: eth-watcher
environment:
- 'HOME_RPC_URL=${RPC_URL}'
- 'HOME_BRIDGE_ADDRESS=${SHARED_DB_ADDRESS}'
- HOME_RPC_URL
- HOME_BRIDGE_ADDRESS
- 'RABBITMQ_URL=amqp://rabbitmq:5672'
volumes:
- '../deploy/build/contracts:/watcher/contracts_data'
- '../deploy/deploy-home/build/contracts/Bridge.json:/watcher/contracts_data/Bridge.json'
networks:
- rabbit-watcher-net
- redis-watcher-net
- test_network
# - rabbit-ethwatcher-net
# - redis-ethwatcher-net
bnc-watcher:
build: bncWatcher
image: bnc-watcher
environment:
- FOREIGN_URL
- 'RABBITMQ_URL=amqp://rabbitmq:5672'
- 'PROXY_URL=http://proxy:8001'
volumes:
- '${PWD}/keys:/keys'
networks:
- test_network
# - rabbit-bncwatcher-net
# - redis-bncwatcher-net
# - bncwatcher-proxy-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:
test_network:
# sign-proxy-net:
# keygen-proxy-net:
# rabbit-signer-net:
# rabbit-keygen-net:
# rabbit-ethwatcher-net:
# rabbit-bncwatcher-net:
# redis-keygen-net:
# redis-signer-net:
# redis-ethwatcher-net:
# redis-bncwatcher-net:
# bncwatcher-proxy-net:

View File

@ -0,0 +1,21 @@
const Redis = require('ioredis')
console.log('Connecting to redis')
const redis = new Redis({
port: 6379,
host: 'redis',
family: 4,
password: 'password',
db: 0
})
redis.on('connect', () => {
console.log('Connected to redis')
})
redis.on('error', () => {
console.log('Redis error')
})
module.exports = redis

View File

@ -1,7 +1,7 @@
const amqp = require('amqplib')
const Web3 = require('web3')
const redis = require('./db')
const bridgeAbi = require('./contracts_data/SharedDB.json').abi
const bridgeAbi = require('./contracts_data/Bridge.json').abi
const { HOME_RPC_URL, HOME_BRIDGE_ADDRESS, RABBITMQ_URL } = process.env
@ -52,11 +52,10 @@ async function initialize () {
}
}
// 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) {
console.log('No block')
await new Promise(r => setTimeout(r, 1000))
return
}
@ -79,9 +78,10 @@ async function main () {
console.log('Sent new epoch event')
if (oldEpoch > 0) {
// Transfer all assets to new account tss account
channel.sendToQueue(signQueue.queue, Buffer.from(JSON.stringify({
epoch: oldEpoch,
nonce: foreignNonce[oldEpoch],
epoch: newEpoch,
//nonce: foreignNonce[oldEpoch],
})), {
persistent: true
})

View File

@ -1,17 +1,21 @@
const express = require('express')
const Web3 = require('web3')
const fs = require('fs')
const BN = require('bignumber.js')
const ethers = require('ethers')
const AsyncLock = require('async-lock')
const { RPC_URL, SHARED_DB_ADDRESS, VALIDATOR_PRIVATE_KEY } = process.env
const abi = require('./contracts_data/SharedDB.json').abi
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 abiSharedDb = require('./contracts_data/SharedDB.json').abi
const abiBridge = require('./contracts_data/Bridge.json').abi
const web3 = new Web3(RPC_URL, null, { transactionConfirmationBlocks: 1 })
const contract = new web3.eth.Contract(abi, SHARED_DB_ADDRESS)
const validatorAddress = web3.eth.accounts.privateKeyToAccount(`0x${VALIDATOR_PRIVATE_KEY}`).address
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 sharedDb = new sideWeb3.eth.Contract(abiSharedDb, SIDE_SHARED_DB_ADDRESS)
const validatorAddress = homeWeb3.eth.accounts.privateKeyToAccount(`0x${VALIDATOR_PRIVATE_KEY}`).address
let validatorNonce
const lock = new AsyncLock()
let homeValidatorNonce
let sideValidatorNonce
const app = express()
app.use(express.json())
@ -22,23 +26,35 @@ app.post('/set', set)
app.post('/signupkeygen', signupKeygen)
app.post('/signupsign', signupSign)
app.get('/params', params)
app.get('/current_params', currentParams)
app.get('/next_params', nextParams)
app.post('/confirm', confirm)
app.post('/transfer', transfer)
const votesProxyApp = express()
votesProxyApp.use(express.json())
votesProxyApp.use(express.urlencoded({ extended: true }))
votesProxyApp.get('/vote/startEpoch/:epoch', voteStartEpoch)
votesProxyApp.get('/vote/addValidator/:validator', voteAddValidator)
votesProxyApp.get('/vote/removeValidator/:validator', voteRemoveValidator)
votesProxyApp.get('/info', info)
async function main () {
validatorNonce = await web3.eth.getTransactionCount(validatorAddress)
try {
fs.mkdirSync('/generated_data')
} catch (e) {
}
homeValidatorNonce = await homeWeb3.eth.getTransactionCount(validatorAddress)
sideValidatorNonce = await sideWeb3.eth.getTransactionCount(validatorAddress)
app.listen(8001, () => {
console.log('Listening on port 8001')
console.log('Proxy is listening on port 8001')
})
votesProxyApp.listen(8002, () => {
console.log('Votes proxy is listening on port 8001')
})
}
main()
function Ok (data) {
return { Ok: data }
}
@ -49,16 +65,23 @@ function Err (data) {
async function get (req, res) {
console.log('Get call')
const round = req.body.key.second
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}`)
let from
if (uuid.startsWith('k'))
from = await bridge.methods.savedNextValidators(parseInt(req.body.key.first) - 1).call()
else {
const validators = await bridge.methods.getValidatorsArray().call()
from = await sharedDb.methods.getSignupAddress(uuid, validators, parseInt(req.body.key.first)).call()
}
const to = Number(req.body.key.fourth) // 0 if empty
const key = homeWeb3.utils.sha3(`${round}_${to}`)
const data = await (uuid.startsWith('k')
? contract.methods.getKeygenData(from, key).call()
: contract.methods.getSignData(from, uuid, key).call())
? sharedDb.methods.getKeygenData(from, key).call()
: sharedDb.methods.getSignData(from, uuid, key).call())
const result = web3.utils.hexToUtf8(data)
const result = homeWeb3.utils.hexToUtf8(data)
if (result.length)
res.send(Ok({ key: req.body.key, value: result }))
else {
@ -70,14 +93,15 @@ async function get (req, res) {
async function set (req, res) {
console.log('Set call')
const round = req.body.key.second
const uuid = req.body.key.third
const to = Number(req.body.key.fourth)
const key = web3.utils.sha3(`${req.body.key.second}_${to}`)
const key = homeWeb3.utils.sha3(`${round}_${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)
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))
await sideSendQuery(query)
res.send(Ok(null))
console.log('Set end')
@ -85,71 +109,168 @@ async function set (req, res) {
async function signupKeygen (req, res) {
console.log('SignupKeygen call')
const epoch = (await contract.methods.epoch().call()).toNumber()
const partyId = (await contract.methods.getPartyId().call({ from: validatorAddress })).toNumber()
const epoch = (await bridge.methods.epoch().call()).toNumber()
const partyId = (await bridge.methods.getNextPartyId(validatorAddress).call()).toNumber()
res.send(Ok({ uuid: `k${epoch}`, number: partyId }))
console.log('SignupKeygen end')
if (partyId === 0) {
res.send(Err({ message: 'Not a validator' }))
} else {
res.send(Ok({ uuid: `k${epoch}`, number: partyId }))
console.log('SignupKeygen end')
}
}
async function signupSign (req, res) {
console.log('SignupSign call')
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)
const hash = sideWeb3.utils.sha3(`0x${req.body.third}`)
const query = sharedDb.methods.signupSign(hash)
await sideSendQuery(query)
while (true) {
const events = await contract.getPastEvents('Signup', {
filter: { from: validatorAddress, hash },
fromBlock: receipt.blockNumber,
toBlock: receipt.blockNumber
})
const event = events[0]
const validators = await bridge.methods.getValidatorsArray().call()
const threshold = await bridge.methods.threshold().call()
const id = (await sharedDb.methods.getSignupNumber(hash, validators, validatorAddress).call()).toNumber()
if (event) {
res.send(Ok({ uuid: hash, number: event.returnValues.partyId.toNumber() }))
break
}
if (id > threshold + 1) {
res.send(Err({}))
}
res.send(Ok({ uuid: hash, number: id }))
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)
const query = bridge.methods.confirm(`0x${x}`, `0x${y}`)
await homeSendQuery(query)
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()
async function currentParams (req, res) {
console.log('Current params call')
const parties = (await bridge.methods.parties().call()).toNumber().toString()
const threshold = (await bridge.methods.threshold().call()).toNumber().toString()
res.send({ parties, threshold })
console.log('Params end')
console.log('Current params end')
}
async function sendQuery (query) {
const encodedABI = query.encodeABI()
const tx = {
data: encodedABI,
from: validatorAddress,
to: SHARED_DB_ADDRESS,
nonce: validatorNonce++,
chainId: 33
async function nextParams (req, res) {
console.log('Next params call')
const parties = (await bridge.methods.nextParties().call()).toNumber().toString()
const threshold = (await bridge.methods.nextThreshold().call()).toNumber().toString()
res.send({ parties, threshold })
console.log('Next params end')
}
function sideSendQuery (query) {
return lock.acquire('side', async () => {
const encodedABI = query.encodeABI()
const tx = {
data: encodedABI,
from: validatorAddress,
to: SIDE_SHARED_DB_ADDRESS,
nonce: sideValidatorNonce++,
chainId: parseInt(SIDE_CHAIN_ID)
}
tx.gas = Math.min(Math.ceil(await query.estimateGas(tx) * 1.5), 6721975)
const signedTx = await sideWeb3.eth.accounts.signTransaction(tx, VALIDATOR_PRIVATE_KEY)
try {
return await sideWeb3.eth.sendSignedTransaction(signedTx.rawTransaction)
} catch (e) {
//sideValidatorNonce--
console.log('Side tx failed', e.message)
return null
}
})
}
function homeSendQuery (query) {
return lock.acquire('home', async () => {
const encodedABI = query.encodeABI()
const tx = {
data: encodedABI,
from: validatorAddress,
to: HOME_BRIDGE_ADDRESS,
nonce: homeValidatorNonce++,
chainId: parseInt(HOME_CHAIN_ID)
}
tx.gas = Math.min(Math.ceil(await query.estimateGas(tx) * 1.5), 6721975)
const signedTx = await homeWeb3.eth.accounts.signTransaction(tx, VALIDATOR_PRIVATE_KEY)
try {
return await homeWeb3.eth.sendSignedTransaction(signedTx.rawTransaction)
} catch (e) {
//homeValidatorNonce--
console.log('Home tx failed', e.message)
return null
}
})
}
async function voteStartEpoch (req, res) {
console.log('Voting for starting new epoch')
const query = bridge.methods.voteStartEpoch(req.params.epoch)
try {
await homeSendQuery(query)
} catch (e) {
console.log(e)
}
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)
return receipt
res.send('Voted')
console.log('Voted successfully')
}
main()
async function voteAddValidator (req, res) {
console.log('Voting for adding new validator')
const query = bridge.methods.voteAddValidator(req.params.validator)
try {
await homeSendQuery(query)
} catch (e) {
console.log(e)
}
res.send('Voted')
console.log('Voted successfully')
}
async function voteRemoveValidator (req, res) {
console.log('Voting for removing validator')
const query = bridge.methods.voteRemoveValidator(req.params.validator)
try {
await homeSendQuery(query)
} catch (e) {
console.log(e)
}
res.send('Voted')
console.log('Voted successfully')
}
async function info (req, res) {
console.log('Info start')
res.send({
epoch: (await bridge.methods.epoch().call()).toNumber(),
threshold: (await bridge.methods.threshold().call()).toNumber(),
nextThreshold: (await bridge.methods.nextThreshold().call()).toNumber(),
validators: await bridge.methods.getValidatorsArray().call(),
nextValidators: await bridge.methods.getNextValidatorsArray().call(),
homeBalance: 0,
foreignBalance: 0
})
console.log('Info end')
}
async function transfer (req, res) {
console.log('Transfer start')
const { hash, to, value } = req.body
if (homeWeb3.utils.isAddress(to)) {
console.log('Calling transfer')
const query = bridge.methods.transfer(hash, to, value)
await homeSendQuery(query)
} else {
// return funds ?
}
res.send()
console.log('Transfer end')
}

View File

@ -3,9 +3,7 @@
"version": "0.0.1",
"dependencies": {
"web3": "1.0.0-beta.55",
"dotenv": "8.0.0",
"express": "4.17.1",
"bignumber.js": "9.0.0",
"ethers": "4.0.31"
"async-lock": "1.2.0"
}
}

View File

@ -2,14 +2,14 @@ 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 { HOME_RPC_URL, HOME_BRIDGE_ADDRESS, HOME_CHAIN_ID, DEPLOY_PRIVATE_KEY, HOME_TOKEN_ADDRESS } = process.env
const web3 = new Web3(HOME_RPC_URL, null, { transactionConfirmationBlocks: 1 })
const abiBridge = require('../deploy/deploy-home/build/contracts/SharedDB').abi
const abiToken = require('../deploy/deploy-home/build/contracts/IERC20').abi
const bridge = new web3.eth.Contract(abiBridge, HOME_BRIDGE_ADDRESS)
const token = new web3.eth.Contract(abiToken, HOME_TOKEN_ADDRESS)
const query1 = token.methods.approve(SHARED_DB_ADDRESS, 1)
const query1 = token.methods.approve(HOME_BRIDGE_ADDRESS, 1)
const query2 = bridge.methods.requestAffirmation(1, 'tbnb1h3nmmqukrtjc0prmtdts0kxlgmw8rend4zfasn')
let nonce
@ -18,8 +18,8 @@ const deployAddress = web3.eth.accounts.privateKeyToAccount(`0x${DEPLOY_PRIVATE_
async function main () {
console.log(deployAddress)
nonce = await web3.eth.getTransactionCount(deployAddress)
await sendQuery(query1, TOKEN_ADDRESS)
await sendQuery(query2, SHARED_DB_ADDRESS)
await sendQuery(query1, HOME_TOKEN_ADDRESS)
await sendQuery(query2, HOME_BRIDGE_ADDRESS)
}
async function sendQuery (query, to) {
@ -29,14 +29,12 @@ async function sendQuery (query, to) {
from: deployAddress,
to,
nonce: nonce++,
chainId: 33
chainId: parseInt(HOME_CHAIN_ID)
}
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
return await web3.eth.sendSignedTransaction(signedTx.rawTransaction)
}
main()

View File

@ -1,17 +1,22 @@
require('dotenv').config()
const Bnc = require('@binance-chain/javascript-sdk')
const axios = require('axios')
const Transaction = require('../oracle/tss-sign/tx')
const Transaction = require('./tss-sign/tx')
const crypto = require('crypto')
const ecc = require('tiny-secp256k1')
const privKey = 'b92a59209e28149e5cee8e54dfceb80a08ea08e654261bdb9d264b15dee2525c'
const asset = 'BNB'
const amount = 2.5
const amount = '2.5'
const addressTo = process.argv[2]
const addressFrom = Bnc.crypto.getAddressFromPrivateKey(privKey)
const message = 'A note to you'
const message = process.argv[3] || 'funding'
const api = 'https://testnet-dex.binance.org/'
const publicKey = {
x: 'b32b5ea8698156239ea7092ef8a44a4b711ea29525da34a8233bdc0dd3af7f1a',
y: '6b5b77f2e925f93cae7fc894ff50bafcb7b6e6e96e339c96e41663ccaf0a4d68'
}
const httpClient = axios.create({ baseURL: api })
@ -19,20 +24,14 @@ 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 tx = new Transaction('tbnb1h3nmmqukrtjc0prmtdts0kxlgmw8rend4zfasn', 674629, sequence, addressTo, amount, asset, message)
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 signature = ecc.sign(Buffer.from(hash, 'hex'), Buffer.from(privKey, '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 => {

View File

@ -8,9 +8,9 @@ until curl "$1" > /dev/null 2>&1; do
sleep 1;
done
echo "Fetching current tss params"
echo "Fetching next tss params"
curl -X GET "$1/params?epoch=$3" -o ./params > /dev/null 2>&1
curl -X GET "$1/next_params" -o ./params > /dev/null 2>&1
echo "Generating key using server $1"
@ -18,6 +18,6 @@ echo "Generating key using server $1"
echo "Generated keys for all parties"
echo "Sending confirmation"
#echo "Sending confirmation"
curl -X POST -H "Content-Type: application/json" -d @"$2" "$1/confirm" > /dev/null 2>&1
#curl -X POST -H "Content-Type: application/json" -d @"$2" "$1/confirm" > /dev/null 2>&1

View File

@ -10,21 +10,43 @@ 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 channel = await connection.createChannel()
const queue = await channel.assertQueue('epochQueue')
channel.prefetch(1)
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')
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)}`)
cmd = exec.execFile('./keygen-entrypoint.sh', [PROXY_URL, keysFile], async () => {
cmd = null
if (fs.existsSync(keysFile)) {
console.log(`Finished keygen for epoch ${data.epoch}`)
const publicKey = JSON.parse(fs.readFileSync(keysFile))[5]
console.log(`Generated multisig account in binance chain: ${publicKeyToAddress(publicKey)}`)
if (data.epoch === 1) {
console.log('Sending keys confirmation on first generated epoch')
await confirm(keysFile)
}
}
else {
console.log(`Keygen for epoch ${data.epoch} failed`)
}
prev = null
channel.ack(msg)
})
cmd.stdout.on('data', data => console.log(data.toString()))
@ -43,10 +65,20 @@ async function connectRabbit (url) {
})
}
function publicKeyToAddress({x, y}) {
const compact = (parseInt(y[63], 16) % 2 ? '03' : '02') + x
async function confirm (keysFile) {
exec.execSync(`curl -X POST -H "Content-Type: application/json" -d @"${keysFile}" "${PROXY_URL}/confirm"`, { stdio: 'pipe' })
}
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

@ -4,6 +4,7 @@
"dependencies": {
"@binance-chain/javascript-sdk": "2.13.9",
"amqplib": "0.5.3",
"axios": "0.19.0"
"axios": "0.19.0",
"bignumber.js": "9.0.0"
}
}

View File

@ -10,10 +10,10 @@ done
echo "Fetching current tss params"
curl -X GET "$1/params?epoch=$3" -o ./params > /dev/null 2>&1
curl -X GET "$1/current_params" -o ./params > /dev/null 2>&1
echo "Signing message using server $1"
./gg18_sign_client "$1" "$2" "$4"
./gg18_sign_client "$1" "$2" "$3"
echo "Signed message"

View File

@ -3,8 +3,10 @@ const fs = require('fs')
const amqp = require('amqplib')
const crypto = require('crypto')
const bech32 = require('bech32')
const BN = require('bignumber.js')
const { RABBITMQ_URL, FOREIGN_URL, PROXY_URL } = process.env
const FOREIGN_ASSET = 'BNB'
const Transaction = require('./tx')
const axios = require('axios')
@ -14,7 +16,7 @@ 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 channel = await connection.createChannel()
const queue = await channel.assertQueue('signQueue')
channel.prefetch(1)
@ -24,36 +26,88 @@ async function main () {
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}`)
if (recipient) {
const keysFile = `/keys/keys${epoch}.store`
console.log('Getting account data')
const account = await getAccount(address)
const { address, publicKey } = await getAccountFromFile(keysFile)
console.log(`Tx from ${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')
const account = await getAccount(address)
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 corresponding trasfer transaction, nonce ${nonce}, recipient ${recipient}`)
const tx = new Transaction(address, account.account_number, nonce, recipient, value, FOREIGN_ASSET)
console.log('Building signed transaction')
const signedTx = tx.addSignature(publicKey, { r: signature[1], s: signature[3] })
const hash = crypto.createHash('sha256').update(tx.getSignBytes()).digest('hex')
console.log('Sending transaction')
console.log(signedTx)
await sendTx(signedTx)
console.log(`Starting signature generation for transaction hash ${hash}`)
const cmd = exec.execFile('./sign-entrypoint.sh', [PROXY_URL, keysFile, hash], async () => {
if (fs.existsSync('signature')) {
console.log('Finished signature generation')
const signature = JSON.parse(fs.readFileSync('signature'))
console.log(signature)
channel.ack(msg)
})
cmd.stdout.on('data', data => console.log(data.toString()))
cmd.stderr.on('data', data => console.error(data.toString()))
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)
}
await waitForAccountNonce(address, nonce + 1)
channel.ack(msg)
})
cmd.stdout.on('data', data => console.log(data.toString()))
cmd.stderr.on('data', data => console.error(data.toString()))
} else {
const accountFile = await waitLastAccountEpoch(epoch)
// If new keys with greater epoch already exists
if (accountFile === null) {
channel.ack(msg)
return
}
const to = accountFile.address
const prevEpoch = getPrevEpoch(epoch)
const prevKeysFile = `/keys/keys${prevEpoch}.store`
const { address: from, publicKey } = await getAccountFromFile(prevKeysFile)
console.log(`Tx from ${from}, to ${to}`)
const account = await getAccount(from)
const maxValue = new BN(account.balances.find(x => x.symbol === FOREIGN_ASSET).free).minus(new BN(37500).div(10 ** 8))
console.log(`Building corresponding transaction for transferring all funds, nonce ${account.sequence}, recipient ${to}`)
const tx = new Transaction(from, account.account_number, account.sequence, to, maxValue, FOREIGN_ASSET)
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')) {
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)
}
await waitForAccountNonce(from, account.sequence + 1)
await confirm(`/keys/keys${epoch}.store`)
channel.ack(msg)
})
cmd.stdout.on('data', data => console.log(data.toString()))
cmd.stderr.on('data', data => console.error(data.toString()))
}
})
}
@ -69,9 +123,14 @@ async function connectRabbit (url) {
})
}
async function confirm (keysFile) {
exec.execSync(`curl -X POST -H "Content-Type: application/json" -d @"${keysFile}" "${PROXY_URL}/confirm"`, { stdio: 'pipe' })
}
async function getAccountFromFile (file) {
console.log(`Reading ${file}`)
while (!fs.existsSync(file)) {
console.log('Waiting for needed epoch key')
console.log('Waiting for needed epoch key', file)
await new Promise(resolve => setTimeout(resolve, 1000))
}
const publicKey = JSON.parse(fs.readFileSync(file))[5]
@ -81,7 +140,36 @@ async function getAccountFromFile (file) {
}
}
function getPrevEpoch (epoch) {
return Math.max(0, ...fs.readdirSync('/keys').map(x => parseInt(x.split('.')[0].substr(4))).filter(x => x < epoch))
}
async function waitLastAccountEpoch (epoch) {
while (true) {
const curEpoch = Math.max(0, ...fs.readdirSync('/keys').map(x => parseInt(x.split('.')[0].substr(4))))
if (curEpoch === epoch)
return getAccountFromFile(`/keys/keys${epoch}.store`)
else if (curEpoch > epoch)
return null
else
await new Promise(resolve => setTimeout(resolve, 1000))
}
}
async function waitForAccountNonce (address, nonce) {
console.log(`Waiting for account ${address} to have nonce ${nonce}`)
while (true) {
const sequence = (await getAccount(address)).sequence
if (sequence === nonce)
break
await new Promise(resolve => setTimeout(resolve, 1000))
console.log('Waiting for needed account nonce')
}
console.log('Account nonce is OK')
}
async function getAccount (address) {
console.log(`Getting account ${address} data`)
return httpClient
.get(`/api/v1/account/${address}`)
.then(res => res.data)
@ -97,90 +185,16 @@ async function sendTx (tx) {
.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 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

@ -1,6 +1,6 @@
const TransactionBnc = require('@binance-chain/javascript-sdk/lib/tx').default
const { crypto } = require('@binance-chain/javascript-sdk')
const BN = require('bn.js')
const BN = require('bignumber.js')
const { FOREIGN_CHAIN_ID } = process.env
@ -9,7 +9,7 @@ class Transaction {
const accCode = crypto.decodeAddress(fromAddress)
const toAccCode = crypto.decodeAddress(toAddress)
amount *= 10 ** 8
amount = new BN(amount).multipliedBy(10 ** 8).toNumber()
const coin = {
denom: asset,
@ -31,17 +31,11 @@ class Transaction {
this.signMsg = {
inputs: [{
address: fromAddress,
coins: [{
amount: amount,
denom: asset
}]
coins: [coin]
}],
outputs: [{
address: toAddress,
coins: [{
amount: amount,
denom: asset
}]
coins: [coin]
}]
}
@ -64,9 +58,9 @@ class Transaction {
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))) {
if (s.gt(n.div(2))) {
console.log('Normalizing s')
signature.s = n.sub(s).toString(16)
signature.s = n.minus(s).toString(16)
}
const publicKeyEncoded = Buffer.from('eb5ae98721' + (yLast % 2 ? '03' : '02') + padZeros(publicKey.x, 64), 'hex')
this.tx.signatures = [{

View File

@ -1,20 +0,0 @@
{
"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
}
}

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