Same private keys in both networks. Implementation modifications
This commit is contained in:
parent
360a593ccf
commit
1ab220caf4
|
@ -13,3 +13,4 @@ demo/ganache_data_side/
|
|||
src/deploy/deploy-home/build/
|
||||
src/deploy/deploy-side/build/
|
||||
src/deploy/deploy-test/build/
|
||||
test.js
|
||||
|
|
11
README.md
11
README.md
|
@ -8,6 +8,7 @@ two are enough to sign any transaction in the Binance Chain,
|
|||
confirm token transfer on the Ethereum Side, or vote for state changes.
|
||||
|
||||
ERC20 Token is used on the Ethereum side of the bridge.
|
||||
All ERC20 tokens are located on address associated with DEPLOY_PRIVATE_KEY.
|
||||
|
||||
BNB Token is used on the Binance Chain side.
|
||||
|
||||
|
@ -49,12 +50,12 @@ container for listening GET requests
|
|||
|
||||
Run this scripts from ```src/oracle``` dir
|
||||
|
||||
* ```node testBinanceSend.js PRIVATE_KEY TO VALUE [MEMO]```
|
||||
- ```PRIVATE_KEY``` - private key of sender in the Binance Chain
|
||||
* ```node testBinanceSend.js TO VALUE_TOKEN [VALUE_BNB]```
|
||||
- ```TO``` - receiver address, current bridge address in the Binance Chain
|
||||
- ```VALUE``` - amount of BNB to send
|
||||
- ```MEMO``` - transaction memo, receiver on the Ethereum side, leave blank for just pre-funding
|
||||
* ```node testApprove.js TO VALUE```
|
||||
- ```VALUE_TOKEN``` - amount of tokens to send
|
||||
- ```VALUE_BNB``` - amount of BNB tokens to send, if present, the
|
||||
transaction is considered as a funding one
|
||||
* ```node testEthereumSend.js TO VALUE```
|
||||
- Approves specified amount of tokens to the bridge account and calls
|
||||
needed method for starting exchange process
|
||||
- ```TO``` - receiver address in the Binance Chain
|
||||
|
|
|
@ -9,7 +9,10 @@ SIDE_SHARED_DB_ADDRESS=0x44c158FE850821ae69DaF37AADF5c539e9d0025B
|
|||
|
||||
FOREIGN_URL=https://testnet-dex.binance.org/
|
||||
FOREIGN_CHAIN_ID=Binance-Chain-Nile
|
||||
FOREIGN_ASSET=KFT-94F
|
||||
|
||||
VALIDATOR_PRIVATE_KEY=2be3f252e16541bf1bb2d4a517d2bf173e6d09f2d765d32c64dc50515aec63ea
|
||||
|
||||
VOTES_PROXY_PORT=5001
|
||||
|
||||
SIGN_RESTART_PORT=6001
|
||||
|
|
|
@ -9,7 +9,10 @@ SIDE_SHARED_DB_ADDRESS=0x44c158FE850821ae69DaF37AADF5c539e9d0025B
|
|||
|
||||
FOREIGN_URL=https://testnet-dex.binance.org/
|
||||
FOREIGN_CHAIN_ID=Binance-Chain-Nile
|
||||
FOREIGN_ASSET=KFT-94F
|
||||
|
||||
VALIDATOR_PRIVATE_KEY=e59d58c77b791f98f10187117374ae9c589d48a62720ec6a5e142b0cc134f685
|
||||
|
||||
VOTES_PROXY_PORT=5002
|
||||
|
||||
SIGN_RESTART_PORT=6002
|
||||
|
|
|
@ -9,7 +9,10 @@ SIDE_SHARED_DB_ADDRESS=0x44c158FE850821ae69DaF37AADF5c539e9d0025B
|
|||
|
||||
FOREIGN_URL=https://testnet-dex.binance.org/
|
||||
FOREIGN_CHAIN_ID=Binance-Chain-Nile
|
||||
FOREIGN_ASSET=KFT-94F
|
||||
|
||||
VALIDATOR_PRIVATE_KEY=afaa4d4d6e54d25b0bf0361e3fd6cef562f6311bf6200de2dd0aa4cab63ae3b5
|
||||
|
||||
VOTES_PROXY_PORT=5003
|
||||
|
||||
SIGN_RESTART_PORT=6003
|
||||
|
|
|
@ -3,13 +3,20 @@ 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
|
||||
event NewEpoch(uint indexed oldEpoch, uint indexed newEpoch);
|
||||
event NewEpochCancelled(uint indexed epoch);
|
||||
event NewFundsTransfer(uint indexed oldEpoch, uint indexed newEpoch);
|
||||
event EpochStart(uint indexed epoch, uint x, uint y);
|
||||
|
||||
struct State {
|
||||
address[] validators;
|
||||
uint threshold;
|
||||
uint x;
|
||||
uint y;
|
||||
}
|
||||
|
||||
mapping(uint => State) states;
|
||||
|
||||
address[] public validators;
|
||||
address[] public nextValidators;
|
||||
address[] public savedNextValidators;
|
||||
mapping(bytes32 => uint) public confirmationsCount;
|
||||
mapping(bytes32 => bool) public confirmations;
|
||||
mapping(bytes32 => uint) public dbTransferCount;
|
||||
|
@ -17,163 +24,213 @@ contract Bridge {
|
|||
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;
|
||||
// 0 - ready
|
||||
// 1 - keygen, can be cancelled
|
||||
// 2 - funds transfer, cannot be cancelled
|
||||
uint public status;
|
||||
|
||||
uint public epoch;
|
||||
uint public nextEpoch;
|
||||
|
||||
constructor(uint _threshold, uint _parties, address[] memory _validators, address _tokenContract) public {
|
||||
require(_parties > 0);
|
||||
require(_threshold < _parties);
|
||||
require(_validators.length == _parties);
|
||||
constructor(uint threshold, address[] memory validators, address _tokenContract) public {
|
||||
require(validators.length > 0);
|
||||
require(threshold < validators.length);
|
||||
|
||||
tokenContract = IERC20(_tokenContract);
|
||||
|
||||
epoch = 0;
|
||||
status = 1;
|
||||
nextEpoch = 1;
|
||||
ready = false;
|
||||
|
||||
nextThreshold = _threshold;
|
||||
savedNextValidators = _validators;
|
||||
states[1] = State(validators, threshold, 0, 0);
|
||||
|
||||
emit NewEpoch(nextEpoch);
|
||||
emit NewEpoch(0, 1);
|
||||
}
|
||||
|
||||
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);
|
||||
modifier ready {
|
||||
require(status == 0, "Not in ready state");
|
||||
_;
|
||||
}
|
||||
|
||||
function transfer(bytes32 hash, address to, uint value) public {
|
||||
uint partyId = getPartyId();
|
||||
require(partyId != 0, "Not a validator");
|
||||
modifier keygen {
|
||||
require(status == 1, "Not in keygen state");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier fundsTransfer {
|
||||
require(status == 2, "Not in funds transfer state");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier currentValidator {
|
||||
require(getPartyId() != 0, "Not a current validator");
|
||||
_;
|
||||
}
|
||||
|
||||
function transfer(bytes32 hash, address to, uint value) public ready currentValidator {
|
||||
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)
|
||||
if (++dbTransferCount[keccak256(abi.encodePacked(hash, to, value))] == getThreshold() + 1) {
|
||||
dbTransferCount[keccak256(abi.encodePacked(hash, to, value))] = 2 ** 255;
|
||||
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(nextEpoch, partyId, _x, _y))], "Already confirmed");
|
||||
function confirmKeygen(uint x, uint y) public keygen {
|
||||
require(getNextPartyId(msg.sender) != 0, "Not a next validator");
|
||||
require(!confirmations[keccak256(abi.encodePacked(uint(1), nextEpoch, msg.sender, x, y))], "Already confirmed");
|
||||
|
||||
confirmations[keccak256(abi.encodePacked(nextEpoch, partyId, _x, _y))] = true;
|
||||
if (++confirmationsCount[keccak256(abi.encodePacked(nextEpoch, _x, _y))] == nextParties()) {
|
||||
confirmationsCount[keccak256(abi.encodePacked(nextEpoch, _x, _y))] = 2 ** 256 - 1;
|
||||
x = _x;
|
||||
y = _y;
|
||||
validators = savedNextValidators;
|
||||
nextValidators = savedNextValidators;
|
||||
threshold = nextThreshold;
|
||||
confirmations[keccak256(abi.encodePacked(uint(1), nextEpoch, msg.sender, x, y))] = true;
|
||||
if (++confirmationsCount[keccak256(abi.encodePacked(uint(1), nextEpoch, x, y))] == getNextThreshold() + 1) {
|
||||
confirmationsCount[keccak256(abi.encodePacked(uint(1), nextEpoch, x, y))] = 2 ** 255;
|
||||
states[nextEpoch].x = x;
|
||||
states[nextEpoch].y = y;
|
||||
if (nextEpoch == 1) {
|
||||
status = 0;
|
||||
epoch = nextEpoch;
|
||||
ready = true;
|
||||
emit KeygenCompleted(epoch, x, y);
|
||||
emit EpochStart(epoch, x, y);
|
||||
}
|
||||
else {
|
||||
status = 2;
|
||||
emit NewFundsTransfer(epoch, nextEpoch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parties() view public returns (uint) {
|
||||
return validators.length;
|
||||
function confirmFundsTransfer() public fundsTransfer currentValidator {
|
||||
require(epoch > 0, "First epoch does not need funds transfer");
|
||||
require(!confirmations[keccak256(abi.encodePacked(uint(2), nextEpoch, msg.sender))], "Already confirmed");
|
||||
|
||||
confirmations[keccak256(abi.encodePacked(uint(2), nextEpoch, msg.sender))] = true;
|
||||
if (++confirmationsCount[keccak256(abi.encodePacked(uint(2), nextEpoch))] == getNextThreshold() + 1) {
|
||||
confirmationsCount[keccak256(abi.encodePacked(uint(2), nextEpoch))] = 2 ** 255;
|
||||
status = 0;
|
||||
epoch = nextEpoch;
|
||||
emit EpochStart(epoch, states[epoch].x, states[epoch].y);
|
||||
}
|
||||
}
|
||||
|
||||
function nextParties() view public returns (uint) {
|
||||
return savedNextValidators.length;
|
||||
function getParties() view public returns (uint) {
|
||||
return getParties(epoch);
|
||||
}
|
||||
|
||||
function getNextParties() view public returns (uint) {
|
||||
return getParties(nextEpoch);
|
||||
}
|
||||
|
||||
function getParties(uint _epoch) view public returns (uint) {
|
||||
return states[_epoch].validators.length;
|
||||
}
|
||||
|
||||
function getThreshold() view public returns (uint) {
|
||||
return getThreshold(epoch);
|
||||
}
|
||||
|
||||
function getNextThreshold() view public returns (uint) {
|
||||
return getThreshold(nextEpoch);
|
||||
}
|
||||
|
||||
function getThreshold(uint _epoch) view public returns (uint) {
|
||||
return states[_epoch].threshold;
|
||||
}
|
||||
|
||||
function getX() view public returns (uint) {
|
||||
return states[epoch].x;
|
||||
}
|
||||
|
||||
function getY() view public returns (uint) {
|
||||
return states[epoch].y;
|
||||
}
|
||||
|
||||
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)
|
||||
address[] memory validators = getValidators();
|
||||
for (uint i = 0; i < getParties(); i++) {
|
||||
if (validators[i] == msg.sender)
|
||||
return i + 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function getNextPartyId(address a) view public returns (uint) {
|
||||
for (uint i = 0; i < nextParties(); i++) {
|
||||
if (savedNextValidators[i] == a)
|
||||
address[] memory validators = getNextValidators();
|
||||
for (uint i = 0; i < getNextParties(); i++) {
|
||||
if (validators[i] == a)
|
||||
return i + 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function getValidatorsArray() view public returns (address[] memory) {
|
||||
return validators;
|
||||
function getValidators() view public returns (address[] memory) {
|
||||
return states[epoch].validators;
|
||||
}
|
||||
|
||||
function getNextValidatorsArray() view public returns (address[] memory) {
|
||||
return savedNextValidators;
|
||||
function getNextValidators() view public returns (address[] memory) {
|
||||
return states[nextEpoch].validators;
|
||||
}
|
||||
|
||||
function voteAddValidator(address validator) public {
|
||||
require(getPartyId() != 0, "Not a current validator");
|
||||
function voteAddValidator(address validator) public ready currentValidator {
|
||||
require(getNextPartyId(validator) == 0, "Already a validator");
|
||||
require(!votes[keccak256(abi.encodePacked(uint(1), epoch, msg.sender, validator))], "Already voted");
|
||||
require(!votes[keccak256(abi.encodePacked(uint(1), nextEpoch, 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);
|
||||
votes[keccak256(abi.encodePacked(uint(1), nextEpoch, msg.sender, validator))] = true;
|
||||
if (++votesCount[keccak256(abi.encodePacked(uint(1), nextEpoch, validator))] == getThreshold() + 1) {
|
||||
states[nextEpoch].validators.push(validator);
|
||||
}
|
||||
}
|
||||
|
||||
function voteRemoveValidator(address validator) public {
|
||||
require(getPartyId() != 0, "Not a current validator");
|
||||
function voteRemoveValidator(address validator) public ready currentValidator {
|
||||
require(getNextPartyId(validator) != 0, "Already not a validator");
|
||||
require(!votes[keccak256(abi.encodePacked(uint(2), epoch, msg.sender, validator))], "Already voted");
|
||||
require(!votes[keccak256(abi.encodePacked(uint(2), nextEpoch, 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) {
|
||||
votes[keccak256(abi.encodePacked(uint(2), nextEpoch, msg.sender, validator))] = true;
|
||||
if (++votesCount[keccak256(abi.encodePacked(uint(2), nextEpoch, validator))] == getThreshold() + 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];
|
||||
for (uint i = 0; i < getNextParties() - 1; i++) {
|
||||
if (states[nextEpoch].validators[i] == validator) {
|
||||
states[nextEpoch].validators[i] = states[nextEpoch].validators[getNextParties() - 1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
delete nextValidators[nextValidators.length - 1];
|
||||
nextValidators.length--;
|
||||
delete states[nextEpoch].validators[getNextParties() - 1];
|
||||
states[nextEpoch].validators.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");
|
||||
function voteChangeThreshold(uint threshold) public ready currentValidator {
|
||||
require(!votes[keccak256(abi.encodePacked(uint(3), nextEpoch, 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;
|
||||
votes[keccak256(abi.encodePacked(uint(3), nextEpoch, msg.sender, threshold))] = true;
|
||||
if (++votesCount[keccak256(abi.encodePacked(uint(3), nextEpoch, threshold))] == getThreshold() + 1) {
|
||||
states[nextEpoch].threshold = threshold;
|
||||
}
|
||||
}
|
||||
|
||||
function voteStartEpoch(uint newEpoch) public {
|
||||
require(newEpoch == nextEpoch + 1, "Wrong epoch number");
|
||||
require(getPartyId() != 0, "Not a current validator");
|
||||
require(!votes[keccak256(abi.encodePacked(uint(4), newEpoch, msg.sender))], "Voted already");
|
||||
function voteStartKeygen() public ready currentValidator {
|
||||
require(!votes[keccak256(abi.encodePacked(uint(4), nextEpoch + 1, msg.sender))], "Voted already");
|
||||
|
||||
votes[keccak256(abi.encodePacked(uint(4), newEpoch, msg.sender))] = true;
|
||||
if (++votesCount[keccak256(abi.encodePacked(uint(4), newEpoch))] == threshold + 1) {
|
||||
ready = false;
|
||||
votes[keccak256(abi.encodePacked(uint(4), nextEpoch + 1, msg.sender))] = true;
|
||||
if (++votesCount[keccak256(abi.encodePacked(uint(4), nextEpoch + 1))] == getThreshold() + 1) {
|
||||
status = 1;
|
||||
|
||||
nextEpoch = newEpoch;
|
||||
savedNextValidators = nextValidators;
|
||||
emit NewEpoch(newEpoch);
|
||||
nextEpoch++;
|
||||
emit NewEpoch(epoch, nextEpoch);
|
||||
}
|
||||
}
|
||||
|
||||
function voteCancelKeygen() public keygen currentValidator {
|
||||
require(!votes[keccak256(abi.encodePacked(uint(5), nextEpoch, msg.sender))], "Voted already");
|
||||
|
||||
votes[keccak256(abi.encodePacked(uint(5), nextEpoch, msg.sender))] = true;
|
||||
if (++votesCount[keccak256(abi.encodePacked(uint(5), nextEpoch))] == getThreshold() + 1) {
|
||||
status = 0;
|
||||
|
||||
emit NewEpochCancelled(nextEpoch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ module.exports = deployer => {
|
|||
deployer.deploy(
|
||||
Bridge,
|
||||
THRESHOLD,
|
||||
PARTIES,
|
||||
//PARTIES,
|
||||
[
|
||||
VALIDATOR_ADDRESS_1,
|
||||
VALIDATOR_ADDRESS_2,
|
||||
|
|
|
@ -12,7 +12,8 @@ contract SharedDB {
|
|||
}
|
||||
|
||||
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");
|
||||
if (dbSignups[keccak256(abi.encodePacked(validator, hash))] == 0)
|
||||
return 0;
|
||||
uint id = 1;
|
||||
for (uint i = 0; i < validators.length; i++) {
|
||||
uint vid = dbSignups[keccak256(abi.encodePacked(validators[i], hash))];
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
#RPC_URL=https://sokol.poa.network
|
||||
HOME_RPC_URL=http://127.0.0.1:4444
|
||||
SIDE_RPC_URL=http://127.0.0.1:3333
|
||||
HOME_CHAIN_ID=44
|
||||
|
@ -8,16 +7,14 @@ HOME_BRIDGE_ADDRESS=0x94b40CC641Ed7db241A1f04C8896ba6f6cC36b85
|
|||
HOME_TOKEN_ADDRESS=0x44c158FE850821ae69DaF37AADF5c539e9d0025B
|
||||
SIDE_SHARED_DB_ADDRESS=0x44c158FE850821ae69DaF37AADF5c539e9d0025B
|
||||
|
||||
#VALIDATOR_PRIVATE_KEY=d1e7b8ff274e517e1a332f2bc0ac051e30db196ba31c68c2efcd022e8ec358f1
|
||||
VALIDATOR_PRIVATE_KEY=2be3f252e16541bf1bb2d4a517d2bf173e6d09f2d765d32c64dc50515aec63ea
|
||||
|
||||
#SSM_URL=http://127.0.0.1:8001
|
||||
|
||||
LOCAL=true
|
||||
|
||||
DEPLOY_PRIVATE_KEY=e2aeb24eaa63102d0c0821717c3b6384abdabd7af2ad4ec8e650dce300798b27
|
||||
|
||||
FOREIGN_URL=https://testnet-dex.binance.org/
|
||||
FOREIGN_CHAIN_ID=Binance-Chain-Nile
|
||||
FOREIGN_ASSET=KFT-94F
|
||||
FOREIGN_PRIVATE_KEY=e2aeb24eaa63102d0c0821717c3b6384abdabd7af2ad4ec8e650dce300798b27
|
||||
|
||||
VOTES_PROXY_PORT=5000
|
||||
SIGN_RESTART_PORT=6000
|
||||
|
|
|
@ -4,9 +4,9 @@ const bech32 = require('bech32')
|
|||
const BN = require('bignumber.js')
|
||||
const fs = require('fs')
|
||||
const crypto = require('crypto')
|
||||
const { computeAddress } = require('ethers').utils
|
||||
|
||||
const { FOREIGN_URL, PROXY_URL } = process.env
|
||||
const FOREIGN_ASSET = 'BNB'
|
||||
const { FOREIGN_URL, PROXY_URL, FOREIGN_ASSET } = process.env
|
||||
|
||||
const foreignHttpClient = axios.create({ baseURL: FOREIGN_URL })
|
||||
const proxyHttpClient = axios.create({ baseURL: PROXY_URL })
|
||||
|
@ -30,9 +30,10 @@ async function main () {
|
|||
|
||||
for (const tx of newTransactions.reverse()) {
|
||||
if (tx.memo !== 'funding') {
|
||||
const publicKeyEncoded = (await getTx(tx.txHash)).signatures[0].pub_key.value
|
||||
await proxyHttpClient
|
||||
.post('/transfer', {
|
||||
to: tx.memo,
|
||||
to: computeAddress(Buffer.from(publicKeyEncoded, 'base64')),
|
||||
value: new BN(tx.value).integerValue(BN.ROUND_FLOOR),//(new BN(tx.value).multipliedBy(10 ** 8)).toNumber(),
|
||||
hash: `0x${tx.txHash}`
|
||||
})
|
||||
|
@ -41,6 +42,17 @@ async function main () {
|
|||
}
|
||||
}
|
||||
|
||||
function getTx(hash) {
|
||||
return foreignHttpClient
|
||||
.get(`/api/v1/tx/${hash}`, {
|
||||
params: {
|
||||
format: 'json'
|
||||
}
|
||||
})
|
||||
.then(res => res.data.tx.value)
|
||||
.catch(() => getTx(hash))
|
||||
}
|
||||
|
||||
async function fetchNewTransactions () {
|
||||
console.log('Fetching new transactions')
|
||||
const startTime = parseInt(await redis.get('foreignTime')) + 1
|
||||
|
@ -60,7 +72,7 @@ async function fetchNewTransactions () {
|
|||
}
|
||||
})
|
||||
.then(res => res.data.tx)
|
||||
.catch(console.log)
|
||||
.catch(() => fetchNewTransactions())
|
||||
}
|
||||
|
||||
function getLastForeignAddress () {
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
"ioredis": "4.10.0",
|
||||
"axios": "0.19.0",
|
||||
"bech32": "1.1.3",
|
||||
"bignumber.js": "9.0.0"
|
||||
"bignumber.js": "9.0.0",
|
||||
"ethers": "4.0.33"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ services:
|
|||
- SIDE_CHAIN_ID
|
||||
- VALIDATOR_PRIVATE_KEY
|
||||
- FOREIGN_URL
|
||||
- FOREIGN_ASSET
|
||||
volumes:
|
||||
- '../deploy/deploy-test/build/contracts/IERC20.json:/proxy/contracts_data/IERC20.json'
|
||||
- '../deploy/deploy-home/build/contracts/Bridge.json:/proxy/contracts_data/Bridge.json'
|
||||
|
@ -45,8 +46,11 @@ services:
|
|||
- 'PROXY_URL=http://proxy:8001'
|
||||
- FOREIGN_CHAIN_ID
|
||||
- FOREIGN_URL
|
||||
- FOREIGN_ASSET
|
||||
volumes:
|
||||
- '${PWD}/keys:/keys'
|
||||
ports:
|
||||
- '${SIGN_RESTART_PORT}:8001'
|
||||
networks:
|
||||
- test_network
|
||||
# - sign-proxy-net
|
||||
|
@ -79,9 +83,12 @@ services:
|
|||
environment:
|
||||
- HOME_RPC_URL
|
||||
- HOME_BRIDGE_ADDRESS
|
||||
- HOME_TOKEN_ADDRESS
|
||||
- HOME_CHAIN_ID
|
||||
- 'RABBITMQ_URL=amqp://rabbitmq:5672'
|
||||
volumes:
|
||||
- '../deploy/deploy-home/build/contracts/Bridge.json:/watcher/contracts_data/Bridge.json'
|
||||
- '../deploy/deploy-test/build/contracts/IERC20.json:/watcher/contracts_data/IERC20.json'
|
||||
networks:
|
||||
- test_network
|
||||
# - rabbit-ethwatcher-net
|
||||
|
@ -91,6 +98,7 @@ services:
|
|||
image: bnc-watcher
|
||||
environment:
|
||||
- FOREIGN_URL
|
||||
- FOREIGN_ASSET
|
||||
- 'RABBITMQ_URL=amqp://rabbitmq:5672'
|
||||
- 'PROXY_URL=http://proxy:8001'
|
||||
volumes:
|
||||
|
|
|
@ -1,19 +1,28 @@
|
|||
const amqp = require('amqplib')
|
||||
const Web3 = require('web3')
|
||||
const redis = require('./db')
|
||||
const bridgeAbi = require('./contracts_data/Bridge.json').abi
|
||||
const crypto = require('crypto')
|
||||
const utils = require('ethers').utils
|
||||
const BN = require('bignumber.js')
|
||||
const bech32 = require('bech32')
|
||||
|
||||
const { HOME_RPC_URL, HOME_BRIDGE_ADDRESS, RABBITMQ_URL } = process.env
|
||||
const abiBridge = require('./contracts_data/Bridge.json').abi
|
||||
const abiToken = require('./contracts_data/IERC20.json').abi
|
||||
|
||||
const { HOME_RPC_URL, HOME_CHAIN_ID, HOME_BRIDGE_ADDRESS, RABBITMQ_URL, HOME_TOKEN_ADDRESS } = process.env
|
||||
|
||||
const web3Home = new Web3(HOME_RPC_URL)
|
||||
const homeBridge = new web3Home.eth.Contract(bridgeAbi, HOME_BRIDGE_ADDRESS)
|
||||
const bridge = new web3Home.eth.Contract(abiBridge, HOME_BRIDGE_ADDRESS)
|
||||
const token = new web3Home.eth.Contract(abiToken, HOME_TOKEN_ADDRESS)
|
||||
|
||||
let channel
|
||||
let signQueue
|
||||
let epochQueue
|
||||
let keygenQueue
|
||||
let cancelKeygenQueue
|
||||
let blockNumber
|
||||
let foreignNonce = []
|
||||
let epoch
|
||||
let redisTx
|
||||
|
||||
async function connectRabbit (url) {
|
||||
return amqp.connect(url).catch(() => {
|
||||
|
@ -28,26 +37,28 @@ async function initialize () {
|
|||
const connection = await connectRabbit(RABBITMQ_URL)
|
||||
channel = await connection.createChannel()
|
||||
signQueue = await channel.assertQueue('signQueue')
|
||||
epochQueue = await channel.assertQueue('epochQueue')
|
||||
keygenQueue = await channel.assertQueue('keygenQueue')
|
||||
cancelKeygenQueue = await channel.assertQueue('cancelKeygenQueue')
|
||||
|
||||
const events = await homeBridge.getPastEvents('KeygenCompleted', {
|
||||
const events = await bridge.getPastEvents('EpochStart', {
|
||||
fromBlock: 1
|
||||
})
|
||||
epoch = events.length ? events[events.length - 1].returnValues.epoch.toNumber() : 0
|
||||
console.log(`Current epoch ${epoch}`)
|
||||
const dbEpoch = parseInt(await redis.get('epoch')) // number, or NaN if empty
|
||||
if (epoch !== dbEpoch) {
|
||||
console.log('Current epoch is outdated, starting from new epoch and block number')
|
||||
blockNumber = events.length ? events[events.length - 1].blockNumber : 1
|
||||
const epochStart = events.length ? events[events.length - 1].blockNumber : 1
|
||||
const saved = (parseInt(await redis.get('homeBlock')) + 1) || 1
|
||||
console.log(epochStart, saved)
|
||||
if (epochStart > saved) {
|
||||
console.log(`Data in db is outdated, starting from epoch ${epoch}, block #${epochStart}`)
|
||||
blockNumber = epochStart
|
||||
await redis.multi()
|
||||
.set('epoch', epoch)
|
||||
.set('homeBlock', blockNumber - 1)
|
||||
.set(`foreignNonce${epoch}`, 0)
|
||||
.exec()
|
||||
foreignNonce[epoch] = 0
|
||||
} else {
|
||||
console.log('Restoring epoch and block number from local db')
|
||||
blockNumber = (parseInt(await redis.get('homeBlock')) + 1) || 1
|
||||
blockNumber = saved
|
||||
foreignNonce[epoch] = parseInt(await redis.get(`foreignNonce${epoch}`)) || 0
|
||||
}
|
||||
}
|
||||
|
@ -60,63 +71,47 @@ async function main () {
|
|||
return
|
||||
}
|
||||
|
||||
const events = await homeBridge.getPastEvents('allEvents', {
|
||||
redisTx = redis.multi()
|
||||
|
||||
const bridgeEvents = await bridge.getPastEvents('allEvents', {
|
||||
fromBlock: blockNumber,
|
||||
toBlock: blockNumber
|
||||
})
|
||||
const epochEvents = events.filter(x => x.event === 'NewEpoch')
|
||||
const transferEvents = events.filter(x => x.event === 'ReceivedTokens')
|
||||
|
||||
epochEvents.forEach(event => {
|
||||
const newEpoch = event.returnValues.epoch.toNumber()
|
||||
const oldEpoch = newEpoch - 1
|
||||
channel.sendToQueue(epochQueue.queue, Buffer.from(JSON.stringify({
|
||||
epoch: newEpoch
|
||||
})), {
|
||||
persistent: true
|
||||
})
|
||||
console.log('Sent new epoch event')
|
||||
|
||||
if (oldEpoch > 0) {
|
||||
// Transfer all assets to new account tss account
|
||||
channel.sendToQueue(signQueue.queue, Buffer.from(JSON.stringify({
|
||||
epoch: newEpoch,
|
||||
//nonce: foreignNonce[oldEpoch],
|
||||
})), {
|
||||
persistent: true
|
||||
})
|
||||
console.log('Sent new epoch sign event')
|
||||
|
||||
foreignNonce[oldEpoch]++
|
||||
redis.incr(`foreignNonce${oldEpoch}`)
|
||||
for (const event of bridgeEvents) {
|
||||
switch (event.event) {
|
||||
case 'NewEpoch':
|
||||
await sendKeygen(event)
|
||||
break
|
||||
case 'NewEpochCancelled':
|
||||
sendKeygenCancelation(event)
|
||||
break
|
||||
case 'NewFundsTransfer':
|
||||
await sendSignFundsTransfer(event)
|
||||
break
|
||||
case 'EpochStart':
|
||||
epoch = event.returnValues.epoch.toNumber()
|
||||
console.log(`Epoch ${epoch} started`)
|
||||
foreignNonce[epoch] = 0
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
redis.multi()
|
||||
.incr('epoch')
|
||||
.set(`foreignNonce${newEpoch}`, 0)
|
||||
.exec()
|
||||
foreignNonce[newEpoch] = 0
|
||||
epoch++
|
||||
}
|
||||
)
|
||||
|
||||
transferEvents.forEach(event => {
|
||||
channel.sendToQueue(signQueue.queue, Buffer.from(JSON.stringify({
|
||||
recipient: event.returnValues.recipient,
|
||||
value: event.returnValues.value.toNumber(),
|
||||
epoch,
|
||||
nonce: foreignNonce[epoch]
|
||||
})), {
|
||||
persistent: true
|
||||
const transferEvents = await token.getPastEvents('Transfer', {
|
||||
filter: {
|
||||
to: HOME_BRIDGE_ADDRESS
|
||||
},
|
||||
fromBlock: blockNumber,
|
||||
toBlock: blockNumber
|
||||
})
|
||||
console.log('Sent new sign event')
|
||||
|
||||
redis.incr(`foreignNonce${epoch}`)
|
||||
foreignNonce[epoch]++
|
||||
for (const event of transferEvents) {
|
||||
await sendSign(event)
|
||||
}
|
||||
)
|
||||
await redis.incr('homeBlock')
|
||||
|
||||
blockNumber++
|
||||
// Exec redis tx
|
||||
await redisTx.incr('homeBlock').exec()
|
||||
}
|
||||
|
||||
initialize().then(async () => {
|
||||
|
@ -124,3 +119,88 @@ initialize().then(async () => {
|
|||
await main()
|
||||
}
|
||||
})
|
||||
|
||||
async function sendKeygen (event) {
|
||||
const newEpoch = event.returnValues.newEpoch.toNumber()
|
||||
channel.sendToQueue(keygenQueue.queue, Buffer.from(JSON.stringify({
|
||||
epoch: newEpoch,
|
||||
threshold: (await bridge.methods.getThreshold(newEpoch).call()).toNumber(),
|
||||
parties: (await bridge.methods.getParties(newEpoch).call()).toNumber()
|
||||
})), {
|
||||
persistent: true
|
||||
})
|
||||
console.log('Sent keygen start event')
|
||||
}
|
||||
|
||||
function sendKeygenCancelation (event) {
|
||||
const epoch = event.returnValues.epoch.toNumber()
|
||||
channel.sendToQueue(cancelKeygenQueue.queue, Buffer.from(JSON.stringify({
|
||||
epoch
|
||||
})), {
|
||||
persistent: true
|
||||
})
|
||||
console.log('Sent keygen cancellation event')
|
||||
}
|
||||
|
||||
async function sendSignFundsTransfer (event) {
|
||||
const newEpoch = event.returnValues.newEpoch.toNumber()
|
||||
const oldEpoch = event.returnValues.oldEpoch.toNumber()
|
||||
channel.sendToQueue(signQueue.queue, Buffer.from(JSON.stringify({
|
||||
epoch: oldEpoch,
|
||||
newEpoch,
|
||||
nonce: foreignNonce[oldEpoch],
|
||||
threshold: (await bridge.methods.getThreshold(oldEpoch).call()).toNumber(),
|
||||
parties: (await bridge.methods.getParties(oldEpoch).call()).toNumber()
|
||||
})), {
|
||||
persistent: true
|
||||
})
|
||||
console.log('Sent sign funds transfer event')
|
||||
foreignNonce[oldEpoch]++
|
||||
redisTx.incr(`foreignNonce${oldEpoch}`)
|
||||
}
|
||||
|
||||
async function sendSign (event) {
|
||||
const tx = await web3Home.eth.getTransaction(event.transactionHash)
|
||||
const msg = utils.serializeTransaction({
|
||||
nonce: tx.nonce,
|
||||
gasPrice: `0x${new BN(tx.gasPrice).toString(16)}`,
|
||||
gasLimit: `0x${new BN(tx.gas).toString(16)}`,
|
||||
to: tx.to,
|
||||
value: `0x${new BN(tx.value).toString(16)}`,
|
||||
data: tx.input,
|
||||
chainId: parseInt(HOME_CHAIN_ID)
|
||||
})
|
||||
const hash = web3Home.utils.sha3(msg)
|
||||
const publicKey = utils.recoverPublicKey(hash, { r: tx.r, s: tx.s, v: tx.v })
|
||||
channel.sendToQueue(signQueue.queue, Buffer.from(JSON.stringify({
|
||||
recipient: publicKeyToAddress({
|
||||
x: publicKey.substr(4, 64),
|
||||
y: publicKey.substr(68, 64)
|
||||
}),
|
||||
value: event.returnValues.value.toNumber(),
|
||||
epoch,
|
||||
nonce: foreignNonce[epoch],
|
||||
threshold: (await bridge.methods.getThreshold(epoch).call()).toNumber(),
|
||||
parties: (await bridge.methods.getParties(epoch).call()).toNumber()
|
||||
})), {
|
||||
persistent: true
|
||||
})
|
||||
console.log('Sent new sign event')
|
||||
|
||||
redisTx.incr(`foreignNonce${epoch}`)
|
||||
foreignNonce[epoch]++
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -4,8 +4,9 @@
|
|||
"dependencies": {
|
||||
"ioredis": "4.10.0",
|
||||
"amqplib": "0.5.3",
|
||||
"web3": "1.0.0-beta.55"
|
||||
"web3": "1.0.0-beta.55",
|
||||
"ethers": "4.0.33",
|
||||
"bignumber.js": "9.0.0",
|
||||
"bech32": "1.1.3"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -6,7 +6,10 @@ const bech32 = require('bech32')
|
|||
const axios = require('axios')
|
||||
const BN = require('bignumber.js')
|
||||
|
||||
const { HOME_RPC_URL, HOME_BRIDGE_ADDRESS, SIDE_RPC_URL, SIDE_SHARED_DB_ADDRESS, VALIDATOR_PRIVATE_KEY, HOME_CHAIN_ID, SIDE_CHAIN_ID, HOME_TOKEN_ADDRESS, FOREIGN_URL } = process.env
|
||||
const {
|
||||
HOME_RPC_URL, HOME_BRIDGE_ADDRESS, SIDE_RPC_URL, SIDE_SHARED_DB_ADDRESS, VALIDATOR_PRIVATE_KEY, HOME_CHAIN_ID,
|
||||
SIDE_CHAIN_ID, HOME_TOKEN_ADDRESS, FOREIGN_URL, FOREIGN_ASSET
|
||||
} = process.env
|
||||
const abiSharedDb = require('./contracts_data/SharedDB.json').abi
|
||||
const abiBridge = require('./contracts_data/Bridge.json').abi
|
||||
const abiToken = require('./contracts_data/IERC20.json').abi
|
||||
|
@ -18,7 +21,6 @@ const token = new homeWeb3.eth.Contract(abiToken, HOME_TOKEN_ADDRESS)
|
|||
const sharedDb = new sideWeb3.eth.Contract(abiSharedDb, SIDE_SHARED_DB_ADDRESS)
|
||||
const validatorAddress = homeWeb3.eth.accounts.privateKeyToAccount(`0x${VALIDATOR_PRIVATE_KEY}`).address
|
||||
|
||||
const FOREIGN_ASSET = 'BNB'
|
||||
const httpClient = axios.create({ baseURL: FOREIGN_URL })
|
||||
|
||||
const lock = new AsyncLock()
|
||||
|
@ -35,16 +37,16 @@ app.post('/set', set)
|
|||
app.post('/signupkeygen', signupKeygen)
|
||||
app.post('/signupsign', signupSign)
|
||||
|
||||
app.get('/current_params', currentParams)
|
||||
app.get('/next_params', nextParams)
|
||||
app.post('/confirm', confirm)
|
||||
app.post('/confirmKeygen', confirmKeygen)
|
||||
app.post('/confirmFundsTransfer', confirmFundsTransfer)
|
||||
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/startKeygen', voteStartKeygen)
|
||||
votesProxyApp.get('/vote/cancelKeygen', voteCancelKeygen)
|
||||
votesProxyApp.get('/vote/addValidator/:validator', voteAddValidator)
|
||||
votesProxyApp.get('/vote/removeValidator/:validator', voteRemoveValidator)
|
||||
votesProxyApp.get('/info', info)
|
||||
|
@ -79,9 +81,9 @@ async function get (req, res) {
|
|||
const uuid = req.body.key.third
|
||||
let from
|
||||
if (uuid.startsWith('k'))
|
||||
from = await bridge.methods.savedNextValidators(parseInt(req.body.key.first) - 1).call()
|
||||
from = (await bridge.methods.getNextValidators().call())[parseInt(req.body.key.first) - 1]
|
||||
else {
|
||||
const validators = await bridge.methods.getValidatorsArray().call()
|
||||
const validators = await bridge.methods.getValidators().call()
|
||||
from = await sharedDb.methods.getSignupAddress(uuid, validators, parseInt(req.body.key.first)).call()
|
||||
}
|
||||
const to = Number(req.body.key.fourth) // 0 if empty
|
||||
|
@ -130,47 +132,42 @@ async function signupSign (req, res) {
|
|||
console.log('SignupSign call')
|
||||
const hash = sideWeb3.utils.sha3(`0x${req.body.third}`)
|
||||
const query = sharedDb.methods.signupSign(hash)
|
||||
await sideSendQuery(query)
|
||||
const receipt = await sideSendQuery(query)
|
||||
|
||||
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 (id > threshold + 1) {
|
||||
res.send(Err({}))
|
||||
// Already have signup
|
||||
if (receipt === false) {
|
||||
console.log('Already have signup')
|
||||
res.send(Ok({ uuid: hash, number: 0 }))
|
||||
return
|
||||
}
|
||||
|
||||
const validators = await bridge.methods.getValidators().call()
|
||||
const id = (await sharedDb.methods.getSignupNumber(hash, validators, validatorAddress).call()).toNumber()
|
||||
|
||||
res.send(Ok({ uuid: hash, number: id }))
|
||||
console.log('SignupSign end')
|
||||
}
|
||||
|
||||
async function confirm (req, res) {
|
||||
console.log('Confirm call')
|
||||
async function confirmKeygen (req, res) {
|
||||
console.log('Confirm keygen call')
|
||||
const { x, y } = req.body[5]
|
||||
const query = bridge.methods.confirm(`0x${x}`, `0x${y}`)
|
||||
const query = bridge.methods.confirmKeygen(`0x${x}`, `0x${y}`)
|
||||
await homeSendQuery(query)
|
||||
res.send()
|
||||
console.log('Confirm end')
|
||||
console.log('Confirm keygen end')
|
||||
}
|
||||
|
||||
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('Current params end')
|
||||
}
|
||||
|
||||
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')
|
||||
async function confirmFundsTransfer (req, res) {
|
||||
console.log('Confirm funds transfer call')
|
||||
const query = bridge.methods.confirmFundsTransfer()
|
||||
await homeSendQuery(query)
|
||||
res.send()
|
||||
console.log('Confirm funds transfer end')
|
||||
}
|
||||
|
||||
function sideSendQuery (query) {
|
||||
return lock.acquire('side', async () => {
|
||||
console.log('Sending query')
|
||||
const encodedABI = query.encodeABI()
|
||||
const tx = {
|
||||
data: encodedABI,
|
||||
|
@ -182,18 +179,27 @@ function sideSendQuery (query) {
|
|||
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)
|
||||
if (e.message.includes('out of gas')) {
|
||||
return sideWeb3.eth.sendSignedTransaction(signedTx.rawTransaction)
|
||||
.catch(e => {
|
||||
const error = parseError(e.message)
|
||||
const reason = parseReason(e.message)
|
||||
if (error === 'revert' && reason.length) {
|
||||
console.log(reason)
|
||||
return false
|
||||
} else if (error === 'out of gas') {
|
||||
console.log('Out of gas, retrying')
|
||||
sideSendQuery(query)
|
||||
}
|
||||
return null
|
||||
return true
|
||||
} else {
|
||||
console.log('Home tx failed', e.message)
|
||||
return false
|
||||
}
|
||||
})
|
||||
})
|
||||
.then(result => {
|
||||
if (result === true)
|
||||
return sideSendQuery(query)
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
function homeSendQuery (query) {
|
||||
|
@ -209,23 +215,54 @@ function homeSendQuery (query) {
|
|||
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)
|
||||
if (e.message.includes('out of gas')) {
|
||||
return homeWeb3.eth.sendSignedTransaction(signedTx.rawTransaction)
|
||||
.catch(e => {
|
||||
const error = parseError(e.message)
|
||||
const reason = parseReason(e.message)
|
||||
if (error === 'revert' && reason.length) {
|
||||
console.log(reason)
|
||||
return false
|
||||
} else if (error === 'out of gas') {
|
||||
console.log('Out of gas, retrying')
|
||||
homeSendQuery(query)
|
||||
}
|
||||
return null
|
||||
return true
|
||||
} else {
|
||||
console.log('Home tx failed', e.message)
|
||||
return false
|
||||
}
|
||||
})
|
||||
})
|
||||
.then(result => {
|
||||
if (result === true)
|
||||
return homeSendQuery(query)
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
async function voteStartEpoch (req, res) {
|
||||
console.log('Voting for starting new epoch')
|
||||
const query = bridge.methods.voteStartEpoch(req.params.epoch)
|
||||
function parseReason (message) {
|
||||
const result = /(?<="reason":").*?(?=")/.exec(message)
|
||||
return result ? result[0] : ''
|
||||
}
|
||||
|
||||
function parseError (message) {
|
||||
const result = /(?<="error":").*?(?=")/.exec(message)
|
||||
return result ? result[0] : ''
|
||||
}
|
||||
|
||||
async function voteStartKeygen (req, res) {
|
||||
console.log('Voting for starting new epoch keygen')
|
||||
const query = bridge.methods.voteStartKeygen()
|
||||
try {
|
||||
await homeSendQuery(query)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
res.send('Voted')
|
||||
console.log('Voted successfully')
|
||||
}
|
||||
|
||||
async function voteCancelKeygen (req, res) {
|
||||
console.log('Voting for cancelling new epoch keygen')
|
||||
const query = bridge.methods.voteCancelKeygen()
|
||||
try {
|
||||
await homeSendQuery(query)
|
||||
} catch (e) {
|
||||
|
@ -261,20 +298,33 @@ async function voteRemoveValidator (req, res) {
|
|||
|
||||
async function info (req, res) {
|
||||
console.log('Info start')
|
||||
const x = new BN(await bridge.methods.x().call()).toString(16)
|
||||
const y = new BN(await bridge.methods.y().call()).toString(16)
|
||||
const [x, y, epoch, nextEpoch, threshold, nextThreshold, validators, nextValidators, homeBalance, status] = await Promise.all([
|
||||
bridge.methods.getX().call().then(x => new BN(x).toString(16)),
|
||||
bridge.methods.getY().call().then(x => new BN(x).toString(16)),
|
||||
bridge.methods.epoch().call().then(x => x.toNumber()),
|
||||
bridge.methods.nextEpoch().call().then(x => x.toNumber()),
|
||||
bridge.methods.getThreshold().call().then(x => x.toNumber()),
|
||||
bridge.methods.getNextThreshold().call().then(x => x.toNumber()),
|
||||
bridge.methods.getValidators().call(),
|
||||
bridge.methods.getNextValidators().call(),
|
||||
token.methods.balanceOf(HOME_BRIDGE_ADDRESS).call().then(x => x.toNumber()),
|
||||
bridge.methods.status().call().then(x => x.toNumber()),
|
||||
])
|
||||
const foreignAddress = publicKeyToAddress({ x, y })
|
||||
const balances = await getForeignBalances(foreignAddress)
|
||||
res.send({
|
||||
epoch: (await bridge.methods.epoch().call()).toNumber(),
|
||||
nextEpoch: (await bridge.methods.nextEpoch().call()).toNumber(),
|
||||
threshold: (await bridge.methods.threshold().call()).toNumber(),
|
||||
nextThreshold: (await bridge.methods.nextThreshold().call()).toNumber(),
|
||||
epoch,
|
||||
nextEpoch,
|
||||
threshold,
|
||||
nextThreshold,
|
||||
homeBridgeAddress: HOME_BRIDGE_ADDRESS,
|
||||
foreignBridgeAddress: publicKeyToAddress({ x, y }),
|
||||
validators: await bridge.methods.getValidatorsArray().call(),
|
||||
nextValidators: await bridge.methods.getNextValidatorsArray().call(),
|
||||
homeBalance: (await token.methods.balanceOf(HOME_BRIDGE_ADDRESS).call()).toNumber(),
|
||||
foreignBalance: await getForeignBalance(publicKeyToAddress({ x, y })),
|
||||
bridgeStatus: await bridge.methods.ready().call()
|
||||
foreignBridgeAddress: foreignAddress,
|
||||
validators,
|
||||
nextValidators,
|
||||
homeBalance,
|
||||
foreignBalanceTokens: parseFloat(balances[FOREIGN_ASSET]) || 0,
|
||||
foreignBalanceNative: parseFloat(balances['BNB']) || 0,
|
||||
bridgeStatus: status ? (status === 1 ? 'keygen' : 'funds transfer') : 'ready'
|
||||
})
|
||||
console.log('Info end')
|
||||
}
|
||||
|
@ -293,11 +343,14 @@ async function transfer (req, res) {
|
|||
console.log('Transfer end')
|
||||
}
|
||||
|
||||
function getForeignBalance(address) {
|
||||
function getForeignBalances (address) {
|
||||
return httpClient
|
||||
.get(`/api/v1/account/${address}`)
|
||||
.then(res => parseFloat(res.data.balances.find(x => x.symbol === FOREIGN_ASSET).free))
|
||||
.catch(err => 0)
|
||||
.then(res => res.data.balances.reduce((prev, cur) => {
|
||||
prev[cur.symbol] = cur.free
|
||||
return prev
|
||||
}, {}))
|
||||
.catch(err => ({}))
|
||||
}
|
||||
|
||||
function publicKeyToAddress ({ x, y }) {
|
||||
|
|
|
@ -7,28 +7,39 @@ const crypto = require('crypto')
|
|||
const ecc = require('tiny-secp256k1')
|
||||
const utils = require('ethers').utils
|
||||
|
||||
const privKey = process.argv[2]//'b92a59209e28149e5cee8e54dfceb80a08ea08e654261bdb9d264b15dee2525c'
|
||||
const asset = 'BNB'
|
||||
const amount = process.argv[4]//'2.5'
|
||||
const addressTo = process.argv[3]
|
||||
const addressFrom = Bnc.crypto.getAddressFromPrivateKey(privKey)
|
||||
const message = process.argv[5] || 'funding'
|
||||
const api = 'https://testnet-dex.binance.org/'
|
||||
const publicKeyStr = utils.computePublicKey(`0x${privKey}`)
|
||||
const { FOREIGN_URL, FOREIGN_ASSET, FOREIGN_PRIVATE_KEY } = process.env
|
||||
const amount = process.argv[3]
|
||||
const addressTo = process.argv[2]
|
||||
const addressFrom = Bnc.crypto.getAddressFromPrivateKey(FOREIGN_PRIVATE_KEY)
|
||||
const bnbs = process.argv[4]
|
||||
const realBnbs = bnbs || '0'
|
||||
const publicKeyStr = utils.computePublicKey(`0x${FOREIGN_PRIVATE_KEY}`)
|
||||
const publicKey = {
|
||||
x: publicKeyStr.substr(4, 64),
|
||||
y: publicKeyStr.substr(68, 64)
|
||||
}
|
||||
|
||||
console.log(`From ${addressFrom} to ${addressTo}, ${amount} tokens, memo '${message}'`)
|
||||
const httpClient = axios.create({ baseURL: api })
|
||||
if (bnbs)
|
||||
console.log(`Funding from ${addressFrom} to ${addressTo}, ${amount} tokens, ${realBnbs} BNB'`)
|
||||
else
|
||||
console.log(`From ${addressFrom} to ${addressTo}, ${amount} tokens'`)
|
||||
const httpClient = axios.create({ baseURL: FOREIGN_URL })
|
||||
httpClient
|
||||
.get(`/api/v1/account/${addressFrom}`)
|
||||
.then((res) => {
|
||||
const { sequence, account_number } = res.data
|
||||
const tx = new Transaction(addressFrom, account_number, sequence, addressTo, amount, asset, message)
|
||||
const tx = new Transaction({
|
||||
from: addressFrom,
|
||||
to: addressTo,
|
||||
accountNumber: account_number,
|
||||
sequence,
|
||||
tokens: amount,
|
||||
asset: FOREIGN_ASSET,
|
||||
bnbs: realBnbs,
|
||||
memo: bnbs ? 'funding' : 'exchange'
|
||||
})
|
||||
const hash = crypto.createHash('sha256').update(tx.getSignBytes()).digest('hex')
|
||||
const signature = ecc.sign(Buffer.from(hash, 'hex'), Buffer.from(privKey, 'hex'))
|
||||
const signature = ecc.sign(Buffer.from(hash, 'hex'), Buffer.from(FOREIGN_PRIVATE_KEY, 'hex'))
|
||||
const sig = {
|
||||
r: signature.toString('hex').substr(0, 64),
|
||||
s: signature.toString('hex').substr(64, 64)
|
||||
|
|
|
@ -4,24 +4,19 @@ const Web3 = require('web3')
|
|||
|
||||
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 amount = parseInt(process.argv[3])
|
||||
const query1 = token.methods.approve(HOME_BRIDGE_ADDRESS, amount)
|
||||
const query2 = bridge.methods.requestAffirmation(amount, process.argv[2])
|
||||
const amount = parseInt(process.argv[2])
|
||||
const query = token.methods.transfer(HOME_BRIDGE_ADDRESS, amount)
|
||||
|
||||
let nonce
|
||||
const deployAddress = web3.eth.accounts.privateKeyToAccount(`0x${DEPLOY_PRIVATE_KEY}`).address
|
||||
|
||||
async function main () {
|
||||
console.log(`Transfer from ${deployAddress} to ${HOME_BRIDGE_ADDRESS}, ${amount} tokens`)
|
||||
console.log(`Exchange to address ${process.argv[2]}`)
|
||||
nonce = await web3.eth.getTransactionCount(deployAddress)
|
||||
console.log(await sendQuery(query1, HOME_TOKEN_ADDRESS))
|
||||
await sendQuery(query2, HOME_BRIDGE_ADDRESS)
|
||||
console.log(await sendQuery(query, HOME_TOKEN_ADDRESS))
|
||||
}
|
||||
|
||||
async function sendQuery (query, to) {
|
|
@ -0,0 +1,13 @@
|
|||
require('dotenv').config()
|
||||
|
||||
const axios = require('axios')
|
||||
|
||||
const { FOREIGN_URL, FOREIGN_ASSET } = process.env
|
||||
|
||||
const address = process.argv[2]
|
||||
const httpClient = axios.create({ baseURL: FOREIGN_URL })
|
||||
|
||||
httpClient
|
||||
.get(`/api/v1/account/${address}`)
|
||||
.then(res => console.log(parseFloat(res.data.balances.find(x => x.symbol === FOREIGN_ASSET).free)))
|
||||
.catch(console.log)
|
|
@ -0,0 +1,12 @@
|
|||
require('dotenv').config()
|
||||
|
||||
const Web3 = require('web3')
|
||||
|
||||
const { HOME_RPC_URL, HOME_TOKEN_ADDRESS } = process.env
|
||||
const web3 = new Web3(HOME_RPC_URL, null, { transactionConfirmationBlocks: 1 })
|
||||
const abiToken = require('../deploy/deploy-home/build/contracts/IERC20').abi
|
||||
const token = new web3.eth.Contract(abiToken, HOME_TOKEN_ADDRESS)
|
||||
|
||||
const address = process.argv[2]
|
||||
|
||||
token.methods.balanceOf(address).call().then(x => console.log(x.toNumber()))
|
|
@ -8,18 +8,8 @@ until curl "$1" > /dev/null 2>&1; do
|
|||
sleep 1;
|
||||
done
|
||||
|
||||
echo "Fetching next tss params"
|
||||
|
||||
curl -X GET "$1/next_params" -o ./params > /dev/null 2>&1
|
||||
|
||||
echo "Generating key using server $1"
|
||||
|
||||
pkill gg18_keygen || true
|
||||
|
||||
./gg18_keygen_client "$1" "$2"
|
||||
|
||||
echo "Generated keys for all parties"
|
||||
|
||||
#echo "Sending confirmation"
|
||||
|
||||
#curl -X POST -H "Content-Type: application/json" -d @"$2" "$1/confirm" > /dev/null 2>&1
|
||||
|
|
|
@ -6,39 +6,56 @@ const amqp = require('amqplib')
|
|||
|
||||
const { RABBITMQ_URL, PROXY_URL } = process.env
|
||||
|
||||
let currentKeygenEpoch = null
|
||||
|
||||
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.createChannel()
|
||||
const queue = await channel.assertQueue('epochQueue')
|
||||
const keygenQueue = await channel.assertQueue('keygenQueue')
|
||||
const cancelKeygenQueue = await channel.assertQueue('cancelKeygenQueue')
|
||||
|
||||
channel.prefetch(2)
|
||||
channel.consume(queue.queue, msg => {
|
||||
const data = JSON.parse(msg.content)
|
||||
console.log(`Consumed new epoch event, starting keygen for epoch ${data.epoch}`)
|
||||
channel.prefetch(1)
|
||||
channel.consume(keygenQueue.queue, msg => {
|
||||
const { epoch, parties, threshold } = JSON.parse(msg.content)
|
||||
console.log(`Consumed new epoch event, starting keygen for epoch ${epoch}`)
|
||||
|
||||
const keysFile = `/keys/keys${data.epoch}.store`
|
||||
const keysFile = `/keys/keys${epoch}.store`
|
||||
|
||||
console.log('Running ./keygen-entrypoint.sh')
|
||||
currentKeygenEpoch = epoch
|
||||
|
||||
console.log('Writing params')
|
||||
fs.writeFileSync('./params', JSON.stringify({ parties: parties.toString(), threshold: threshold.toString() }))
|
||||
const cmd = exec.execFile('./keygen-entrypoint.sh', [PROXY_URL, keysFile], async () => {
|
||||
currentKeygenEpoch = null
|
||||
if (fs.existsSync(keysFile)) {
|
||||
console.log(`Finished keygen for epoch ${data.epoch}`)
|
||||
console.log(`Finished keygen for epoch ${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)
|
||||
}
|
||||
|
||||
console.log('Sending keys confirmation')
|
||||
await confirmKeygen(keysFile)
|
||||
} else {
|
||||
console.log(`Keygen for epoch ${data.epoch} failed`)
|
||||
console.log(`Keygen for epoch ${epoch} failed`)
|
||||
}
|
||||
console.log('Ack for keygen message')
|
||||
channel.ack(msg)
|
||||
})
|
||||
cmd.stdout.on('data', data => console.error(data.toString()))
|
||||
cmd.stdout.on('data', data => console.log(data.toString()))
|
||||
cmd.stderr.on('data', data => console.error(data.toString()))
|
||||
})
|
||||
|
||||
channel.consume(cancelKeygenQueue.queue, async msg => {
|
||||
const { epoch } = JSON.parse(msg.content)
|
||||
console.log(`Consumed new cancel event for epoch ${epoch} keygen`)
|
||||
if (currentKeygenEpoch === epoch) {
|
||||
console.log('Cancelling current keygen')
|
||||
exec.execSync('pkill gg18_keygen || true')
|
||||
}
|
||||
channel.ack(msg)
|
||||
})
|
||||
}
|
||||
|
||||
main()
|
||||
|
@ -52,8 +69,8 @@ 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 confirmKeygen (keysFile) {
|
||||
exec.execSync(`curl -X POST -H "Content-Type: application/json" -d @"${keysFile}" "${PROXY_URL}/confirmKeygen"`, { stdio: 'pipe' })
|
||||
}
|
||||
|
||||
function publicKeyToAddress ({ x, y }) {
|
||||
|
|
|
@ -3,7 +3,7 @@ FROM node:10.16.0-slim
|
|||
WORKDIR /tss
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y libssl1.1 libssl-dev curl python make g++ libudev-dev libusb-dev usbutils
|
||||
apt-get install -y libssl1.1 libssl-dev curl python make g++ libudev-dev libusb-dev usbutils procps
|
||||
#apk packages: libssl1.1 eudev-dev libressl-dev curl build-base python linux-headers libusb-dev
|
||||
|
||||
COPY package.json /tss/
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
"@binance-chain/javascript-sdk": "2.13.9",
|
||||
"amqplib": "0.5.3",
|
||||
"axios": "0.19.0",
|
||||
"bignumber.js": "9.0.0"
|
||||
"bignumber.js": "9.0.0",
|
||||
"express": "4.17.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,10 +8,6 @@ until curl "$1" > /dev/null 2>&1; do
|
|||
sleep 1;
|
||||
done
|
||||
|
||||
echo "Fetching current tss params"
|
||||
|
||||
curl -X GET "$1/current_params" -o ./params > /dev/null 2>&1
|
||||
|
||||
echo "Signing message using server $1"
|
||||
|
||||
rm -f signature
|
||||
|
|
|
@ -4,115 +4,148 @@ const amqp = require('amqplib')
|
|||
const crypto = require('crypto')
|
||||
const bech32 = require('bech32')
|
||||
const BN = require('bignumber.js')
|
||||
const express = require('express')
|
||||
|
||||
const { RABBITMQ_URL, FOREIGN_URL, PROXY_URL } = process.env
|
||||
const FOREIGN_ASSET = 'BNB'
|
||||
const app = express()
|
||||
app.get('/restart/:attempt', restart)
|
||||
app.listen(8001, () => console.log('Listening on 8001'))
|
||||
|
||||
const { RABBITMQ_URL, FOREIGN_URL, PROXY_URL, FOREIGN_ASSET } = process.env
|
||||
const Transaction = require('./tx')
|
||||
const axios = require('axios')
|
||||
|
||||
const httpClient = axios.create({ baseURL: FOREIGN_URL })
|
||||
|
||||
let attempt
|
||||
let nextAttempt = null
|
||||
let cancelled
|
||||
|
||||
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.createChannel()
|
||||
const queue = await channel.assertQueue('signQueue')
|
||||
const signQueue = await channel.assertQueue('signQueue')
|
||||
|
||||
channel.prefetch(1)
|
||||
channel.consume(queue.queue, async msg => {
|
||||
channel.consume(signQueue.queue, async msg => {
|
||||
const data = JSON.parse(msg.content)
|
||||
|
||||
console.log('Consumed sign event')
|
||||
console.log(data)
|
||||
const { recipient, value, nonce, epoch } = data
|
||||
const { recipient, value, nonce, epoch, newEpoch, parties, threshold } = data
|
||||
|
||||
if (recipient) {
|
||||
const keysFile = `/keys/keys${epoch}.store`
|
||||
|
||||
const { address, publicKey } = await getAccountFromFile(keysFile)
|
||||
console.log(`Tx from ${address}`)
|
||||
|
||||
const account = await getAccount(address)
|
||||
|
||||
console.log(`Building corresponding trasfer transaction, nonce ${nonce}, recipient ${recipient}`)
|
||||
const tx = new Transaction(address, account.account_number, nonce, recipient, value, FOREIGN_ASSET)
|
||||
|
||||
const hash = crypto.createHash('sha256').update(tx.getSignBytes()).digest('hex')
|
||||
|
||||
console.log(`Starting signature generation for transaction hash ${hash}`)
|
||||
const cmd = exec.execFile('./sign-entrypoint.sh', [PROXY_URL, keysFile, 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(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 { address: from, publicKey } = await getAccountFromFile(keysFile)
|
||||
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)
|
||||
console.log('Writing params')
|
||||
fs.writeFileSync('./params', JSON.stringify({ parties: parties.toString(), threshold: threshold.toString() }))
|
||||
|
||||
attempt = 1
|
||||
|
||||
if (recipient && account.sequence <= nonce) {
|
||||
while (true) {
|
||||
console.log(`Building corresponding transfer transaction, nonce ${nonce}, recipient ${recipient}`)
|
||||
const tx = new Transaction({
|
||||
from,
|
||||
accountNumber: account.account_number,
|
||||
sequence: nonce,
|
||||
to: recipient,
|
||||
tokens: value,
|
||||
asset: FOREIGN_ASSET,
|
||||
memo: `Attempt ${attempt}`
|
||||
})
|
||||
|
||||
const hash = crypto.createHash('sha256').update(tx.getSignBytes()).digest('hex')
|
||||
|
||||
console.log(`Starting signature generation for transaction hash ${hash}`)
|
||||
const cmd = exec.execFile('./sign-entrypoint.sh', [PROXY_URL, prevKeysFile, hash], async () => {
|
||||
if (fs.existsSync('signature')) {
|
||||
console.log('Finished signature generation')
|
||||
const signature = JSON.parse(fs.readFileSync('signature'))
|
||||
console.log(signature)
|
||||
const done = await sign(keysFile, hash, tx, publicKey) && await waitForAccountNonce(from, nonce + 1)
|
||||
|
||||
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)
|
||||
if (done) {
|
||||
break
|
||||
}
|
||||
await waitForAccountNonce(from, account.sequence + 1)
|
||||
attempt = nextAttempt ? nextAttempt : attempt + 1
|
||||
console.log(`Sign failed, starting next attempt ${attempt}`)
|
||||
nextAttempt = null
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
}
|
||||
} else if (account.sequence <= nonce) {
|
||||
const newKeysFile = `/keys/keys${newEpoch}.store`
|
||||
const { address: to } = await getAccountFromFile(newKeysFile)
|
||||
|
||||
await confirm(`/keys/keys${epoch}.store`)
|
||||
while (true) {
|
||||
console.log(`Building corresponding transaction for transferring all funds, nonce ${nonce}, recipient ${to}`)
|
||||
const tx = new Transaction({
|
||||
from,
|
||||
accountNumber: account.account_number,
|
||||
sequence: nonce,
|
||||
to,
|
||||
tokens: account.balances.find(x => x.symbol === FOREIGN_ASSET).free,
|
||||
asset: FOREIGN_ASSET,
|
||||
bnbs: new BN(account.balances.find(x => x.symbol === 'BNB').free).minus(new BN(60000).div(10 ** 8)),
|
||||
memo: `Attempt ${attempt}`
|
||||
})
|
||||
|
||||
const hash = crypto.createHash('sha256').update(tx.getSignBytes()).digest('hex')
|
||||
console.log(`Starting signature generation for transaction hash ${hash}`)
|
||||
const done = await sign(keysFile, hash, tx, publicKey) && await waitForAccountNonce(from, nonce + 1)
|
||||
|
||||
if (done) {
|
||||
await confirmFundsTransfer()
|
||||
break
|
||||
}
|
||||
attempt = nextAttempt ? nextAttempt : attempt + 1
|
||||
console.log(`Sign failed, starting next attempt ${attempt}`)
|
||||
nextAttempt = null
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log('Tx has been already sent')
|
||||
}
|
||||
console.log('Acking message')
|
||||
channel.ack(msg)
|
||||
})
|
||||
cmd.stdout.on('data', data => console.log(data.toString()))
|
||||
cmd.stderr.on('data', data => console.error(data.toString()))
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
async function connectRabbit (url) {
|
||||
function sign (keysFile, hash, tx, publicKey) {
|
||||
return new Promise(resolve => {
|
||||
const cmd = exec.execFile('./sign-entrypoint.sh', [PROXY_URL, keysFile, hash], async (error) => {
|
||||
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)
|
||||
resolve(true)
|
||||
} else if (error === null || error.code === 0) {
|
||||
resolve(true)
|
||||
} else {
|
||||
console.log('Sign failed')
|
||||
resolve(false)
|
||||
}
|
||||
})
|
||||
cmd.stdout.on('data', data => console.log(data.toString()))
|
||||
cmd.stderr.on('data', data => console.error(data.toString()))
|
||||
})
|
||||
}
|
||||
|
||||
function restart (req, res) {
|
||||
console.log('Cancelling current sign')
|
||||
nextAttempt = req.params.attempt
|
||||
exec.execSync('pkill gg18_sign || true')
|
||||
cancelled = true
|
||||
res.send('Cancelled')
|
||||
}
|
||||
|
||||
function connectRabbit (url) {
|
||||
return amqp.connect(url).catch(() => {
|
||||
console.log('Failed to connect, reconnecting')
|
||||
return new Promise(resolve =>
|
||||
|
@ -121,8 +154,8 @@ 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' })
|
||||
function confirmFundsTransfer () {
|
||||
exec.execSync(`curl -X POST -H "Content-Type: application/json" "${PROXY_URL}/confirmFundsTransfer"`, { stdio: 'pipe' })
|
||||
}
|
||||
|
||||
async function getAccountFromFile (file) {
|
||||
|
@ -138,49 +171,44 @@ 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) {
|
||||
cancelled = false
|
||||
console.log(`Waiting for account ${address} to have nonce ${nonce}`)
|
||||
while (true) {
|
||||
while (!cancelled) {
|
||||
const sequence = (await getAccount(address)).sequence
|
||||
if (sequence === nonce)
|
||||
if (sequence >= nonce)
|
||||
break
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
console.log('Waiting for needed account nonce')
|
||||
}
|
||||
console.log('Account nonce is OK')
|
||||
return !cancelled
|
||||
}
|
||||
|
||||
async function getAccount (address) {
|
||||
function getAccount (address) {
|
||||
console.log(`Getting account ${address} data`)
|
||||
return httpClient
|
||||
.get(`/api/v1/account/${address}`)
|
||||
.then(res => res.data)
|
||||
.catch(() => {
|
||||
console.log('Retrying')
|
||||
return getAccount(address)
|
||||
})
|
||||
}
|
||||
|
||||
async function sendTx (tx) {
|
||||
function sendTx (tx) {
|
||||
return httpClient
|
||||
.post(`/api/v1/broadcast?sync=true`, tx, {
|
||||
headers: {
|
||||
'Content-Type': 'text/plain'
|
||||
}
|
||||
})
|
||||
.then(x => console.log(x.response), x => console.log(x.response))
|
||||
.catch(err => {
|
||||
if (err.response.data.message.includes('Tx already exists in cache'))
|
||||
console.log('Tx already exists in cache')
|
||||
else
|
||||
console.log(err.response)
|
||||
})
|
||||
}
|
||||
|
||||
function publicKeyToAddress ({ x, y }) {
|
||||
|
|
|
@ -5,49 +5,59 @@ const BN = require('bignumber.js')
|
|||
const { FOREIGN_CHAIN_ID } = process.env
|
||||
|
||||
class Transaction {
|
||||
constructor (fromAddress, accountNumber, sequence, toAddress, amount, asset, memo = 'test') {
|
||||
const accCode = crypto.decodeAddress(fromAddress)
|
||||
const toAccCode = crypto.decodeAddress(toAddress)
|
||||
constructor (options) {
|
||||
const { from, accountNumber, sequence, to, tokens, asset, bnbs, memo = '' } = options
|
||||
const accCode = crypto.decodeAddress(from)
|
||||
const toAccCode = crypto.decodeAddress(to)
|
||||
|
||||
amount = new BN(amount).multipliedBy(10 ** 8).toNumber()
|
||||
const coins = []
|
||||
|
||||
const coin = {
|
||||
if (tokens && tokens !== '0' && asset) {
|
||||
coins.push({
|
||||
denom: asset,
|
||||
amount: amount,
|
||||
amount: new BN(tokens).multipliedBy(10 ** 8).toNumber(),
|
||||
})
|
||||
}
|
||||
if (bnbs && bnbs !== '0') {
|
||||
coins.push({
|
||||
denom: 'BNB',
|
||||
amount: new BN(bnbs).multipliedBy(10 ** 8).toNumber(),
|
||||
})
|
||||
}
|
||||
|
||||
coins.sort((a, b) => a.denom > b.denom)
|
||||
|
||||
const msg = {
|
||||
inputs: [{
|
||||
address: accCode,
|
||||
coins: [coin]
|
||||
coins
|
||||
}],
|
||||
outputs: [{
|
||||
address: toAccCode,
|
||||
coins: [coin]
|
||||
coins
|
||||
}],
|
||||
msgType: 'MsgSend'
|
||||
}
|
||||
|
||||
this.signMsg = {
|
||||
inputs: [{
|
||||
address: fromAddress,
|
||||
coins: [coin]
|
||||
address: from,
|
||||
coins
|
||||
}],
|
||||
outputs: [{
|
||||
address: toAddress,
|
||||
coins: [coin]
|
||||
address: to,
|
||||
coins
|
||||
}]
|
||||
}
|
||||
|
||||
const options = {
|
||||
this.tx = new TransactionBnc({
|
||||
account_number: accountNumber,
|
||||
chain_id: FOREIGN_CHAIN_ID,
|
||||
memo: memo,
|
||||
memo,
|
||||
msg,
|
||||
sequence,
|
||||
type: msg.msgType,
|
||||
}
|
||||
this.tx = new TransactionBnc(options)
|
||||
})
|
||||
}
|
||||
|
||||
getSignBytes () {
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 1d0c8867aa4d3da0d80de41a60ec3fd6bad2f85a
|
||||
Subproject commit 1b71aae16f2c8c497f8d10067efcf522e6ad575b
|
Loading…
Reference in New Issue