Demo of ETH-to-BNC bridge
This commit is contained in:
parent
8b50b4dced
commit
8a528e631c
|
@ -4,8 +4,12 @@ node_modules/
|
||||||
**/keys*.store
|
**/keys*.store
|
||||||
**/signature
|
**/signature
|
||||||
**/params
|
**/params
|
||||||
src/tss/multi-party-ecdsa/
|
|
||||||
data/
|
data/
|
||||||
demo/validator*/
|
demo/validator*/db
|
||||||
|
demo/validator*/keys
|
||||||
|
demo/validator*/queue
|
||||||
demo/ganache_data/
|
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/
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
[submodule "src/deploy/contracts/openzeppelin-solidity"]
|
[submodule "src/deploy/deploy-home/contracts/openzeppelin-solidity"]
|
||||||
path = src/deploy/contracts/openzeppelin-solidity
|
path = src/deploy/deploy-home/contracts/openzeppelin-solidity
|
||||||
url = https://github.com/OpenZeppelin/openzeppelin-solidity.git
|
url = https://github.com/OpenZeppelin/openzeppelin-solidity.git
|
||||||
[submodule "src/deploy-test/contracts/openzeppelin-solidity"]
|
[submodule "src/deploy/deploy-test/contracts/openzeppelin-solidity"]
|
||||||
path = src/deploy-test/contracts/openzeppelin-solidity
|
path = src/deploy/deploy-test/contracts/openzeppelin-solidity
|
||||||
url = https://github.com/OpenZeppelin/openzeppelin-solidity.git
|
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
|
||||||
|
|
|
@ -4,26 +4,44 @@ set -e
|
||||||
|
|
||||||
cd $(dirname "$0")
|
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
|
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"
|
echo "Deploying erc20"
|
||||||
|
|
||||||
cd ../src/deploy-test
|
cd ../src/deploy/deploy-test
|
||||||
|
|
||||||
truffle deploy --network development --reset > /dev/null
|
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
|
truffle deploy --network development --reset > /dev/null
|
||||||
|
|
||||||
|
echo "Deploying side part"
|
||||||
|
|
||||||
|
cd ../deploy-side
|
||||||
|
|
||||||
|
truffle deploy --network development --reset > /dev/null
|
||||||
|
|
||||||
|
echo "Done"
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -78,7 +78,6 @@
|
||||||
"version": "4.63.2",
|
"version": "4.63.2",
|
||||||
"resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport-node-hid/-/hw-transport-node-hid-4.63.2.tgz",
|
"resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport-node-hid/-/hw-transport-node-hid-4.63.2.tgz",
|
||||||
"integrity": "sha512-8sMI7KTXQk6UIVrlSfli7KcqZV+sy/BKZ4/RAzqLY1JVrV5qYt6UGPifZD9T99kJut8EgfdV0HETjhdUAy8EgA==",
|
"integrity": "sha512-8sMI7KTXQk6UIVrlSfli7KcqZV+sy/BKZ4/RAzqLY1JVrV5qYt6UGPifZD9T99kJut8EgfdV0HETjhdUAy8EgA==",
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"@ledgerhq/devices": "^4.63.2",
|
"@ledgerhq/devices": "^4.63.2",
|
||||||
"@ledgerhq/errors": "^4.63.2",
|
"@ledgerhq/errors": "^4.63.2",
|
||||||
|
@ -94,7 +93,6 @@
|
||||||
"version": "4.63.2",
|
"version": "4.63.2",
|
||||||
"resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport-node-hid-noevents/-/hw-transport-node-hid-noevents-4.63.2.tgz",
|
"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==",
|
"integrity": "sha512-KKq3QE3/CBZkO2FpW5SMI9UJ/L3rhFwA3l1N/AQhBuzqIEdXhr/eCoxEcT9GC2Xy4TPwguTiDDcZMBwLuPOafQ==",
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"@ledgerhq/devices": "^4.63.2",
|
"@ledgerhq/devices": "^4.63.2",
|
||||||
"@ledgerhq/errors": "^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",
|
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
|
||||||
"integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ=="
|
"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": {
|
"bindings": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||||
|
@ -3338,7 +3341,6 @@
|
||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/usb/-/usb-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/usb/-/usb-1.6.0.tgz",
|
||||||
"integrity": "sha512-52DyWlCk9K+iw3LnvY95WXSnpHjxJoI++aGkV8HiMNPc4zmvDQlYvWAzrkbJ2JH3oUcx26XfU5sZcG4RAcVkMg==",
|
"integrity": "sha512-52DyWlCk9K+iw3LnvY95WXSnpHjxJoI++aGkV8HiMNPc4zmvDQlYvWAzrkbJ2JH3oUcx26XfU5sZcG4RAcVkMg==",
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"bindings": "^1.4.0",
|
"bindings": "^1.4.0",
|
||||||
"nan": "2.13.2",
|
"nan": "2.13.2",
|
||||||
|
@ -3348,8 +3350,7 @@
|
||||||
"nan": {
|
"nan": {
|
||||||
"version": "2.13.2",
|
"version": "2.13.2",
|
||||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz",
|
"resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz",
|
||||||
"integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==",
|
"integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw=="
|
||||||
"optional": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
RPC_URL=https://sokol.poa.network
|
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=e49fe947f224ae8e126c41b1be3e52be701509c2366e835ea8c08651f91030e0
|
||||||
PRIVATE_KEY_DEV=e2aeb24eaa63102d0c0821717c3b6384abdabd7af2ad4ec8e650dce300798b27
|
PRIVATE_KEY_DEV=e2aeb24eaa63102d0c0821717c3b6384abdabd7af2ad4ec8e650dce300798b27
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
require('dotenv').config()
|
require('dotenv').config()
|
||||||
|
|
||||||
const SharedDB = artifacts.require('SharedDB')
|
const Bridge = artifacts.require('Bridge')
|
||||||
|
|
||||||
const {
|
const {
|
||||||
THRESHOLD, PARTIES, VALIDATOR_ADDRESS_1, VALIDATOR_ADDRESS_2, VALIDATOR_ADDRESS_3, VALIDATOR_ADDRESS_4,
|
THRESHOLD, PARTIES, VALIDATOR_ADDRESS_1, VALIDATOR_ADDRESS_2, VALIDATOR_ADDRESS_3, VALIDATOR_ADDRESS_4,
|
||||||
|
@ -9,7 +9,7 @@ const {
|
||||||
|
|
||||||
module.exports = deployer => {
|
module.exports = deployer => {
|
||||||
deployer.deploy(
|
deployer.deploy(
|
||||||
SharedDB,
|
Bridge,
|
||||||
THRESHOLD,
|
THRESHOLD,
|
||||||
PARTIES,
|
PARTIES,
|
||||||
[
|
[
|
|
@ -8,7 +8,7 @@ module.exports = {
|
||||||
networks: {
|
networks: {
|
||||||
development: {
|
development: {
|
||||||
provider: new PrivateKeyProvider(PRIVATE_KEY_DEV, RPC_URL_DEV),
|
provider: new PrivateKeyProvider(PRIVATE_KEY_DEV, RPC_URL_DEV),
|
||||||
network_id: '33'
|
network_id: '44'
|
||||||
},
|
},
|
||||||
staging: {
|
staging: {
|
||||||
provider: new PrivateKeyProvider(PRIVATE_KEY, RPC_URL),
|
provider: new PrivateKeyProvider(PRIVATE_KEY, RPC_URL),
|
|
@ -1,7 +1,5 @@
|
||||||
RPC_URL=https://sokol.poa.network
|
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=e49fe947f224ae8e126c41b1be3e52be701509c2366e835ea8c08651f91030e0
|
||||||
PRIVATE_KEY_DEV=e2aeb24eaa63102d0c0821717c3b6384abdabd7af2ad4ec8e650dce300798b27
|
PRIVATE_KEY_DEV=e2aeb24eaa63102d0c0821717c3b6384abdabd7af2ad4ec8e650dce300798b27
|
||||||
|
|
||||||
SHARED_DB_ADDRESS=0xED3B25004A77de5dE38850a7D148315537C15572
|
|
|
@ -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))];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
const SharedDB = artifacts.require('SharedDB')
|
||||||
|
|
||||||
|
module.exports = deployer => {
|
||||||
|
deployer.deploy(SharedDB)
|
||||||
|
}
|
|
@ -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
|
|
@ -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,5 @@
|
||||||
|
const Migrations = artifacts.require('Migrations')
|
||||||
|
|
||||||
|
module.exports = deployer => {
|
||||||
|
deployer.deploy(Migrations)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,12 @@
|
||||||
THRESHOLD=1
|
|
||||||
PARTIES=3
|
|
||||||
|
|
||||||
KEY_FILE=keys.store
|
|
||||||
|
|
||||||
#RPC_URL=https://sokol.poa.network
|
#RPC_URL=https://sokol.poa.network
|
||||||
RPC_URL=http://host.docker.internal:7545
|
HOME_RPC_URL=http://127.0.0.1:4444
|
||||||
RPC_URL_DEV=http://127.0.0.1:7545
|
SIDE_RPC_URL=http://127.0.0.1:3333
|
||||||
|
HOME_CHAIN_ID=44
|
||||||
|
SIDE_CHAIN_ID=33
|
||||||
|
|
||||||
SHARED_DB_ADDRESS=0x94b40CC641Ed7db241A1f04C8896ba6f6cC36b85
|
HOME_BRIDGE_ADDRESS=0x94b40CC641Ed7db241A1f04C8896ba6f6cC36b85
|
||||||
TOKEN_ADDRESS=0x44c158FE850821ae69DaF37AADF5c539e9d0025B
|
HOME_TOKEN_ADDRESS=0x44c158FE850821ae69DaF37AADF5c539e9d0025B
|
||||||
|
SIDE_SHARED_DB_ADDRESS=0x44c158FE850821ae69DaF37AADF5c539e9d0025B
|
||||||
|
|
||||||
#VALIDATOR_PRIVATE_KEY=d1e7b8ff274e517e1a332f2bc0ac051e30db196ba31c68c2efcd022e8ec358f1
|
#VALIDATOR_PRIVATE_KEY=d1e7b8ff274e517e1a332f2bc0ac051e30db196ba31c68c2efcd022e8ec358f1
|
||||||
VALIDATOR_PRIVATE_KEY=2be3f252e16541bf1bb2d4a517d2bf173e6d09f2d765d32c64dc50515aec63ea
|
VALIDATOR_PRIVATE_KEY=2be3f252e16541bf1bb2d4a517d2bf173e6d09f2d765d32c64dc50515aec63ea
|
||||||
|
@ -17,9 +15,9 @@ VALIDATOR_PRIVATE_KEY=2be3f252e16541bf1bb2d4a517d2bf173e6d09f2d765d32c64dc50515a
|
||||||
|
|
||||||
LOCAL=true
|
LOCAL=true
|
||||||
|
|
||||||
#MESSAGE=fb2487601395e1cabad725e2e006ae16dd134a536ea1e30919b86e7aa572584d
|
|
||||||
|
|
||||||
DEPLOY_PRIVATE_KEY=e2aeb24eaa63102d0c0821717c3b6384abdabd7af2ad4ec8e650dce300798b27
|
DEPLOY_PRIVATE_KEY=e2aeb24eaa63102d0c0821717c3b6384abdabd7af2ad4ec8e650dce300798b27
|
||||||
|
|
||||||
FOREIGN_URL=https://testnet-dex.binance.org/
|
FOREIGN_URL=https://testnet-dex.binance.org/
|
||||||
FOREIGN_CHAIN_ID=Binance-Chain-Nile
|
FOREIGN_CHAIN_ID=Binance-Chain-Nile
|
||||||
|
|
||||||
|
VOTES_PROXY_PORT=5000
|
||||||
|
|
|
@ -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"]
|
|
@ -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()
|
||||||
|
}
|
||||||
|
})
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,14 +4,23 @@ services:
|
||||||
image: blockchain-proxy
|
image: blockchain-proxy
|
||||||
build: ./proxy
|
build: ./proxy
|
||||||
environment:
|
environment:
|
||||||
- RPC_URL
|
- HOME_RPC_URL
|
||||||
- SHARED_DB_ADDRESS
|
- HOME_BRIDGE_ADDRESS
|
||||||
|
- HOME_CHAIN_ID
|
||||||
|
- SIDE_RPC_URL
|
||||||
|
- SIDE_SHARED_DB_ADDRESS
|
||||||
|
- SIDE_CHAIN_ID
|
||||||
- VALIDATOR_PRIVATE_KEY
|
- VALIDATOR_PRIVATE_KEY
|
||||||
volumes:
|
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:
|
networks:
|
||||||
- sign-proxy-net
|
- test_network
|
||||||
- keygen-proxy-net
|
# - sign-proxy-net
|
||||||
|
# - keygen-proxy-net
|
||||||
|
# - bncwatcher-proxy-net
|
||||||
keygen:
|
keygen:
|
||||||
image: keygen-client
|
image: keygen-client
|
||||||
build: ./tss-keygen
|
build: ./tss-keygen
|
||||||
|
@ -21,9 +30,10 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- '${PWD}/keys:/keys'
|
- '${PWD}/keys:/keys'
|
||||||
networks:
|
networks:
|
||||||
- keygen-proxy-net
|
- test_network
|
||||||
- rabbit-keygen-net
|
# - keygen-proxy-net
|
||||||
- redis-keygen-net
|
# - rabbit-keygen-net
|
||||||
|
# - redis-keygen-net
|
||||||
signer:
|
signer:
|
||||||
image: sign-client
|
image: sign-client
|
||||||
build: ./tss-sign
|
build: ./tss-sign
|
||||||
|
@ -35,44 +45,68 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- '${PWD}/keys:/keys'
|
- '${PWD}/keys:/keys'
|
||||||
networks:
|
networks:
|
||||||
- sign-proxy-net
|
- test_network
|
||||||
- rabbit-signer-net
|
# - sign-proxy-net
|
||||||
- redis-signer-net
|
# - rabbit-signer-net
|
||||||
|
# - redis-signer-net
|
||||||
redis:
|
redis:
|
||||||
image: redis:5.0.5-alpine
|
image: redis:5.0.5-alpine
|
||||||
volumes:
|
volumes:
|
||||||
- '${PWD}/db:/data'
|
- '${PWD}/db:/data'
|
||||||
networks:
|
networks:
|
||||||
- redis-signer-net
|
- test_network
|
||||||
- redis-keygen-net
|
# - redis-signer-net
|
||||||
- redis-watcher-net
|
# - redis-keygen-net
|
||||||
|
# - redis-ethwatcher-net
|
||||||
|
# - redis-bncwatcher-net
|
||||||
rabbitmq:
|
rabbitmq:
|
||||||
hostname: rabbit
|
hostname: rabbit
|
||||||
image: rabbitmq:3.7.15-alpine
|
image: rabbitmq:3.7.15-alpine
|
||||||
volumes:
|
volumes:
|
||||||
- '${PWD}/queue:/var/lib/rabbitmq/mnesia'
|
- '${PWD}/queue:/var/lib/rabbitmq/mnesia'
|
||||||
networks:
|
networks:
|
||||||
- rabbit-signer-net
|
- test_network
|
||||||
- rabbit-keygen-net
|
# - rabbit-signer-net
|
||||||
- rabbit-watcher-net
|
# - rabbit-keygen-net
|
||||||
|
# - rabbit-ethwatcher-net
|
||||||
|
# - rabbit-bncwatcher-net
|
||||||
eth-watcher:
|
eth-watcher:
|
||||||
build: ./watcher
|
build: ethWatcher
|
||||||
image: eth-watcher
|
image: eth-watcher
|
||||||
environment:
|
environment:
|
||||||
- 'HOME_RPC_URL=${RPC_URL}'
|
- HOME_RPC_URL
|
||||||
- 'HOME_BRIDGE_ADDRESS=${SHARED_DB_ADDRESS}'
|
- HOME_BRIDGE_ADDRESS
|
||||||
- 'RABBITMQ_URL=amqp://rabbitmq:5672'
|
- 'RABBITMQ_URL=amqp://rabbitmq:5672'
|
||||||
volumes:
|
volumes:
|
||||||
- '../deploy/build/contracts:/watcher/contracts_data'
|
- '../deploy/deploy-home/build/contracts/Bridge.json:/watcher/contracts_data/Bridge.json'
|
||||||
networks:
|
networks:
|
||||||
- rabbit-watcher-net
|
- test_network
|
||||||
- redis-watcher-net
|
# - 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:
|
networks:
|
||||||
sign-proxy-net:
|
test_network:
|
||||||
keygen-proxy-net:
|
# sign-proxy-net:
|
||||||
rabbit-signer-net:
|
# keygen-proxy-net:
|
||||||
rabbit-keygen-net:
|
# rabbit-signer-net:
|
||||||
rabbit-watcher-net:
|
# rabbit-keygen-net:
|
||||||
redis-keygen-net:
|
# rabbit-ethwatcher-net:
|
||||||
redis-signer-net:
|
# rabbit-bncwatcher-net:
|
||||||
redis-watcher-net:
|
# redis-keygen-net:
|
||||||
|
# redis-signer-net:
|
||||||
|
# redis-ethwatcher-net:
|
||||||
|
# redis-bncwatcher-net:
|
||||||
|
# bncwatcher-proxy-net:
|
||||||
|
|
|
@ -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
|
|
@ -1,7 +1,7 @@
|
||||||
const amqp = require('amqplib')
|
const amqp = require('amqplib')
|
||||||
const Web3 = require('web3')
|
const Web3 = require('web3')
|
||||||
const redis = require('./db')
|
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
|
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 () {
|
async function main () {
|
||||||
console.log(`Watching events in block #${blockNumber}`)
|
console.log(`Watching events in block #${blockNumber}`)
|
||||||
if (await web3Home.eth.getBlock(blockNumber) === null) {
|
if (await web3Home.eth.getBlock(blockNumber) === null) {
|
||||||
|
console.log('No block')
|
||||||
await new Promise(r => setTimeout(r, 1000))
|
await new Promise(r => setTimeout(r, 1000))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -79,9 +78,10 @@ async function main () {
|
||||||
console.log('Sent new epoch event')
|
console.log('Sent new epoch event')
|
||||||
|
|
||||||
if (oldEpoch > 0) {
|
if (oldEpoch > 0) {
|
||||||
|
// Transfer all assets to new account tss account
|
||||||
channel.sendToQueue(signQueue.queue, Buffer.from(JSON.stringify({
|
channel.sendToQueue(signQueue.queue, Buffer.from(JSON.stringify({
|
||||||
epoch: oldEpoch,
|
epoch: newEpoch,
|
||||||
nonce: foreignNonce[oldEpoch],
|
//nonce: foreignNonce[oldEpoch],
|
||||||
})), {
|
})), {
|
||||||
persistent: true
|
persistent: true
|
||||||
})
|
})
|
|
@ -1,17 +1,21 @@
|
||||||
const express = require('express')
|
const express = require('express')
|
||||||
const Web3 = require('web3')
|
const Web3 = require('web3')
|
||||||
const fs = require('fs')
|
const AsyncLock = require('async-lock')
|
||||||
const BN = require('bignumber.js')
|
|
||||||
const ethers = require('ethers')
|
|
||||||
|
|
||||||
const { RPC_URL, SHARED_DB_ADDRESS, VALIDATOR_PRIVATE_KEY } = 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 } = process.env
|
||||||
const abi = require('./contracts_data/SharedDB.json').abi
|
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 homeWeb3 = new Web3(HOME_RPC_URL, null, { transactionConfirmationBlocks: 1 })
|
||||||
const contract = new web3.eth.Contract(abi, SHARED_DB_ADDRESS)
|
const sideWeb3 = new Web3(SIDE_RPC_URL, null, { transactionConfirmationBlocks: 1 })
|
||||||
const validatorAddress = web3.eth.accounts.privateKeyToAccount(`0x${VALIDATOR_PRIVATE_KEY}`).address
|
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()
|
const app = express()
|
||||||
app.use(express.json())
|
app.use(express.json())
|
||||||
|
@ -22,23 +26,35 @@ app.post('/set', set)
|
||||||
app.post('/signupkeygen', signupKeygen)
|
app.post('/signupkeygen', signupKeygen)
|
||||||
app.post('/signupsign', signupSign)
|
app.post('/signupsign', signupSign)
|
||||||
|
|
||||||
app.get('/params', params)
|
app.get('/current_params', currentParams)
|
||||||
|
app.get('/next_params', nextParams)
|
||||||
app.post('/confirm', confirm)
|
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 () {
|
async function main () {
|
||||||
validatorNonce = await web3.eth.getTransactionCount(validatorAddress)
|
homeValidatorNonce = await homeWeb3.eth.getTransactionCount(validatorAddress)
|
||||||
|
sideValidatorNonce = await sideWeb3.eth.getTransactionCount(validatorAddress)
|
||||||
try {
|
|
||||||
fs.mkdirSync('/generated_data')
|
|
||||||
} catch (e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
app.listen(8001, () => {
|
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) {
|
function Ok (data) {
|
||||||
return { Ok: data }
|
return { Ok: data }
|
||||||
}
|
}
|
||||||
|
@ -49,16 +65,23 @@ function Err (data) {
|
||||||
|
|
||||||
async function get (req, res) {
|
async function get (req, res) {
|
||||||
console.log('Get call')
|
console.log('Get call')
|
||||||
|
const round = req.body.key.second
|
||||||
const uuid = req.body.key.third
|
const uuid = req.body.key.third
|
||||||
const from = parseInt(req.body.key.first)
|
let from
|
||||||
const to = Number(req.body.key.fourth)
|
if (uuid.startsWith('k'))
|
||||||
const key = web3.utils.sha3(`${req.body.key.second}_${to}`)
|
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')
|
const data = await (uuid.startsWith('k')
|
||||||
? contract.methods.getKeygenData(from, key).call()
|
? sharedDb.methods.getKeygenData(from, key).call()
|
||||||
: contract.methods.getSignData(from, uuid, key).call())
|
: sharedDb.methods.getSignData(from, uuid, key).call())
|
||||||
|
|
||||||
const result = web3.utils.hexToUtf8(data)
|
const result = homeWeb3.utils.hexToUtf8(data)
|
||||||
if (result.length)
|
if (result.length)
|
||||||
res.send(Ok({ key: req.body.key, value: result }))
|
res.send(Ok({ key: req.body.key, value: result }))
|
||||||
else {
|
else {
|
||||||
|
@ -70,14 +93,15 @@ async function get (req, res) {
|
||||||
|
|
||||||
async function set (req, res) {
|
async function set (req, res) {
|
||||||
console.log('Set call')
|
console.log('Set call')
|
||||||
|
const round = req.body.key.second
|
||||||
const uuid = req.body.key.third
|
const uuid = req.body.key.third
|
||||||
const to = Number(req.body.key.fourth)
|
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))
|
const query = uuid.startsWith('k')
|
||||||
: contract.methods.setSignData(uuid, key, web3.utils.utf8ToHex(req.body.value))
|
? sharedDb.methods.setKeygenData(key, sideWeb3.utils.utf8ToHex(req.body.value))
|
||||||
await sendQuery(query)
|
: sharedDb.methods.setSignData(uuid, key, sideWeb3.utils.utf8ToHex(req.body.value))
|
||||||
fs.writeFileSync(`/generated_data/${req.body.key.first}_${req.body.key.second}_${req.body.key.third}_${req.body.key.fourth}.json`, req.body.value)
|
await sideSendQuery(query)
|
||||||
|
|
||||||
res.send(Ok(null))
|
res.send(Ok(null))
|
||||||
console.log('Set end')
|
console.log('Set end')
|
||||||
|
@ -85,71 +109,168 @@ async function set (req, res) {
|
||||||
|
|
||||||
async function signupKeygen (req, res) {
|
async function signupKeygen (req, res) {
|
||||||
console.log('SignupKeygen call')
|
console.log('SignupKeygen call')
|
||||||
const epoch = (await contract.methods.epoch().call()).toNumber()
|
const epoch = (await bridge.methods.epoch().call()).toNumber()
|
||||||
const partyId = (await contract.methods.getPartyId().call({ from: validatorAddress })).toNumber()
|
const partyId = (await bridge.methods.getNextPartyId(validatorAddress).call()).toNumber()
|
||||||
|
|
||||||
res.send(Ok({ uuid: `k${epoch}`, number: partyId }))
|
if (partyId === 0) {
|
||||||
console.log('SignupKeygen end')
|
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) {
|
async function signupSign (req, res) {
|
||||||
console.log('SignupSign call')
|
console.log('SignupSign call')
|
||||||
console.log(req.body.third)
|
const hash = sideWeb3.utils.sha3(`0x${req.body.third}`)
|
||||||
const hash = web3.utils.sha3(`0x${req.body.third}`)
|
const query = sharedDb.methods.signupSign(hash)
|
||||||
const query = contract.methods.signupSign(hash)
|
await sideSendQuery(query)
|
||||||
const receipt = await sendQuery(query)
|
|
||||||
|
|
||||||
while (true) {
|
const validators = await bridge.methods.getValidatorsArray().call()
|
||||||
const events = await contract.getPastEvents('Signup', {
|
const threshold = await bridge.methods.threshold().call()
|
||||||
filter: { from: validatorAddress, hash },
|
const id = (await sharedDb.methods.getSignupNumber(hash, validators, validatorAddress).call()).toNumber()
|
||||||
fromBlock: receipt.blockNumber,
|
|
||||||
toBlock: receipt.blockNumber
|
|
||||||
})
|
|
||||||
const event = events[0]
|
|
||||||
|
|
||||||
if (event) {
|
if (id > threshold + 1) {
|
||||||
res.send(Ok({ uuid: hash, number: event.returnValues.partyId.toNumber() }))
|
res.send(Err({}))
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
res.send(Ok({ uuid: hash, number: id }))
|
||||||
console.log('SignupSign end')
|
console.log('SignupSign end')
|
||||||
}
|
}
|
||||||
|
|
||||||
async function confirm (req, res) {
|
async function confirm (req, res) {
|
||||||
console.log('Confirm call')
|
console.log('Confirm call')
|
||||||
const { x, y } = req.body[5]
|
const { x, y } = req.body[5]
|
||||||
const query = contract.methods.confirm(`0x${x}`, `0x${y}`)
|
const query = bridge.methods.confirm(`0x${x}`, `0x${y}`)
|
||||||
await sendQuery(query)
|
await homeSendQuery(query)
|
||||||
//const addr = `0x${web3.utils.sha3(`0x${x}${y}`).substring(26)}`
|
|
||||||
//console.log(addr)
|
|
||||||
res.send()
|
res.send()
|
||||||
console.log('Confirm end')
|
console.log('Confirm end')
|
||||||
}
|
}
|
||||||
|
|
||||||
async function params (req, res) {
|
async function currentParams (req, res) {
|
||||||
console.log('Params call')
|
console.log('Current params call')
|
||||||
const epoch = parseInt(req.query.epoch)
|
const parties = (await bridge.methods.parties().call()).toNumber().toString()
|
||||||
const parties = (await contract.methods.parties(epoch).call()).toNumber().toString()
|
const threshold = (await bridge.methods.threshold().call()).toNumber().toString()
|
||||||
const threshold = (await contract.methods.threshold(epoch).call()).toNumber().toString()
|
|
||||||
res.send({ parties, threshold })
|
res.send({ parties, threshold })
|
||||||
console.log('Params end')
|
console.log('Current params end')
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendQuery (query) {
|
async function nextParams (req, res) {
|
||||||
const encodedABI = query.encodeABI()
|
console.log('Next params call')
|
||||||
const tx = {
|
const parties = (await bridge.methods.nextParties().call()).toNumber().toString()
|
||||||
data: encodedABI,
|
const threshold = (await bridge.methods.nextThreshold().call()).toNumber().toString()
|
||||||
from: validatorAddress,
|
res.send({ parties, threshold })
|
||||||
to: SHARED_DB_ADDRESS,
|
console.log('Next params end')
|
||||||
nonce: validatorNonce++,
|
}
|
||||||
chainId: 33
|
|
||||||
|
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)
|
res.send('Voted')
|
||||||
const signedTx = await web3.eth.accounts.signTransaction(tx, VALIDATOR_PRIVATE_KEY)
|
console.log('Voted successfully')
|
||||||
|
|
||||||
const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction)
|
|
||||||
|
|
||||||
return receipt
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,7 @@
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"web3": "1.0.0-beta.55",
|
"web3": "1.0.0-beta.55",
|
||||||
"dotenv": "8.0.0",
|
|
||||||
"express": "4.17.1",
|
"express": "4.17.1",
|
||||||
"bignumber.js": "9.0.0",
|
"async-lock": "1.2.0"
|
||||||
"ethers": "4.0.31"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,14 @@ require('dotenv').config()
|
||||||
|
|
||||||
const Web3 = require('web3')
|
const Web3 = require('web3')
|
||||||
|
|
||||||
const { RPC_URL_DEV, SHARED_DB_ADDRESS, DEPLOY_PRIVATE_KEY, TOKEN_ADDRESS } = process.env
|
const { HOME_RPC_URL, HOME_BRIDGE_ADDRESS, HOME_CHAIN_ID, DEPLOY_PRIVATE_KEY, HOME_TOKEN_ADDRESS } = process.env
|
||||||
const web3 = new Web3(RPC_URL_DEV, null, { transactionConfirmationBlocks: 1 })
|
const web3 = new Web3(HOME_RPC_URL, null, { transactionConfirmationBlocks: 1 })
|
||||||
const abiBridge = require('../deploy/build/contracts/SharedDB').abi
|
const abiBridge = require('../deploy/deploy-home/build/contracts/SharedDB').abi
|
||||||
const abiToken = require('../deploy/build/contracts/IERC20').abi
|
const abiToken = require('../deploy/deploy-home/build/contracts/IERC20').abi
|
||||||
const bridge = new web3.eth.Contract(abiBridge, SHARED_DB_ADDRESS)
|
const bridge = new web3.eth.Contract(abiBridge, HOME_BRIDGE_ADDRESS)
|
||||||
const token = new web3.eth.Contract(abiToken, TOKEN_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')
|
const query2 = bridge.methods.requestAffirmation(1, 'tbnb1h3nmmqukrtjc0prmtdts0kxlgmw8rend4zfasn')
|
||||||
|
|
||||||
let nonce
|
let nonce
|
||||||
|
@ -18,8 +18,8 @@ const deployAddress = web3.eth.accounts.privateKeyToAccount(`0x${DEPLOY_PRIVATE_
|
||||||
async function main () {
|
async function main () {
|
||||||
console.log(deployAddress)
|
console.log(deployAddress)
|
||||||
nonce = await web3.eth.getTransactionCount(deployAddress)
|
nonce = await web3.eth.getTransactionCount(deployAddress)
|
||||||
await sendQuery(query1, TOKEN_ADDRESS)
|
await sendQuery(query1, HOME_TOKEN_ADDRESS)
|
||||||
await sendQuery(query2, SHARED_DB_ADDRESS)
|
await sendQuery(query2, HOME_BRIDGE_ADDRESS)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendQuery (query, to) {
|
async function sendQuery (query, to) {
|
||||||
|
@ -29,14 +29,12 @@ async function sendQuery (query, to) {
|
||||||
from: deployAddress,
|
from: deployAddress,
|
||||||
to,
|
to,
|
||||||
nonce: nonce++,
|
nonce: nonce++,
|
||||||
chainId: 33
|
chainId: parseInt(HOME_CHAIN_ID)
|
||||||
}
|
}
|
||||||
tx.gas = Math.min(Math.ceil(await query.estimateGas(tx) * 1.5), 6721975)
|
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 signedTx = await web3.eth.accounts.signTransaction(tx, DEPLOY_PRIVATE_KEY)
|
||||||
|
|
||||||
const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction)
|
return await web3.eth.sendSignedTransaction(signedTx.rawTransaction)
|
||||||
|
|
||||||
return receipt
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -1,17 +1,22 @@
|
||||||
|
require('dotenv').config()
|
||||||
|
|
||||||
const Bnc = require('@binance-chain/javascript-sdk')
|
const Bnc = require('@binance-chain/javascript-sdk')
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const Transaction = require('../oracle/tss-sign/tx')
|
const Transaction = require('./tss-sign/tx')
|
||||||
const crypto = require('crypto')
|
const crypto = require('crypto')
|
||||||
const ecc = require('tiny-secp256k1')
|
const ecc = require('tiny-secp256k1')
|
||||||
|
|
||||||
const privKey = 'b92a59209e28149e5cee8e54dfceb80a08ea08e654261bdb9d264b15dee2525c'
|
const privKey = 'b92a59209e28149e5cee8e54dfceb80a08ea08e654261bdb9d264b15dee2525c'
|
||||||
const asset = 'BNB'
|
const asset = 'BNB'
|
||||||
const amount = 2.5
|
const amount = '2.5'
|
||||||
const addressTo = process.argv[2]
|
const addressTo = process.argv[2]
|
||||||
const addressFrom = Bnc.crypto.getAddressFromPrivateKey(privKey)
|
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 api = 'https://testnet-dex.binance.org/'
|
||||||
|
const publicKey = {
|
||||||
|
x: 'b32b5ea8698156239ea7092ef8a44a4b711ea29525da34a8233bdc0dd3af7f1a',
|
||||||
|
y: '6b5b77f2e925f93cae7fc894ff50bafcb7b6e6e96e339c96e41663ccaf0a4d68'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const httpClient = axios.create({ baseURL: api })
|
const httpClient = axios.create({ baseURL: api })
|
||||||
|
@ -19,20 +24,14 @@ httpClient
|
||||||
.get(`/api/v1/account/${addressFrom}`)
|
.get(`/api/v1/account/${addressFrom}`)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
const { sequence } = res.data
|
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')
|
const hash = crypto.createHash('sha256').update(tx.getSignBytes()).digest('hex')
|
||||||
console.log(tx.getSignBytes().toString('hex'))
|
const signature = ecc.sign(Buffer.from(hash, 'hex'), Buffer.from(privKey, 'hex'))
|
||||||
console.log(hash)
|
|
||||||
const signature = ecc.sign(Buffer.from(hash, 'hex'), Buffer.from('b92a59209e28149e5cee8e54dfceb80a08ea08e654261bdb9d264b15dee2525c', 'hex'))
|
|
||||||
const sig = {
|
const sig = {
|
||||||
r: signature.toString('hex').substr(0, 64),
|
r: signature.toString('hex').substr(0, 64),
|
||||||
s: signature.toString('hex').substr(64, 64)
|
s: signature.toString('hex').substr(64, 64)
|
||||||
}
|
}
|
||||||
console.log(sig)
|
|
||||||
const publicKey = {
|
|
||||||
x: 'b32b5ea8698156239ea7092ef8a44a4b711ea29525da34a8233bdc0dd3af7f1a',
|
|
||||||
y: '6b5b77f2e925f93cae7fc894ff50bafcb7b6e6e96e339c96e41663ccaf0a4d68'
|
|
||||||
}
|
|
||||||
return tx.addSignature(publicKey, sig)
|
return tx.addSignature(publicKey, sig)
|
||||||
})
|
})
|
||||||
.then(signed => {
|
.then(signed => {
|
||||||
|
|
|
@ -8,9 +8,9 @@ until curl "$1" > /dev/null 2>&1; do
|
||||||
sleep 1;
|
sleep 1;
|
||||||
done
|
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"
|
echo "Generating key using server $1"
|
||||||
|
|
||||||
|
@ -18,6 +18,6 @@ echo "Generating key using server $1"
|
||||||
|
|
||||||
echo "Generated keys for all parties"
|
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
|
||||||
|
|
|
@ -10,21 +10,43 @@ async function main () {
|
||||||
console.log('Connecting to RabbitMQ server')
|
console.log('Connecting to RabbitMQ server')
|
||||||
const connection = await connectRabbit(RABBITMQ_URL)
|
const connection = await connectRabbit(RABBITMQ_URL)
|
||||||
console.log('Connecting to epoch events queue')
|
console.log('Connecting to epoch events queue')
|
||||||
const channel = await connection.createConfirmChannel()
|
const channel = await connection.createChannel()
|
||||||
const queue = await channel.assertQueue('epochQueue')
|
const queue = await channel.assertQueue('epochQueue')
|
||||||
|
|
||||||
channel.prefetch(1)
|
let prev
|
||||||
|
let cmd
|
||||||
|
|
||||||
|
channel.prefetch(2)
|
||||||
channel.consume(queue.queue, msg => {
|
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)
|
const data = JSON.parse(msg.content)
|
||||||
console.log(`Consumed new epoch event, starting keygen for epoch ${data.epoch}`)
|
console.log(`Consumed new epoch event, starting keygen for epoch ${data.epoch}`)
|
||||||
|
|
||||||
const keysFile = `/keys/keys${data.epoch}.store`
|
const keysFile = `/keys/keys${data.epoch}.store`
|
||||||
|
|
||||||
console.log('Running ./keygen-entrypoint.sh')
|
console.log('Running ./keygen-entrypoint.sh')
|
||||||
const cmd = exec.execFile('./keygen-entrypoint.sh', [PROXY_URL, keysFile, data.epoch], () => {
|
cmd = exec.execFile('./keygen-entrypoint.sh', [PROXY_URL, keysFile], async () => {
|
||||||
console.log('Finished keygen')
|
cmd = null
|
||||||
const publicKey = JSON.parse(fs.readFileSync(keysFile).toString())[5]
|
if (fs.existsSync(keysFile)) {
|
||||||
console.log(`Generated multisig account in binance chain: ${publicKeyToAddress(publicKey)}`)
|
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)
|
channel.ack(msg)
|
||||||
})
|
})
|
||||||
cmd.stdout.on('data', data => console.log(data.toString()))
|
cmd.stdout.on('data', data => console.log(data.toString()))
|
||||||
|
@ -43,10 +65,20 @@ async function connectRabbit (url) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function publicKeyToAddress({x, y}) {
|
async function confirm (keysFile) {
|
||||||
const compact = (parseInt(y[63], 16) % 2 ? '03' : '02') + x
|
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 sha256Hash = crypto.createHash('sha256').update(Buffer.from(compact, 'hex')).digest('hex')
|
||||||
const hash = crypto.createHash('ripemd160').update(Buffer.from(sha256Hash, 'hex')).digest('hex')
|
const hash = crypto.createHash('ripemd160').update(Buffer.from(sha256Hash, 'hex')).digest('hex')
|
||||||
const words = bech32.toWords(Buffer.from(hash, 'hex'))
|
const words = bech32.toWords(Buffer.from(hash, 'hex'))
|
||||||
return bech32.encode('tbnb', words)
|
return bech32.encode('tbnb', words)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function padZeros (s, len) {
|
||||||
|
while (s.length < len)
|
||||||
|
s = '0' + s
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@binance-chain/javascript-sdk": "2.13.9",
|
"@binance-chain/javascript-sdk": "2.13.9",
|
||||||
"amqplib": "0.5.3",
|
"amqplib": "0.5.3",
|
||||||
"axios": "0.19.0"
|
"axios": "0.19.0",
|
||||||
|
"bignumber.js": "9.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,10 +10,10 @@ done
|
||||||
|
|
||||||
echo "Fetching current tss params"
|
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"
|
echo "Signing message using server $1"
|
||||||
|
|
||||||
./gg18_sign_client "$1" "$2" "$4"
|
./gg18_sign_client "$1" "$2" "$3"
|
||||||
|
|
||||||
echo "Signed message"
|
echo "Signed message"
|
||||||
|
|
|
@ -3,8 +3,10 @@ const fs = require('fs')
|
||||||
const amqp = require('amqplib')
|
const amqp = require('amqplib')
|
||||||
const crypto = require('crypto')
|
const crypto = require('crypto')
|
||||||
const bech32 = require('bech32')
|
const bech32 = require('bech32')
|
||||||
|
const BN = require('bignumber.js')
|
||||||
|
|
||||||
const { RABBITMQ_URL, FOREIGN_URL, PROXY_URL } = process.env
|
const { RABBITMQ_URL, FOREIGN_URL, PROXY_URL } = process.env
|
||||||
|
const FOREIGN_ASSET = 'BNB'
|
||||||
const Transaction = require('./tx')
|
const Transaction = require('./tx')
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
|
|
||||||
|
@ -14,7 +16,7 @@ async function main () {
|
||||||
console.log('Connecting to RabbitMQ server')
|
console.log('Connecting to RabbitMQ server')
|
||||||
const connection = await connectRabbit(RABBITMQ_URL)
|
const connection = await connectRabbit(RABBITMQ_URL)
|
||||||
console.log('Connecting to signature events queue')
|
console.log('Connecting to signature events queue')
|
||||||
const channel = await connection.createConfirmChannel()
|
const channel = await connection.createChannel()
|
||||||
const queue = await channel.assertQueue('signQueue')
|
const queue = await channel.assertQueue('signQueue')
|
||||||
|
|
||||||
channel.prefetch(1)
|
channel.prefetch(1)
|
||||||
|
@ -24,36 +26,88 @@ async function main () {
|
||||||
console.log('Consumed sign event')
|
console.log('Consumed sign event')
|
||||||
console.log(data)
|
console.log(data)
|
||||||
const { recipient, value, nonce, epoch } = data
|
const { recipient, value, nonce, epoch } = data
|
||||||
const keysFile = `/keys/keys${epoch}.store`
|
|
||||||
|
|
||||||
console.log(`Reading ${keysFile}`)
|
if (recipient) {
|
||||||
const { address, publicKey } = await getAccountFromFile(keysFile)
|
const keysFile = `/keys/keys${epoch}.store`
|
||||||
console.log(`Tx from ${address}`)
|
|
||||||
|
|
||||||
console.log('Getting account data')
|
const { address, publicKey } = await getAccountFromFile(keysFile)
|
||||||
const account = await getAccount(address)
|
console.log(`Tx from ${address}`)
|
||||||
|
|
||||||
console.log(`Building corresponding transaction, nonce ${nonce}, recipient ${recipient}`)
|
const account = await getAccount(address)
|
||||||
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}`)
|
console.log(`Building corresponding trasfer transaction, nonce ${nonce}, recipient ${recipient}`)
|
||||||
const cmd = exec.execFile('./sign-entrypoint.sh', [PROXY_URL, keysFile, epoch, hash], async () => {
|
const tx = new Transaction(address, account.account_number, nonce, recipient, value, FOREIGN_ASSET)
|
||||||
console.log('Finished signature generation')
|
|
||||||
const signature = JSON.parse(fs.readFileSync('signature'))
|
|
||||||
console.log(signature)
|
|
||||||
|
|
||||||
console.log('Building signed transaction')
|
const hash = crypto.createHash('sha256').update(tx.getSignBytes()).digest('hex')
|
||||||
const signedTx = tx.addSignature(publicKey, { r: signature[1], s: signature[3] })
|
|
||||||
|
|
||||||
console.log('Sending transaction')
|
console.log(`Starting signature generation for transaction hash ${hash}`)
|
||||||
console.log(signedTx)
|
const cmd = exec.execFile('./sign-entrypoint.sh', [PROXY_URL, keysFile, hash], async () => {
|
||||||
await sendTx(signedTx)
|
if (fs.existsSync('signature')) {
|
||||||
|
console.log('Finished signature generation')
|
||||||
|
const signature = JSON.parse(fs.readFileSync('signature'))
|
||||||
|
console.log(signature)
|
||||||
|
|
||||||
channel.ack(msg)
|
console.log('Building signed transaction')
|
||||||
})
|
const signedTx = tx.addSignature(publicKey, { r: signature[1], s: signature[3] })
|
||||||
cmd.stdout.on('data', data => console.log(data.toString()))
|
|
||||||
cmd.stderr.on('data', data => console.error(data.toString()))
|
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) {
|
async function getAccountFromFile (file) {
|
||||||
|
console.log(`Reading ${file}`)
|
||||||
while (!fs.existsSync(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))
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||||
}
|
}
|
||||||
const publicKey = JSON.parse(fs.readFileSync(file))[5]
|
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) {
|
async function getAccount (address) {
|
||||||
|
console.log(`Getting account ${address} data`)
|
||||||
return httpClient
|
return httpClient
|
||||||
.get(`/api/v1/account/${address}`)
|
.get(`/api/v1/account/${address}`)
|
||||||
.then(res => res.data)
|
.then(res => res.data)
|
||||||
|
@ -97,90 +185,16 @@ async function sendTx (tx) {
|
||||||
.then(x => console.log(x.response), x => console.log(x.response))
|
.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 }) {
|
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 sha256Hash = crypto.createHash('sha256').update(Buffer.from(compact, 'hex')).digest('hex')
|
||||||
const hash = crypto.createHash('ripemd160').update(Buffer.from(sha256Hash, 'hex')).digest('hex')
|
const hash = crypto.createHash('ripemd160').update(Buffer.from(sha256Hash, 'hex')).digest('hex')
|
||||||
const words = bech32.toWords(Buffer.from(hash, 'hex'))
|
const words = bech32.toWords(Buffer.from(hash, 'hex'))
|
||||||
return bech32.encode('tbnb', words)
|
return bech32.encode('tbnb', words)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function padZeros (s, len) {
|
||||||
|
while (s.length < len)
|
||||||
|
s = '0' + s
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
const TransactionBnc = require('@binance-chain/javascript-sdk/lib/tx').default
|
const TransactionBnc = require('@binance-chain/javascript-sdk/lib/tx').default
|
||||||
const { crypto } = require('@binance-chain/javascript-sdk')
|
const { crypto } = require('@binance-chain/javascript-sdk')
|
||||||
const BN = require('bn.js')
|
const BN = require('bignumber.js')
|
||||||
|
|
||||||
const { FOREIGN_CHAIN_ID } = process.env
|
const { FOREIGN_CHAIN_ID } = process.env
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ class Transaction {
|
||||||
const accCode = crypto.decodeAddress(fromAddress)
|
const accCode = crypto.decodeAddress(fromAddress)
|
||||||
const toAccCode = crypto.decodeAddress(toAddress)
|
const toAccCode = crypto.decodeAddress(toAddress)
|
||||||
|
|
||||||
amount *= 10 ** 8
|
amount = new BN(amount).multipliedBy(10 ** 8).toNumber()
|
||||||
|
|
||||||
const coin = {
|
const coin = {
|
||||||
denom: asset,
|
denom: asset,
|
||||||
|
@ -31,17 +31,11 @@ class Transaction {
|
||||||
this.signMsg = {
|
this.signMsg = {
|
||||||
inputs: [{
|
inputs: [{
|
||||||
address: fromAddress,
|
address: fromAddress,
|
||||||
coins: [{
|
coins: [coin]
|
||||||
amount: amount,
|
|
||||||
denom: asset
|
|
||||||
}]
|
|
||||||
}],
|
}],
|
||||||
outputs: [{
|
outputs: [{
|
||||||
address: toAddress,
|
address: toAddress,
|
||||||
coins: [{
|
coins: [coin]
|
||||||
amount: amount,
|
|
||||||
denom: asset
|
|
||||||
}]
|
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,9 +58,9 @@ class Transaction {
|
||||||
const yLast = parseInt(publicKey.y[publicKey.y.length - 1], 16)
|
const yLast = parseInt(publicKey.y[publicKey.y.length - 1], 16)
|
||||||
const n = new BN('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141', 16)
|
const n = new BN('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141', 16)
|
||||||
const s = new BN(signature.s, 16)
|
const s = new BN(signature.s, 16)
|
||||||
if (s.gt(n.divn(2))) {
|
if (s.gt(n.div(2))) {
|
||||||
console.log('Normalizing s')
|
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')
|
const publicKeyEncoded = Buffer.from('eb5ae98721' + (yLast % 2 ? '03' : '02') + padZeros(publicKey.x, 64), 'hex')
|
||||||
this.tx.signatures = [{
|
this.tx.signatures = [{
|
||||||
|
|
|
@ -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
|
Loading…
Reference in New Issue