Merge pull request #21 from poanetwork/dynamic-validators
Dynamic validators
This commit is contained in:
commit
5610c0eda2
|
@ -5,12 +5,16 @@ node_modules/
|
|||
**/signature
|
||||
**/params
|
||||
data/
|
||||
demo/validator*/db
|
||||
demo/validator*/keys
|
||||
demo/validator*/queue
|
||||
demo/validator*/development
|
||||
demo/validator*/staging
|
||||
demo/validator*/.keys.staging
|
||||
demo/ganache_home_db/
|
||||
demo/ganache_side_db/
|
||||
demo/*.zip
|
||||
src/deploy/deploy-home/build/
|
||||
src/deploy/deploy-side/build/
|
||||
src/deploy/deploy-test/build/
|
||||
src/deploy/.keys.staging
|
||||
src/test-services/.keys.staging
|
||||
src/test-services/.keys.development
|
||||
test.js
|
||||
|
|
164
DEMO.md
164
DEMO.md
|
@ -2,13 +2,32 @@
|
|||
|
||||
These instructions describes how to run the bridge between an Ethereum-based chain and the Binance Chain testnet.
|
||||
|
||||
### Ethereum side of the bridge
|
||||
|
||||
This demo supports two ways of dealing with the Ethereum side of a bridge:
|
||||
- Using development EVM-base chains ([ganache](https://github.com/trufflesuite/ganache-cli))
|
||||
- Using public networks (predefined configs use Kovan testnet and Sokol POA testnet)
|
||||
|
||||
#### Development mode
|
||||
|
||||
As part of this demo two EVM-based chains ([ganache](https://github.com/trufflesuite/ganache-cli)) will be started:
|
||||
- **Home chain** - it keeps an ERC20 contract (`0x44c158FE850821ae69DaF37AADF5c539e9d0025B`) and the bridge contract (`0x94b40CC641Ed7db241A1f04C8896ba6f6cC36b85`).
|
||||
- **Side chain** - the MPC orchestration contract (`0x44c158FE850821ae69DaF37AADF5c539e9d0025B`) is located here
|
||||
- **Home chain** - it keeps an ERC20 contract (`0xd5fE0D28e058D375b0b038fFbB446Da37E85fFdc`) and the bridge contract (`0x44c158FE850821ae69DaF37AADF5c539e9d0025B`).
|
||||
- **Side chain** - the MPC orchestration contract (`0xd5fE0D28e058D375b0b038fFbB446Da37E85fFdc`) is located here
|
||||
Both chains are run in separate docker containers.
|
||||
|
||||
#### Staging mode
|
||||
|
||||
As part of this demo two EVM-based public chains will be used:
|
||||
- **Home chain** - Kovan testnet keeps an ERC20 contract and the bridge contract.
|
||||
- **Side chain** - Sokol POA testnet keeps the MPC orchestration contract.
|
||||
Interaction with chains is done by using public available RPC urls.
|
||||
|
||||
### Demo validators
|
||||
|
||||
Three validators will be run and only two validators are required to confirm the transfer. Every validator node is a set of docker containers (`eth-watcher`, `bnc-watcher`, `signer`, `proxy`, `redis`, `rabbitmq`).
|
||||
|
||||
### Binance side of the bridge
|
||||
|
||||
The public Binance Chain testnet will keep a BEP2 token.
|
||||
|
||||
### Running demo
|
||||
|
@ -56,28 +75,57 @@ The public Binance Chain testnet will keep a BEP2 token.
|
|||
docker build -t tss -f ./src/tss/Dockerfile-local ./src/tss
|
||||
```
|
||||
2. Run test environment
|
||||
* (2.1) Modify `src/deploy/deploy-test/.env` and specify the amount of tokens to mint in the parameter `TOKEN_INITIAL_MINT`.
|
||||
* (2.2) Run testnets and deploy contracts
|
||||
```
|
||||
./demo/start-environment.sh
|
||||
```
|
||||
This command will also mint tokens, the owner of tokens is the address that corresponds to the private key specified in `PRIVATE_KEY_DEV` of `src/deploy/deploy-test/.env`.
|
||||
* 2.1 Running in development mode (using local ganache networks):
|
||||
* (2.1.1) Modify `src/deploy/deploy-test/.env.development` and specify the amount of tokens to mint in the parameter `TOKEN_INITIAL_MINT`.
|
||||
* (2.1.2) Run testnets and deploy contracts
|
||||
```
|
||||
TARGET_NETWORK=development ./demo/start-environment.sh
|
||||
```
|
||||
This command will also mint tokens, the owner of tokens is the address that corresponds to the
|
||||
private key specified in `HOME_PRIVATE_KEY` of `src/deploy/deploy-test/.env.development` (`0xA374DC09057D6B3253d04fACb15736B43fBc7943`).
|
||||
* 2.2 Running in staging mode (using public test networks):
|
||||
* (2.2.1) Prepare three private keys for validators. Get the Ethereum account addresses for these keys.
|
||||
* (2.2.2) Modify `src/deploy/deploy-home/.env.staging` and specify the token contract address in
|
||||
the Kovan network via `HOME_TOKEN_ADDRESS` (use empty address `0x` if you want to create new
|
||||
ERC20 contract while deployment). \
|
||||
Set `VALIDATOR_ADDRESS_*` to Ethereum addresses obtained in the previous step.
|
||||
* (2.2.3) Modify `src/deploy/.keys.staging` and specify private keys for prefunded accounts in both networks.
|
||||
These accounts are used for contract deployment. Use `src/deploy/.keys.staging.example` as an example.
|
||||
* (2.2.4) Deploy contracts
|
||||
```
|
||||
TARGET_NETWORK=staging ./demo/start-environment.sh
|
||||
```
|
||||
This command will deploy ERC20 contract and also mint tokens if you left `HOME_TOKEN_ADDRESS` empty,
|
||||
the owner of tokens is the address that corresponds to the private key specified in
|
||||
`HOME_PRIVATE_KEY` of `src/deploy/.keys.staging`.\
|
||||
Deployed contract address will be automatically updates in all required validators
|
||||
and test services configs.
|
||||
* (2.2.5) Prefund validator accounts in home network (Kovan):
|
||||
```
|
||||
TARGET_NETWORK=staging ./src/test-services/ethereumSend/run.sh <Nth validator address> 0 0.5
|
||||
```
|
||||
* (2.2.6) Prefund validator accounts in side network (Sokol):
|
||||
```
|
||||
TARGET_NETWORK=staging ./src/test-services/sidePrefund/run.sh <Nth validator address> 1
|
||||
```
|
||||
* (2.3) Get the Ethereum account address for the first test account from its private key (step 1.2). [NiftyWallet](https://forum.poa.network/c/nifty-wallet) could be used for this.
|
||||
* (2.4) Send few tokens and coins from the current token owner to the first account. Coins are needed to pay transaction fees.
|
||||
```
|
||||
./src/test-services/ethereumSend/run.sh <first account Ethereum address> 5000000000000000000 0.5
|
||||
TARGET_NETWORK=<target network> ./src/test-services/ethereumSend/run.sh <first account Ethereum address> 5000000000000000000 0.5
|
||||
```
|
||||
* (2.5) Check that the tokens were transferred properly:
|
||||
```
|
||||
./src/test-services/ethereumBalance/run.sh <first account Ethereum address>
|
||||
TARGET_NETWORK=<target network> ./src/test-services/ethereumBalance/run.sh <first account Ethereum address>
|
||||
```
|
||||
3. Run validators nodes:
|
||||
* (3.1) Modify the parameter `FOREIGN_ASSET` in `demo/validator1/.env`, `demo/validator2/.env` and `demo/validator3/.env` to specify the identificator of the token (step 1.8) that the oracle will watch.
|
||||
* (3.1) Modify the parameter `FOREIGN_ASSET` in `demo/validator1/.env.<network>`, `demo/validator2/.env.<network>`
|
||||
and `demo/validator3/.env.<network>` to specify the identificator of the token (step 1.8) that the oracle will watch. \
|
||||
For staging environment additionally specify `VALIDATOR_PRIVATE_KEY` in the `demo/validator<N>/.keys.staging` (step 2.2.1)
|
||||
* (3.2) Run three validators in separate terminal sessions.
|
||||
```
|
||||
N=1 ./demo/validator-demo.sh
|
||||
N=2 ./demo/validator-demo.sh
|
||||
N=3 ./demo/validator-demo.sh
|
||||
N=1 TARGET_NETWORK=<network> ./demo/validator-demo.sh
|
||||
N=2 TARGET_NETWORK=<network> ./demo/validator-demo.sh
|
||||
N=3 TARGET_NETWORK=<network> ./demo/validator-demo.sh
|
||||
```
|
||||
Wait for when the line like the following appears:
|
||||
```
|
||||
|
@ -105,15 +153,17 @@ The public Binance Chain testnet will keep a BEP2 token.
|
|||
```
|
||||
To check the balance of the bridge account the [Binance Testnet Explorer](https://testnet-explorer.binance.org) could be used. It should report about two assets owned by the account.
|
||||
5. Transfer tokens from Ethereum-based chain to the Binance Chain:
|
||||
* (5.1) Modify the parameter `HOME_PRIVATE_KEY` as so it contains the private key of the first test account (step 1.2)
|
||||
* (5.1) Modify the parameter `HOME_PRIVATE_KEY`
|
||||
(in `src/test-services/ethereumSend/.env.development` or `src/test-services/.keys.staging`)
|
||||
as so it contains the private key of the first test account (step 1.2)
|
||||
* (5.2) Send some amount of tokens to the bridge contract:
|
||||
```
|
||||
./src/test-services/ethereumSend/run.sh bridge 5000000000000000000
|
||||
TARGET_NETWORK=<network> ./src/test-services/ethereumSend/run.sh bridge 5000000000000000000
|
||||
```
|
||||
* (5.3) The validators will catch the event and start the process to sign the transaction.
|
||||
* (5.4) As soon as the signature is generated and sent, the balance of the bridge account in both chains will be changed:
|
||||
```
|
||||
./src/test-services/ethereumBalance/run.sh 0x94b40CC641Ed7db241A1f04C8896ba6f6cC36b85
|
||||
TARGET_NETWORK=<network> ./src/test-services/ethereumBalance/run.sh 0x94b40CC641Ed7db241A1f04C8896ba6f6cC36b85
|
||||
```
|
||||
should report non-zero balance
|
||||
```
|
||||
|
@ -131,6 +181,76 @@ The public Binance Chain testnet will keep a BEP2 token.
|
|||
--node=data-seed-pre-2-s1.binance.org:80 --memo "any note"
|
||||
```
|
||||
* Check the balances of the test account on both sides of the bridge to see that the funds were transferred properly.
|
||||
7. Bridge supports changing the list of validators and required voting threshold via voting process, and then keys regeneration.
|
||||
* (7.0) Obtain information about current epoch, current list validators, upcoming epoch information, bridge state via:
|
||||
```
|
||||
./curl http://localhost:$PORT/info
|
||||
```
|
||||
Where `$PORT` is specific port for some validator oracle.
|
||||
The response object contains lots of useful information about current bridge state.
|
||||
```json5
|
||||
{
|
||||
// current epoch number, in which bridge is operating
|
||||
"epoch": 2,
|
||||
|
||||
// next epoch number, for which votes and keygen operations are applied
|
||||
"nextEpoch": 3,
|
||||
|
||||
// threshold number for current epoch,
|
||||
// threshold + 1 votes are required for any changes in next epoch
|
||||
"threshold": 1,
|
||||
|
||||
// threshold number for next epoch
|
||||
"nextThreshold": 1,
|
||||
|
||||
// current bridge addresses in home and foreign networks
|
||||
"homeBridgeAddress": "0x44c158FE850821ae69DaF37AADF5c539e9d0025B",
|
||||
"foreignBridgeAddress": "tbnb19z22khee969yj05dckg9usvmwndkucpyl543xk",
|
||||
|
||||
// current set of validators
|
||||
"validators": [
|
||||
"0x99Eb3D86663c6Db090eFFdBC20510Ca9f836DCE3",
|
||||
"0x6352e3e6038e05b9da00C84AE851308f9774F883"
|
||||
],
|
||||
|
||||
// set of validators for the next epoch
|
||||
"nextValidators": [
|
||||
"0x99Eb3D86663c6Db090eFFdBC20510Ca9f836DCE3",
|
||||
"0x6352e3e6038e05b9da00C84AE851308f9774F883",
|
||||
"0xAa006899B0EC407De930bA8A166DEfe59bBfd3DC"
|
||||
],
|
||||
|
||||
// balances of bridge in both networks
|
||||
"homeBalance": 50,
|
||||
"foreignBalanceTokens": 100,
|
||||
"foreignBalanceNative": 0.0994,
|
||||
|
||||
// current bridge status, can be one of: ready, voting, keygen, funds_transfer
|
||||
"bridgeStatus": "ready",
|
||||
|
||||
// current votes count for starting voting, starting/cancelling keygen
|
||||
// -1 means that enough confirmations are already collected
|
||||
"votesForVoting": 0,
|
||||
"votesForKeygen": 0,
|
||||
"votesForCancelKeygen": 0,
|
||||
|
||||
// collected confirmations for changing epoch to nextEpoch
|
||||
// -1 means that enough confirmations are already collected
|
||||
"confirmationsForFundsTransfer": 0
|
||||
}
|
||||
```
|
||||
* (7.1) Start voting process for next epoch, via sending `$THRESHOLD + 1` requests to `/vote/startVoting` url. Bridge
|
||||
state should be successfully changed to `voting`.
|
||||
* 7.2 Changing next epoch bridge validators / threshold
|
||||
* (7.2.1) Add / remove validator in next validators list, via sending `$THRESHOLD + 1` requests to
|
||||
`/vote/addValidator/$ADDRESS` / `/vote/removeValidator/$ADDRESS`.
|
||||
* (7.2.2) Change threshold for the next epoch, via sending `$THRESHOLD + 1` requests to `/vote/changeThreshold/$THRESHOLD`.
|
||||
* (7.3) Start keygen process for next epoch, via sending `$THRESHOLD + 1` requests to `/vote/startKeygen` url. Bridge
|
||||
state should be successfully changed to `keygen`, and in some time to `funds_transfer`, and then to `ready`.
|
||||
* (7.4) If keygen process at some state was stopped(i. e. one validator turned of his oracle),
|
||||
it can be cancelled via via sending `$THRESHOLD + 1` requests to `/vote/cancelKeygen` url. After
|
||||
keygen cancellation, bridge state will return to `voting`, and later it can be restarted manually
|
||||
once again.
|
||||
|
||||
### Finish demo
|
||||
|
||||
|
@ -150,7 +270,7 @@ The public Binance Chain testnet will keep a BEP2 token.
|
|||
```
|
||||
4. Remove testnets and validators data:
|
||||
```
|
||||
./demo/clean.sh
|
||||
TARGET_NETWORK=<network> ./demo/clean.sh
|
||||
```
|
||||
|
||||
#### Testing tools for both sides of the bridge
|
||||
|
@ -163,10 +283,14 @@ In these tools, `run.sh` file simply builds and runs a docker container for inte
|
|||
- `NATIVE` - amount of BNB tokens to send, if present, the
|
||||
transaction is considered as a funding one.
|
||||
* `./src/test-services/ethereumSend/run.sh TO TOKENS NATIVE`
|
||||
- Transfers specified amount of tokens and coins to the an Ethereum account.
|
||||
- Transfers specified amount of tokens and coins to the an Ethereum account on the home network.
|
||||
- `TO` - receiver address in the Ethereum-based chain, specify `bridge` to send tokens to the bridge address.
|
||||
- `VALUE` - amount of tokens to transfer and exchange.
|
||||
- `NATIVE` - amount of coins to send (in `ether`). Could be omitted.
|
||||
* `./src/test-services/sidePrefund/run.sh TO NATIVE`
|
||||
- Transfers specified amount of tokens and coins to the an Ethereum account on the side network.
|
||||
- `TO` - receiver address in the Ethereum-based chain.
|
||||
- `NATIVE` - amount of coins to send (in `ether`). Could be omitted.
|
||||
* `./src/test-services/binanceBalance/run.sh ADDRESS` (it is recommended to use `tbnbcli` instead)
|
||||
- Gets current BEP2 token and BNB balances of the specified account.
|
||||
- `ADDRESS` - account address in the Binance Chain.
|
||||
|
@ -194,4 +318,4 @@ container for listening GET requests
|
|||
- http://localhost:5001/vote/addValidator/ADDRESS
|
||||
- `ADDRESS` - Ethereum address of a validator.
|
||||
- After enough votes are collected, validator is removed from
|
||||
the next validators list for the next epoch.
|
||||
the next validators list for the next epoch.
|
||||
|
|
|
@ -4,15 +4,22 @@ set -e
|
|||
|
||||
cd $(dirname "$0")
|
||||
|
||||
rm -rf ganache_side_db
|
||||
rm -rf ganache_home_db
|
||||
# either development or staging
|
||||
TARGET_NETWORK=${TARGET_NETWORK:=development}
|
||||
|
||||
echo "Cleaning $TARGET_NETWORK network"
|
||||
|
||||
if [[ "$TARGET_NETWORK" == "development" ]]; then
|
||||
rm -rf ganache_side_db
|
||||
rm -rf ganache_home_db
|
||||
mkdir ganache_side_db
|
||||
mkdir ganache_home_db
|
||||
fi
|
||||
|
||||
for (( I = 1; I < 4; ++I )); do
|
||||
DIRNAME="validator$I"
|
||||
rm -rf "$DIRNAME/db"
|
||||
rm -rf "$DIRNAME/queue"
|
||||
rm -rf "$DIRNAME/keys"
|
||||
mkdir "$DIRNAME/db"
|
||||
mkdir "$DIRNAME/queue"
|
||||
mkdir "$DIRNAME/keys"
|
||||
rm -rf "$DIRNAME/$TARGET_NETWORK"
|
||||
mkdir -p "$DIRNAME/$TARGET_NETWORK/db"
|
||||
mkdir -p "$DIRNAME/$TARGET_NETWORK/queue"
|
||||
mkdir -p "$DIRNAME/$TARGET_NETWORK/keys"
|
||||
done
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
cd $(dirname "$0")
|
||||
|
||||
# either development or staging
|
||||
TARGET_NETWORK=${TARGET_NETWORK:=development}
|
||||
|
||||
echo "Resetting block in redis"
|
||||
|
||||
docker network create redis_net > /dev/null 2>&1 || true
|
||||
docker kill redis > /dev/null 2>&1 || true
|
||||
docker rm redis > /dev/null 2>&1 || true
|
||||
docker run --rm --network redis_net -d --name redis \
|
||||
-v "`pwd`/validator$N/$TARGET_NETWORK/db:/data" \
|
||||
-v "`dirname "$(pwd)"`/src/oracle/configs/redis.conf:/usr/local/etc/redis/redis.conf" \
|
||||
redis:5.0.5-alpine > /dev/null 2>&1 || true
|
||||
|
||||
../src/oracle/scripts/resetToBlock/run.sh redis_net $1
|
||||
|
||||
docker kill redis > /dev/null 2>&1 || true
|
|
@ -0,0 +1,417 @@
|
|||
ObjC.import('stdlib')
|
||||
|
||||
const terminal = Application('Terminal')
|
||||
const system = Application('System Events')
|
||||
const curApp = Application.currentApplication()
|
||||
curApp.includeStandardAdditions = true
|
||||
|
||||
const validator1 = '0x99Eb3D86663c6Db090eFFdBC20510Ca9f836DCE3'
|
||||
const validator2 = '0xAa006899B0EC407De930bA8A166DEfe59bBfd3DC'
|
||||
const validator3 = '0x6352e3e6038e05b9da00C84AE851308f9774F883'
|
||||
|
||||
const userAccounts = [
|
||||
{
|
||||
privateKey: '7ed93ad7753e00b52265a73dfbbcd2296256772965323fcb9a6320b5cd084b89',
|
||||
ethAddress: '0x4db6b4bd0a3fdc03b027a60f1c48f05c572312aa',
|
||||
bncAddress: 'tbnb14r3z8xk7qsar3vwj05w8cd8gqwk7g6gfurlt5l'
|
||||
},
|
||||
{
|
||||
privateKey: '2ad6e3a232ad3ea058b61352302118a99085600ff8b6eec4ccf0066a33756231',
|
||||
ethAddress: '0xf7ca4aed1795e424433498cef43f6a3825c88731',
|
||||
bncAddress: 'tbnb1efjg7xt98t67ql2cmwjc5860lgayet9l8m55ym'
|
||||
},
|
||||
{
|
||||
privateKey: 'eb6dd328677b3fa2822fb8e834507e569bda52e8ffa49266df0f2de239c4ec98',
|
||||
ethAddress: '0xad6c8127143032d843a260c5d379d8d9b3d51f15',
|
||||
bncAddress: 'tbnb12epcy4p7ktas0nlyrfuektcyh0e83dwzuq73f4'
|
||||
}
|
||||
]
|
||||
|
||||
let bridgeBncAddress
|
||||
|
||||
const windows = terminal.windows()
|
||||
|
||||
const wins = []
|
||||
|
||||
function saveBlockchainData () {
|
||||
console.log('Saving blockchain data')
|
||||
curApp.doShellScript('zip -r ./demo/ganache_home_backup.zip ./demo/ganache_home_db')
|
||||
curApp.doShellScript('zip -r ./demo/ganache_side_backup.zip ./demo/ganache_side_db')
|
||||
}
|
||||
|
||||
function reloadBlockchainData () {
|
||||
console.log('Reloading blockchain data')
|
||||
curApp.doShellScript('unzip -d . ./demo/ganache_home_backup.zip')
|
||||
curApp.doShellScript('unzip -d . ./demo/ganache_side_backup.zip')
|
||||
}
|
||||
|
||||
function closeOldWindows () {
|
||||
for (let i in windows) {
|
||||
try {
|
||||
windows[i].selectedTab()
|
||||
|
||||
if (windows[i].selectedTab().customTitle().startsWith('Validator') || windows[i].selectedTab().customTitle() === 'Services') {
|
||||
windows[i].close()
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function killValidators () {
|
||||
terminal.activate()
|
||||
for (let i = 0; i < 3; i++) {
|
||||
wins[i].frontmost = true
|
||||
delay(0.5)
|
||||
system.keystroke('c', { using: 'control down' })
|
||||
}
|
||||
}
|
||||
|
||||
function openNewWindows () {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
// open new terminal
|
||||
const tab = terminal.doScript()
|
||||
// get opened window
|
||||
const winId = terminal.windows[0].id()
|
||||
wins[i] = terminal.windows.byId(winId)
|
||||
tab.customTitle = `Validator ${i + 1}`
|
||||
}
|
||||
wins[0].bounds = { x: 0, y: 23, width: 558, height: 1027 }
|
||||
wins[1].bounds = { x: 559, y: 374, width: 560, height: 676 }
|
||||
wins[2].bounds = { x: 1120, y: 374, width: 560, height: 676 }
|
||||
// open new terminal
|
||||
const tab = terminal.doScript()
|
||||
// get opened window
|
||||
const winId = terminal.windows[0].id()
|
||||
wins[3] = terminal.windows.byId(winId)
|
||||
tab.customTitle = `Services`
|
||||
wins[3].bounds = { x: 559, y: 23, width: 1120, height: 350 }
|
||||
|
||||
terminal.activate()
|
||||
delay(0.5)
|
||||
}
|
||||
|
||||
function apiRequestBackground (url) {
|
||||
const response = curApp.doShellScript(`curl -s -X GET "${url}"`)
|
||||
try {
|
||||
return JSON.parse(response)
|
||||
} catch (e) {
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
function exec (n, script) {
|
||||
terminal.doScript(script, { in: wins[n - 1] })
|
||||
}
|
||||
|
||||
function wait (n) {
|
||||
while (wins[n - 1].selectedTab().busy()) {
|
||||
delay(0.2)
|
||||
}
|
||||
}
|
||||
|
||||
function execSync (n, script) {
|
||||
exec(n, script)
|
||||
wait(n)
|
||||
}
|
||||
|
||||
function waitAll () {
|
||||
wait(1)
|
||||
wait(2)
|
||||
wait(3)
|
||||
wait(4)
|
||||
}
|
||||
|
||||
function waitLog (n, log) {
|
||||
do {
|
||||
const s = wins[n - 1].selectedTab().contents().split('\n').find(x => x.includes(log))
|
||||
if (s) {
|
||||
return s
|
||||
}
|
||||
delay(0.2)
|
||||
} while (true)
|
||||
}
|
||||
|
||||
function waitApi (n, url, check) {
|
||||
do {
|
||||
const res = apiRequestBackground(`http://localhost:500${n}${url}`)
|
||||
const checkerRes = check ? check(res) : true
|
||||
if (checkerRes)
|
||||
return checkerRes
|
||||
delay(3)
|
||||
} while (true)
|
||||
}
|
||||
|
||||
function prefundEthAddresses () {
|
||||
for (let { ethAddress } of userAccounts) {
|
||||
execSync(4, `./src/test-services/ethereumSend/run.sh ${ethAddress} 100`)
|
||||
}
|
||||
}
|
||||
|
||||
function prefundBncAddresses () {
|
||||
for (let { bncAddress } of userAccounts) {
|
||||
execSync(4, `./src/test-services/binanceSend/run.sh ${bncAddress} 100 0.1`)
|
||||
}
|
||||
}
|
||||
|
||||
function initBalances () {
|
||||
userAccounts.forEach(account => {
|
||||
account.ethBalance = getEthTokenBalance(account.ethAddress)
|
||||
account.bncBalance = getBncTokenBalance(account.bncAddress)
|
||||
})
|
||||
}
|
||||
|
||||
function getBncTokenBalance (address) {
|
||||
const res = curApp.doShellScript(`./src/test-services/binanceBalance/run.sh ${address}`)
|
||||
return parseFloat(/KFT-94F: [0-9.]+/.exec(res)[0].split(' ')[1])
|
||||
}
|
||||
|
||||
function waitBncTokenBalance (address, balance) {
|
||||
while (true) {
|
||||
const newBalance = getBncTokenBalance(address)
|
||||
if (Math.abs(newBalance - balance) < 0.0001)
|
||||
return newBalance
|
||||
delay(3)
|
||||
}
|
||||
}
|
||||
|
||||
function getEthTokenBalance (address) {
|
||||
const res = curApp.doShellScript(`./src/test-services/ethereumBalance/run.sh ${address}`)
|
||||
return parseFloat(/[0-9.]+ tokens/.exec(res)[0].split(' ')[0])
|
||||
}
|
||||
|
||||
function waitEthTokenBalance (address, balance) {
|
||||
while (true) {
|
||||
const newBalance = getEthTokenBalance(address)
|
||||
if (Math.abs(newBalance - balance) < 0.0001)
|
||||
return newBalance
|
||||
delay(3)
|
||||
}
|
||||
}
|
||||
|
||||
function apiRequest (n, url, suffix) {
|
||||
execSync(4, `curl -s -X GET http://localhost:500${n}${url} ${suffix ? suffix : ''}`)
|
||||
}
|
||||
|
||||
function printState (msg) {
|
||||
execSync(4, `echo "${msg}"`)
|
||||
apiRequest(1, '/info', '| jq .')
|
||||
}
|
||||
|
||||
function initCwd () {
|
||||
const cwd = $.getenv('PWD')
|
||||
|
||||
for (let i = 1; i <= 4; i++) {
|
||||
exec(i, `cd "${cwd}"`)
|
||||
}
|
||||
waitAll()
|
||||
}
|
||||
|
||||
function killDockerContainers () {
|
||||
execSync(4, `docker kill $(docker ps | grep validator | awk '{print $1}') > /dev/null 2>&1 || true`)
|
||||
execSync(4, `docker kill ganache_side ganache_home > /dev/null 2>&1 || true`)
|
||||
}
|
||||
|
||||
function clean () {
|
||||
killDockerContainers()
|
||||
execSync(4, `./demo/clean.sh`)
|
||||
|
||||
exec(1, `clear`)
|
||||
exec(2, `clear`)
|
||||
exec(3, `clear`)
|
||||
waitAll()
|
||||
}
|
||||
|
||||
function testEthToBnc () {
|
||||
console.log('Testing eth => bnc')
|
||||
// try token transfer in eth => bnc direction
|
||||
let prevBridgeHomeBalance
|
||||
let prevBridgeForeignBalance
|
||||
waitApi(1, '/info', res => {
|
||||
prevBridgeHomeBalance = res.homeBalance
|
||||
prevBridgeForeignBalance = res.foreignBalanceTokens
|
||||
return true
|
||||
})
|
||||
|
||||
userAccounts.forEach((account, i) => {
|
||||
execSync(4, `PRIVATE_KEY=${account.privateKey} ./src/test-services/ethereumSend/run.sh bridge ${5 + i}`)
|
||||
account.ethBalance -= 5 + i
|
||||
})
|
||||
|
||||
const delta = (9 + userAccounts.length) * userAccounts.length / 2
|
||||
waitApi(1, '/info', res => res.homeBalance === prevBridgeHomeBalance + delta && res.foreignBalanceTokens === prevBridgeForeignBalance - delta)
|
||||
|
||||
userAccounts.forEach((account, i) => {
|
||||
account.bncBalance = waitBncTokenBalance(account.bncAddress, account.bncBalance + 5 + i)
|
||||
})
|
||||
|
||||
printState(`Token transfer in eth => bnc direction succeed`)
|
||||
console.log('Testing eth => bnc is OK')
|
||||
}
|
||||
|
||||
function testBncToEth () {
|
||||
console.log('Testing bnc => eth')
|
||||
// try token transfer in bnc => eth direction
|
||||
let prevBridgeHomeBalance
|
||||
let prevBridgeForeignBalance
|
||||
waitApi(1, '/info', res => {
|
||||
prevBridgeHomeBalance = res.homeBalance
|
||||
prevBridgeForeignBalance = res.foreignBalanceTokens
|
||||
return true
|
||||
})
|
||||
|
||||
userAccounts.forEach((account , i) => {
|
||||
execSync(4, `PRIVATE_KEY=${account.privateKey} ./src/test-services/binanceSend/run.sh ${bridgeBncAddress} ${3 + i}`)
|
||||
account.bncBalance -= 3 + i
|
||||
})
|
||||
|
||||
const delta = (5 + userAccounts.length) * userAccounts.length / 2
|
||||
|
||||
waitApi(1, '/info', res => res.homeBalance === prevBridgeHomeBalance - delta && res.foreignBalanceTokens === prevBridgeForeignBalance + delta)
|
||||
|
||||
userAccounts.forEach((account, i) => {
|
||||
account.ethBalance = waitEthTokenBalance(account.ethAddress, account.ethBalance + 3 + i)
|
||||
})
|
||||
|
||||
printState(`Token transfer in bnc => eth direction succeed`)
|
||||
console.log('Testing bnc => eth is OK')
|
||||
}
|
||||
|
||||
function testRemoveValidator () {
|
||||
console.log('Testing removing validator')
|
||||
apiRequest(1, '/vote/startVoting')
|
||||
apiRequest(2, '/vote/startVoting')
|
||||
waitApi(1, '/info', res => res.bridgeStatus === 'voting')
|
||||
apiRequest(1, `/vote/removeValidator/${validator2}`)
|
||||
apiRequest(3, `/vote/removeValidator/${validator2}`)
|
||||
waitApi(1, '/info', res => res.nextValidators.length === 2)
|
||||
apiRequest(1, '/vote/startKeygen')
|
||||
apiRequest(3, '/vote/startKeygen')
|
||||
waitApi(1, '/info', res => {
|
||||
if (res.bridgeStatus === 'ready' && res.epoch === 2 && res.validators.length === 2) {
|
||||
bridgeBncAddress = res.foreignBridgeAddress
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
printState(`Removing validator succeed`)
|
||||
console.log('Testing removing validator is OK')
|
||||
}
|
||||
|
||||
function testAddValidator () {
|
||||
console.log('Testing adding validator')
|
||||
apiRequest(1, '/vote/startVoting')
|
||||
apiRequest(3, '/vote/startVoting')
|
||||
waitApi(1, '/info', res => res.bridgeStatus === 'voting')
|
||||
apiRequest(1, `/vote/addValidator/${validator2}`)
|
||||
apiRequest(3, `/vote/addValidator/${validator2}`)
|
||||
waitApi(1, '/info', res => res.nextValidators.length === 3)
|
||||
apiRequest(1, '/vote/startKeygen')
|
||||
apiRequest(3, '/vote/startKeygen')
|
||||
waitApi(1, '/info', res => {
|
||||
if (res.bridgeStatus === 'ready' && res.epoch === 3 && res.validators.length === 3) {
|
||||
bridgeBncAddress = res.foreignBridgeAddress
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
printState(`Adding validator succeed`)
|
||||
console.log('Testing adding validator is OK')
|
||||
}
|
||||
|
||||
function testChangeThreshold () {
|
||||
console.log('Testing changing threshold')
|
||||
apiRequest(1, '/vote/startVoting')
|
||||
apiRequest(3, '/vote/startVoting')
|
||||
waitApi(1, '/info', res => res.bridgeStatus === 'voting')
|
||||
apiRequest(2, `/vote/changeThreshold/2`)
|
||||
apiRequest(3, `/vote/changeThreshold/2`)
|
||||
waitApi(1, '/info', res => res.nextThreshold === 2)
|
||||
apiRequest(1, '/vote/startKeygen')
|
||||
apiRequest(2, '/vote/startKeygen')
|
||||
waitApi(1, '/info', res => {
|
||||
if (res.bridgeStatus === 'ready' && res.epoch === 4 && res.threshold === 2) {
|
||||
bridgeBncAddress = res.foreignBridgeAddress
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
printState(`Changing threshold succeed`)
|
||||
console.log('Testing changing threshold is OK')
|
||||
}
|
||||
|
||||
function run () {
|
||||
closeOldWindows()
|
||||
|
||||
openNewWindows()
|
||||
|
||||
initCwd()
|
||||
|
||||
clean()
|
||||
|
||||
if ($.getenv('RELOAD') !== 'true') {
|
||||
execSync(4, `./demo/start-environment.sh`)
|
||||
|
||||
prefundEthAddresses()
|
||||
|
||||
saveBlockchainData()
|
||||
} else {
|
||||
reloadBlockchainData()
|
||||
|
||||
execSync(4, `./demo/start-environment.sh`)
|
||||
}
|
||||
|
||||
prefundBncAddresses()
|
||||
|
||||
initBalances()
|
||||
|
||||
exec(1, `N=1 ./demo/validator-demo.sh`)
|
||||
exec(2, `N=2 ./demo/validator-demo.sh`)
|
||||
exec(3, `N=3 ./demo/validator-demo.sh`)
|
||||
|
||||
// wait until binance account willl be generated
|
||||
waitLog(1, 'Generated multisig account in binance chain')
|
||||
waitApi(1, '/info', res => {
|
||||
if (res.epoch === 1) {
|
||||
bridgeBncAddress = res.foreignBridgeAddress
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
// prefund binance account
|
||||
execSync(4, `./src/test-services/binanceSend/run.sh ${bridgeBncAddress} 100 0.1`)
|
||||
|
||||
// wait until binance prefund transaction will be processed
|
||||
waitApi(1, '/info', res => res.foreignBalanceTokens === 100)
|
||||
|
||||
printState(`Binance bridge account at ${bridgeBncAddress} for epoch 1 is generated and prefunded`)
|
||||
|
||||
testEthToBnc()
|
||||
|
||||
testBncToEth()
|
||||
|
||||
testRemoveValidator()
|
||||
|
||||
testEthToBnc()
|
||||
|
||||
testBncToEth()
|
||||
|
||||
testAddValidator()
|
||||
|
||||
testEthToBnc()
|
||||
|
||||
testBncToEth()
|
||||
|
||||
testChangeThreshold()
|
||||
|
||||
testEthToBnc()
|
||||
|
||||
testBncToEth()
|
||||
|
||||
console.log('PASSED ALL TESTS')
|
||||
|
||||
killValidators()
|
||||
|
||||
killDockerContainers()
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
#!/bin/bash
|
||||
|
||||
RELOAD=${RELOAD:=false}
|
||||
|
||||
RELOAD="$RELOAD" osascript -l JavaScript ./demo/scenarios/macos/main.jxa
|
|
@ -3,78 +3,165 @@
|
|||
set -e
|
||||
|
||||
cd $(dirname "$0")
|
||||
cd ..
|
||||
|
||||
start_blockchains() {
|
||||
# either development or staging
|
||||
TARGET_NETWORK=${TARGET_NETWORK:=development}
|
||||
|
||||
DEPLOY_DIR="`pwd`/src/deploy"
|
||||
TEST_SERVICES_DIR="`pwd`/src/test-services"
|
||||
DEMO_DIR="`pwd`/demo"
|
||||
|
||||
SIDE_GANACHE_DB="$DEMO_DIR/ganache_side_db"
|
||||
HOME_GANACHE_DB="$DEMO_DIR/ganache_home_db"
|
||||
|
||||
start_dev_blockchain_networks() {
|
||||
echo "Starting side test blockchain"
|
||||
|
||||
docker kill ganache_side > /dev/null 2>&1 || true
|
||||
docker network create blockchain_side > /dev/null 2>&1 || true
|
||||
docker run -d --network blockchain_side --rm --name ganache_side -v "$side_db_mount_point:/app/db" \
|
||||
docker run -d --network blockchain_side --rm --name ganache_side -v "$SIDE_GANACHE_DB:/app/db" \
|
||||
-p "7545:8545" \
|
||||
trufflesuite/ganache-cli:latest \
|
||||
-m "shrug dwarf easily blade trigger lucky reopen cage lake scatter desk boat" -i 33 -q --db /app/db
|
||||
-m "shrug dwarf easily blade trigger lucky reopen cage lake scatter desk boat" -i 33 -q --db /app/db -b 3 --noVMErrorsOnRPCResponse
|
||||
|
||||
echo "Starting home test blockchain"
|
||||
|
||||
docker kill ganache_home > /dev/null 2>&1 || true
|
||||
docker network create blockchain_home > /dev/null 2>&1 || true
|
||||
docker run -d --network blockchain_home --rm --name ganache_home -v "$home_db_mount_point:/app/db" \
|
||||
docker run -d --network blockchain_home --rm --name ganache_home -v "$HOME_GANACHE_DB:/app/db" \
|
||||
-p "8545:8545" \
|
||||
trufflesuite/ganache-cli:latest \
|
||||
-m "shrug dwarf easily blade trigger lucky reopen cage lake scatter desk boat" -i 44 -q --db /app/db
|
||||
-m "shrug dwarf easily blade trigger lucky reopen cage lake scatter desk boat" -i 44 -q --db /app/db -b 3 --noVMErrorsOnRPCResponse
|
||||
|
||||
sleep 4
|
||||
}
|
||||
|
||||
deploy_all() {
|
||||
deploy_token() {
|
||||
echo "Compiling and deploying erc20"
|
||||
|
||||
cd ../src/deploy/deploy-test
|
||||
|
||||
echo "Building deploy docker image"
|
||||
docker build -t deploy_test . > /dev/null 2>&1
|
||||
docker build -t deploy_test "$DEPLOY_DIR/deploy-test" > /dev/null 2>&1
|
||||
|
||||
echo "Deploying"
|
||||
docker run --network blockchain_home --rm -v "$(pwd)/build:/build/build" --env-file .env deploy_test --network development --reset > /dev/null 2>&1
|
||||
|
||||
|
||||
if [[ "$TARGET_NETWORK" == "development" ]]; then
|
||||
TOKEN_ADDRESS=$(docker run --network blockchain_home --rm -v "$DEPLOY_DIR/deploy-test/build:/build/build" --env-file "$DEPLOY_DIR/deploy-test/.env.$TARGET_NETWORK" \
|
||||
deploy_test \
|
||||
--network home 2>&1 \
|
||||
| grep "contract address" \
|
||||
| awk '{print $4}')
|
||||
else
|
||||
TOKEN_ADDRESS=$(docker run --rm -v "$DEPLOY_DIR/deploy-test/build:/build/build" --env-file "$DEPLOY_DIR/deploy-test/.env.$TARGET_NETWORK" --env-file "$DEPLOY_DIR/.keys.$TARGET_NETWORK" \
|
||||
deploy_test \
|
||||
--network home 2>&1 \
|
||||
| grep "contract address" \
|
||||
| awk '{print $4}')
|
||||
fi
|
||||
}
|
||||
|
||||
deploy_bridge() {
|
||||
echo "Compiling and deploying home part"
|
||||
|
||||
cd ../deploy-home
|
||||
|
||||
echo "Building deploy docker image"
|
||||
docker build -t deploy_home . > /dev/null 2>&1
|
||||
docker build -t deploy_home "$DEPLOY_DIR/deploy-home" > /dev/null 2>&1
|
||||
|
||||
echo "Deploying"
|
||||
docker run --network blockchain_home --rm -v "$(pwd)/build:/build/build" --env-file .env deploy_home --network development --reset > /dev/null 2>&1
|
||||
|
||||
|
||||
if [[ "$TARGET_NETWORK" == "development" ]]; then
|
||||
BRIDGE_ADDRESS=$(docker run --network blockchain_home --rm -v "$DEPLOY_DIR/deploy-home/build:/build/build" --env-file "$DEPLOY_DIR/deploy-home/.env.$TARGET_NETWORK" \
|
||||
deploy_home \
|
||||
--network home 2>&1 \
|
||||
| grep "contract address" \
|
||||
| awk '{print $4}')
|
||||
else
|
||||
BRIDGE_ADDRESS=$(docker run --rm -v "$DEPLOY_DIR/deploy-home/build:/build/build" --env-file "$DEPLOY_DIR/deploy-home/.env.$TARGET_NETWORK" --env-file "$DEPLOY_DIR/.keys.$TARGET_NETWORK" \
|
||||
deploy_home \
|
||||
--network home 2>&1 \
|
||||
| grep "contract address" \
|
||||
| awk '{print $4}')
|
||||
fi
|
||||
}
|
||||
|
||||
deploy_db() {
|
||||
echo "Compiling and deploying side part"
|
||||
|
||||
cd ../deploy-side
|
||||
|
||||
echo "Building deploy docker image"
|
||||
docker build -t deploy_side . > /dev/null 2>&1
|
||||
docker build -t deploy_side "$DEPLOY_DIR/deploy-side" > /dev/null 2>&1
|
||||
|
||||
echo "Deploying"
|
||||
docker run --network blockchain_side --rm -v "$(pwd)/build:/build/build" --env-file .env deploy_side --network development --reset > /dev/null 2>&1
|
||||
if [[ "$TARGET_NETWORK" == "development" ]]; then
|
||||
SHARED_DB_ADDRESS=$(docker run --network blockchain_side --rm -v "$DEPLOY_DIR/deploy-side/build:/build/build" --env-file "$DEPLOY_DIR/deploy-side/.env.$TARGET_NETWORK" \
|
||||
deploy_side \
|
||||
--network side 2>&1 \
|
||||
| grep "contract address" \
|
||||
| awk '{print $4}')
|
||||
else
|
||||
SHARED_DB_ADDRESS=$(docker run --rm -v "$DEPLOY_DIR/deploy-side/build:/build/build" --env-file "$DEPLOY_DIR/deploy-side/.env.$TARGET_NETWORK" --env-file "$DEPLOY_DIR/.keys.$TARGET_NETWORK" \
|
||||
deploy_side \
|
||||
--network side 2>&1 \
|
||||
| grep "contract address" \
|
||||
| awk '{print $4}')
|
||||
fi
|
||||
}
|
||||
|
||||
deploy_all() {
|
||||
TOKEN_ADDRESS=$(source "$DEPLOY_DIR/deploy-home/.env.$TARGET_NETWORK"; echo "$HOME_TOKEN_ADDRESS")
|
||||
|
||||
if [[ "$TARGET_NETWORK" == "development" ]] || [[ "$TOKEN_ADDRESS" == "0x" ]]; then
|
||||
deploy_token
|
||||
sed -i 's/TOKEN_ADDRESS=0x$/TOKEN_ADDRESS='"$TOKEN_ADDRESS"'/' "$DEPLOY_DIR/deploy-home/.env.$TARGET_NETWORK"
|
||||
fi
|
||||
|
||||
deploy_bridge
|
||||
deploy_db
|
||||
|
||||
echo "Token contract address in $TARGET_NETWORK network is $TOKEN_ADDRESS"
|
||||
echo "Bridge contract address in $TARGET_NETWORK network is $BRIDGE_ADDRESS"
|
||||
echo "Database contract address in $TARGET_NETWORK side network is $SHARED_DB_ADDRESS"
|
||||
|
||||
echo "Updating deployed contract addresses in demo validators .env.$TARGET_NETWORK configs"
|
||||
for file in "$DEMO_DIR"/validator*/.env."$TARGET_NETWORK"; do
|
||||
sed -i 's/HOME_TOKEN_ADDRESS=.*$/HOME_TOKEN_ADDRESS='"$TOKEN_ADDRESS"'/' "$file"
|
||||
sed -i 's/HOME_BRIDGE_ADDRESS=.*$/HOME_BRIDGE_ADDRESS='"$BRIDGE_ADDRESS"'/' "$file"
|
||||
sed -i 's/SIDE_SHARED_DB_ADDRESS=.*$/SIDE_SHARED_DB_ADDRESS='"$SHARED_DB_ADDRESS"'/' "$file"
|
||||
done
|
||||
|
||||
echo "Updating deployed contract addresses in test-services .env.$TARGET_NETWORK configs"
|
||||
sed -i 's/HOME_TOKEN_ADDRESS=.*$/HOME_TOKEN_ADDRESS='"$TOKEN_ADDRESS"'/' "$TEST_SERVICES_DIR/ethereumBalance/.env.$TARGET_NETWORK"
|
||||
sed -i 's/HOME_BRIDGE_ADDRESS=.*$/HOME_BRIDGE_ADDRESS='"$BRIDGE_ADDRESS"'/' "$TEST_SERVICES_DIR/ethereumSend/.env.$TARGET_NETWORK"
|
||||
sed -i 's/HOME_TOKEN_ADDRESS=.*$/HOME_TOKEN_ADDRESS='"$TOKEN_ADDRESS"'/' "$TEST_SERVICES_DIR/ethereumSend/.env.$TARGET_NETWORK"
|
||||
}
|
||||
|
||||
|
||||
side_db_mount_point="$(pwd)/ganache_side_db"
|
||||
if [ ! -d "$side_db_mount_point" ]; then
|
||||
mkdir "$side_db_mount_point"
|
||||
fi
|
||||
|
||||
home_db_mount_point="$(pwd)/ganache_home_db"
|
||||
if [ ! -d "$home_db_mount_point" ]; then
|
||||
mkdir "$home_db_mount_point"
|
||||
fi
|
||||
|
||||
if [ -z "$(ls -A ganache_side_db)" ] || [ -z "$(ls -A ganache_home_db)" ]; then
|
||||
echo "Starting new blockchain networks and deploying contracts"
|
||||
start_blockchains
|
||||
deploy_all
|
||||
if [[ "$TARGET_NETWORK" == "development" ]]; then
|
||||
if [[ ! -d "$SIDE_GANACHE_DB" ]]; then
|
||||
mkdir "$SIDE_GANACHE_DB"
|
||||
fi
|
||||
|
||||
if [[ ! -d "$HOME_GANACHE_DB" ]]; then
|
||||
mkdir "$HOME_GANACHE_DB"
|
||||
fi
|
||||
|
||||
|
||||
if [[ -z "$(ls -A "$SIDE_GANACHE_DB")" ]] || [[ -z "$(ls -A "$HOME_GANACHE_DB")" ]]; then
|
||||
echo "Starting dev blockchain networks and deploying contracts"
|
||||
need_to_deploy=true
|
||||
else
|
||||
echo "Restarting dev blockchain networks"
|
||||
fi
|
||||
|
||||
start_dev_blockchain_networks
|
||||
|
||||
if [[ -n "$need_to_deploy" ]]; then
|
||||
deploy_all
|
||||
else
|
||||
echo "Contracts are already deployed, run clean.sh first if you want to redeploy everything"
|
||||
fi
|
||||
else
|
||||
echo "Restarting blockchain networks"
|
||||
start_blockchains
|
||||
echo "Deploying to the staging blockchain environment"
|
||||
|
||||
deploy_all
|
||||
fi
|
||||
|
||||
echo "Done"
|
||||
|
|
|
@ -1,14 +1,26 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
set -o allexport
|
||||
|
||||
# either development or staging
|
||||
TARGET_NETWORK=${TARGET_NETWORK:=development}
|
||||
|
||||
DCU_FLAGS="--build --force-recreate"
|
||||
NAME="validator$N"
|
||||
|
||||
cd $(dirname "$0")
|
||||
|
||||
echo "Starting $NAME"
|
||||
echo "Starting $NAME in $TARGET_NETWORK network"
|
||||
|
||||
mkdir -p "$NAME"
|
||||
cd "$NAME"
|
||||
|
||||
# load private key form git ignored .keys file
|
||||
if [[ "$TARGET_NETWORK" == "staging" ]]; then
|
||||
source ".keys.$TARGET_NETWORK"
|
||||
fi
|
||||
# load env for particular environment
|
||||
source ".env.$TARGET_NETWORK"
|
||||
|
||||
docker-compose -p "$NAME" -f ../../src/oracle/docker-compose-test.yml up ${DCU_FLAGS}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
HOME_RPC_URL=http://ganache_home:8545
|
||||
HOME_CHAIN_ID=44
|
||||
HOME_BRIDGE_ADDRESS=0x44c158FE850821ae69DaF37AADF5c539e9d0025B
|
||||
HOME_TOKEN_ADDRESS=0xd5fE0D28e058D375b0b038fFbB446Da37E85fFdc
|
||||
HOME_START_BLOCK=1
|
||||
|
||||
SIDE_RPC_URL=http://ganache_side:8545
|
||||
SIDE_CHAIN_ID=33
|
||||
SIDE_SHARED_DB_ADDRESS=0xd5fE0D28e058D375b0b038fFbB446Da37E85fFdc
|
||||
|
||||
FOREIGN_URL=https://testnet-dex.binance.org/
|
||||
|
@ -16,3 +15,5 @@ VALIDATOR_PRIVATE_KEY=2be3f252e16541bf1bb2d4a517d2bf173e6d09f2d765d32c64dc50515a
|
|||
VOTES_PROXY_PORT=5001
|
||||
|
||||
SIGN_RESTART_PORT=6001
|
||||
|
||||
LOG_LEVEL=debug
|
|
@ -0,0 +1,19 @@
|
|||
HOME_RPC_URL=https://kovan.infura.io/v3/5d7bd94c50ed43fab1cb8e74f58678b0
|
||||
HOME_BRIDGE_ADDRESS=0x6ADCa5e691341fb9de8927d15c0a89B83A4E665e
|
||||
HOME_TOKEN_ADDRESS=0x57d2533B640cfb58f8f1F69C14c089968Da9fdFc
|
||||
HOME_START_BLOCK=13276224
|
||||
|
||||
SIDE_RPC_URL=https://sokol.poa.network
|
||||
SIDE_SHARED_DB_ADDRESS=0xda9a1cA2Fcb18cAB02934269369627D2b4ea8902
|
||||
|
||||
FOREIGN_URL=https://testnet-dex.binance.org/
|
||||
FOREIGN_CHAIN_ID=Binance-Chain-Nile
|
||||
FOREIGN_ASSET=KFT-94F
|
||||
|
||||
#VALIDATOR_PRIVATE_KEY is taken from .keys
|
||||
|
||||
VOTES_PROXY_PORT=5001
|
||||
|
||||
SIGN_RESTART_PORT=6001
|
||||
|
||||
LOG_LEVEL=info
|
|
@ -0,0 +1 @@
|
|||
VALIDATOR_PRIVATE_KEY=0000000000000000000000000000000000000000000000000000000000000000
|
|
@ -1,10 +1,9 @@
|
|||
HOME_RPC_URL=http://ganache_home:8545
|
||||
HOME_CHAIN_ID=44
|
||||
HOME_BRIDGE_ADDRESS=0x44c158FE850821ae69DaF37AADF5c539e9d0025B
|
||||
HOME_TOKEN_ADDRESS=0xd5fE0D28e058D375b0b038fFbB446Da37E85fFdc
|
||||
HOME_START_BLOCK=1
|
||||
|
||||
SIDE_RPC_URL=http://ganache_side:8545
|
||||
SIDE_CHAIN_ID=33
|
||||
SIDE_SHARED_DB_ADDRESS=0xd5fE0D28e058D375b0b038fFbB446Da37E85fFdc
|
||||
|
||||
FOREIGN_URL=https://testnet-dex.binance.org/
|
||||
|
@ -16,3 +15,5 @@ VALIDATOR_PRIVATE_KEY=e59d58c77b791f98f10187117374ae9c589d48a62720ec6a5e142b0cc1
|
|||
VOTES_PROXY_PORT=5002
|
||||
|
||||
SIGN_RESTART_PORT=6002
|
||||
|
||||
LOG_LEVEL=debug
|
|
@ -0,0 +1,19 @@
|
|||
HOME_RPC_URL=https://kovan.infura.io/v3/5d7bd94c50ed43fab1cb8e74f58678b0
|
||||
HOME_BRIDGE_ADDRESS=0x6ADCa5e691341fb9de8927d15c0a89B83A4E665e
|
||||
HOME_TOKEN_ADDRESS=0x57d2533B640cfb58f8f1F69C14c089968Da9fdFc
|
||||
HOME_START_BLOCK=13276224
|
||||
|
||||
SIDE_RPC_URL=https://sokol.poa.network
|
||||
SIDE_SHARED_DB_ADDRESS=0xda9a1cA2Fcb18cAB02934269369627D2b4ea8902
|
||||
|
||||
FOREIGN_URL=https://testnet-dex.binance.org/
|
||||
FOREIGN_CHAIN_ID=Binance-Chain-Nile
|
||||
FOREIGN_ASSET=KFT-94F
|
||||
|
||||
#VALIDATOR_PRIVATE_KEY is taken from .keys
|
||||
|
||||
VOTES_PROXY_PORT=5002
|
||||
|
||||
SIGN_RESTART_PORT=6002
|
||||
|
||||
LOG_LEVEL=info
|
|
@ -0,0 +1 @@
|
|||
VALIDATOR_PRIVATE_KEY=1111111111111111111111111111111111111111111111111111111111111111
|
|
@ -1,10 +1,9 @@
|
|||
HOME_RPC_URL=http://ganache_home:8545
|
||||
HOME_CHAIN_ID=44
|
||||
HOME_BRIDGE_ADDRESS=0x44c158FE850821ae69DaF37AADF5c539e9d0025B
|
||||
HOME_TOKEN_ADDRESS=0xd5fE0D28e058D375b0b038fFbB446Da37E85fFdc
|
||||
HOME_START_BLOCK=1
|
||||
|
||||
SIDE_RPC_URL=http://ganache_side:8545
|
||||
SIDE_CHAIN_ID=33
|
||||
SIDE_SHARED_DB_ADDRESS=0xd5fE0D28e058D375b0b038fFbB446Da37E85fFdc
|
||||
|
||||
FOREIGN_URL=https://testnet-dex.binance.org/
|
||||
|
@ -16,3 +15,5 @@ VALIDATOR_PRIVATE_KEY=afaa4d4d6e54d25b0bf0361e3fd6cef562f6311bf6200de2dd0aa4cab6
|
|||
VOTES_PROXY_PORT=5003
|
||||
|
||||
SIGN_RESTART_PORT=6003
|
||||
|
||||
LOG_LEVEL=debug
|
|
@ -0,0 +1,19 @@
|
|||
HOME_RPC_URL=https://kovan.infura.io/v3/5d7bd94c50ed43fab1cb8e74f58678b0
|
||||
HOME_BRIDGE_ADDRESS=0x6ADCa5e691341fb9de8927d15c0a89B83A4E665e
|
||||
HOME_TOKEN_ADDRESS=0x57d2533B640cfb58f8f1F69C14c089968Da9fdFc
|
||||
HOME_START_BLOCK=13276224
|
||||
|
||||
SIDE_RPC_URL=https://sokol.poa.network
|
||||
SIDE_SHARED_DB_ADDRESS=0xda9a1cA2Fcb18cAB02934269369627D2b4ea8902
|
||||
|
||||
FOREIGN_URL=https://testnet-dex.binance.org/
|
||||
FOREIGN_CHAIN_ID=Binance-Chain-Nile
|
||||
FOREIGN_ASSET=KFT-94F
|
||||
|
||||
#VALIDATOR_PRIVATE_KEY is taken from .keys
|
||||
|
||||
VOTES_PROXY_PORT=5003
|
||||
|
||||
SIGN_RESTART_PORT=6003
|
||||
|
||||
LOG_LEVEL=info
|
|
@ -0,0 +1 @@
|
|||
VALIDATOR_PRIVATE_KEY=2222222222222222222222222222222222222222222222222222222222222222
|
|
@ -0,0 +1,2 @@
|
|||
HOME_PRIVATE_KEY=0000000000000000000000000000000000000000000000000000000000000000
|
||||
SIDE_PRIVATE_KEY=1111111111111111111111111111111111111111111111111111111111111111
|
|
@ -1,17 +0,0 @@
|
|||
RPC_URL=https://sokol.poa.network
|
||||
RPC_URL_DEV=http://ganache_home:8545
|
||||
|
||||
PRIVATE_KEY=e49fe947f224ae8e126c41b1be3e52be701509c2366e835ea8c08651f91030e0
|
||||
PRIVATE_KEY_DEV=e2aeb24eaa63102d0c0821717c3b6384abdabd7af2ad4ec8e650dce300798b27
|
||||
|
||||
TOKEN_ADDRESS=0xd5fE0D28e058D375b0b038fFbB446Da37E85fFdc
|
||||
|
||||
VALIDATOR_ADDRESS_1=0x99Eb3D86663c6Db090eFFdBC20510Ca9f836DCE3
|
||||
VALIDATOR_ADDRESS_2=0xAa006899B0EC407De930bA8A166DEfe59bBfd3DC
|
||||
VALIDATOR_ADDRESS_3=0x6352e3e6038e05b9da00C84AE851308f9774F883
|
||||
VALIDATOR_ADDRESS_4=0x4dB6b4bD0a3fdc03B027A60f1c48f05C572312aa
|
||||
VALIDATOR_ADDRESS_5=0xf7Ca4aED1795E424433498CEf43f6a3825C88731
|
||||
VALIDATOR_ADDRESS_6=0xAd6c8127143032D843A260c5D379D8d9b3D51F15
|
||||
|
||||
THRESHOLD=1
|
||||
PARTIES=3
|
|
@ -0,0 +1,17 @@
|
|||
HOME_RPC_URL=http://ganache_home:8545
|
||||
|
||||
HOME_PRIVATE_KEY=e2aeb24eaa63102d0c0821717c3b6384abdabd7af2ad4ec8e650dce300798b27
|
||||
|
||||
HOME_TOKEN_ADDRESS=0xd5fE0D28e058D375b0b038fFbB446Da37E85fFdc
|
||||
|
||||
VALIDATOR_ADDRESS_1=0x99eb3d86663c6db090effdbc20510ca9f836dce3
|
||||
VALIDATOR_ADDRESS_2=0xaa006899b0ec407de930ba8a166defe59bbfd3dc
|
||||
VALIDATOR_ADDRESS_3=0x6352e3e6038e05b9da00c84ae851308f9774f883
|
||||
#VALIDATOR_ADDRESS_4=0x4db6b4bd0a3fdc03b027a60f1c48f05c572312aa
|
||||
|
||||
THRESHOLD=1
|
||||
|
||||
MIN_TX_LIMIT=10000000000000000
|
||||
MAX_TX_LIMIT=100000000000000000000
|
||||
|
||||
BLOCKS_RANGE_SIZE=25
|
|
@ -0,0 +1,18 @@
|
|||
HOME_RPC_URL=https://kovan.infura.io/v3/5d7bd94c50ed43fab1cb8e74f58678b0
|
||||
|
||||
#HOME_PRIVATE_KEY is taken from src/deploy/.keys.staging
|
||||
|
||||
#Set to '0x' for redeployment of token contract in staging environment
|
||||
HOME_TOKEN_ADDRESS=0x57d2533B640cfb58f8f1F69C14c089968Da9fdFc
|
||||
|
||||
VALIDATOR_ADDRESS_1=0xaaaaB15630f63cA01bb50943AaAb4008CB53748D
|
||||
VALIDATOR_ADDRESS_2=0xbbbb63D6Fc58bD14dAF9eeF653650c4D10f3dBC8
|
||||
VALIDATOR_ADDRESS_3=0xcccc27ae510b63E30eC3C68AAD7DdD2578bD62ed
|
||||
#VALIDATOR_ADDRESS_4=0xdddd9300e32fe162bA420f7313651Fd901C2ed71
|
||||
|
||||
THRESHOLD=1
|
||||
|
||||
MIN_TX_LIMIT=10000000000000000
|
||||
MAX_TX_LIMIT=100000000000000000000
|
||||
|
||||
BLOCKS_RANGE_SIZE=25
|
|
@ -30,4 +30,4 @@ RUN truffle compile
|
|||
COPY truffle-config.js /build/truffle-config.js
|
||||
COPY migrations /build/migrations
|
||||
|
||||
ENTRYPOINT truffle deploy
|
||||
ENTRYPOINT ["truffle", "deploy"]
|
||||
|
|
|
@ -3,6 +3,7 @@ pragma solidity ^0.5.0;
|
|||
import './openzeppelin-solidity/contracts/token/ERC20/IERC20.sol';
|
||||
|
||||
contract Bridge {
|
||||
event ExchangeRequest(uint value, uint nonce);
|
||||
event NewEpoch(uint indexed oldEpoch, uint indexed newEpoch);
|
||||
event NewEpochCancelled(uint indexed epoch);
|
||||
event NewFundsTransfer(uint indexed oldEpoch, uint indexed newEpoch);
|
||||
|
@ -11,38 +12,63 @@ contract Bridge {
|
|||
struct State {
|
||||
address[] validators;
|
||||
uint threshold;
|
||||
uint rangeSize;
|
||||
uint startBlock;
|
||||
uint nonce;
|
||||
uint x;
|
||||
uint y;
|
||||
}
|
||||
|
||||
enum Status {
|
||||
READY, // bridge is in ready to perform operations
|
||||
VOTING, // voting for changing in next epoch, but still ready
|
||||
KEYGEN, //keygen, can be cancelled
|
||||
FUNDS_TRANSFER // funds transfer, cannot be cancelled
|
||||
}
|
||||
|
||||
enum Vote {
|
||||
CONFIRM_KEYGEN,
|
||||
CONFIRM_FUNDS_TRANSFER,
|
||||
START_VOTING,
|
||||
ADD_VALIDATOR,
|
||||
REMOVE_VALIDATOR,
|
||||
CHANGE_THRESHOLD,
|
||||
CHANGE_RANGE_SIZE,
|
||||
START_KEYGEN,
|
||||
CANCEL_KEYGEN,
|
||||
TRANSFER
|
||||
}
|
||||
|
||||
mapping(uint => State) states;
|
||||
|
||||
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;
|
||||
mapping(bytes32 => bool) public usedRange;
|
||||
|
||||
// 0 - ready
|
||||
// 1 - keygen, can be cancelled
|
||||
// 2 - funds transfer, cannot be cancelled
|
||||
uint public status;
|
||||
Status public status;
|
||||
|
||||
uint public epoch;
|
||||
uint public nextEpoch;
|
||||
|
||||
constructor(uint threshold, address[] memory validators, address _tokenContract) public {
|
||||
uint minTxLimit;
|
||||
uint maxTxLimit;
|
||||
|
||||
constructor(uint threshold, address[] memory validators, address _tokenContract, uint[2] memory limits, uint rangeSize) public {
|
||||
require(validators.length > 0);
|
||||
require(threshold < validators.length);
|
||||
|
||||
tokenContract = IERC20(_tokenContract);
|
||||
|
||||
epoch = 0;
|
||||
status = 1;
|
||||
status = Status.KEYGEN;
|
||||
nextEpoch = 1;
|
||||
|
||||
states[1] = State(validators, threshold, 0, 0);
|
||||
states[nextEpoch] = State(validators, threshold, rangeSize, 0, uint(-1), 0, 0);
|
||||
|
||||
minTxLimit = limits[0];
|
||||
maxTxLimit = limits[1];
|
||||
|
||||
emit NewEpoch(0, 1);
|
||||
}
|
||||
|
@ -50,17 +76,27 @@ contract Bridge {
|
|||
IERC20 public tokenContract;
|
||||
|
||||
modifier ready {
|
||||
require(status == 0, "Not in ready state");
|
||||
require(status == Status.READY, "Not in ready state");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier readyOrVoting {
|
||||
require(status == Status.READY || status == Status.VOTING, "Not in ready or voting state");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier voting {
|
||||
require(status == Status.VOTING, "Not in voting state");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier keygen {
|
||||
require(status == 1, "Not in keygen state");
|
||||
require(status == Status.KEYGEN, "Not in keygen state");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier fundsTransfer {
|
||||
require(status == 2, "Not in funds transfer state");
|
||||
require(status == Status.FUNDS_TRANSFER, "Not in funds transfer state");
|
||||
_;
|
||||
}
|
||||
|
||||
|
@ -69,32 +105,40 @@ contract Bridge {
|
|||
_;
|
||||
}
|
||||
|
||||
function transfer(bytes32 hash, address to, uint value) public ready currentValidator {
|
||||
require(!dbTransfer[keccak256(abi.encodePacked(hash, msg.sender, to, value))], "Already voted");
|
||||
function exchange(uint value) public ready {
|
||||
require(value >= minTxLimit && value >= 10 ** 10 && value <= maxTxLimit);
|
||||
|
||||
dbTransfer[keccak256(abi.encodePacked(hash, msg.sender, to, value))] = true;
|
||||
if (++dbTransferCount[keccak256(abi.encodePacked(hash, to, value))] == getThreshold() + 1) {
|
||||
dbTransferCount[keccak256(abi.encodePacked(hash, to, value))] = 2 ** 255;
|
||||
uint txRange = (block.number - getStartBlock()) / getRangeSize();
|
||||
if (!usedRange[keccak256(abi.encodePacked(txRange, epoch))]) {
|
||||
usedRange[keccak256(abi.encodePacked(txRange, epoch))] = true;
|
||||
states[epoch].nonce++;
|
||||
}
|
||||
|
||||
tokenContract.transferFrom(msg.sender, address(this), value);
|
||||
emit ExchangeRequest(value, getNonce());
|
||||
}
|
||||
|
||||
function transfer(bytes32 hash, address to, uint value) public readyOrVoting currentValidator {
|
||||
if (tryVote(Vote.TRANSFER, hash, to, value)) {
|
||||
tokenContract.transfer(to, value);
|
||||
}
|
||||
}
|
||||
|
||||
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(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;
|
||||
if (tryConfirm(Vote.CONFIRM_KEYGEN, x, y)) {
|
||||
states[nextEpoch].x = x;
|
||||
states[nextEpoch].y = y;
|
||||
if (nextEpoch == 1) {
|
||||
status = 0;
|
||||
status = Status.READY;
|
||||
states[nextEpoch].startBlock = block.number;
|
||||
states[nextEpoch].nonce = uint(-1);
|
||||
epoch = nextEpoch;
|
||||
emit EpochStart(epoch, x, y);
|
||||
}
|
||||
else {
|
||||
status = 2;
|
||||
status = Status.FUNDS_TRANSFER;
|
||||
emit NewFundsTransfer(epoch, nextEpoch);
|
||||
}
|
||||
}
|
||||
|
@ -102,14 +146,13 @@ contract Bridge {
|
|||
|
||||
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;
|
||||
if (tryConfirm(Vote.CONFIRM_FUNDS_TRANSFER)) {
|
||||
status = Status.READY;
|
||||
states[nextEpoch].startBlock = block.number;
|
||||
states[nextEpoch].nonce = uint(-1);
|
||||
epoch = nextEpoch;
|
||||
emit EpochStart(epoch, states[epoch].x, states[epoch].y);
|
||||
emit EpochStart(epoch, getX(), getY());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -137,6 +180,34 @@ contract Bridge {
|
|||
return states[_epoch].threshold;
|
||||
}
|
||||
|
||||
function getStartBlock() view public returns (uint) {
|
||||
return getStartBlock(epoch);
|
||||
}
|
||||
|
||||
function getStartBlock(uint _epoch) view public returns (uint) {
|
||||
return states[_epoch].startBlock;
|
||||
}
|
||||
|
||||
function getRangeSize() view public returns (uint) {
|
||||
return getRangeSize(epoch);
|
||||
}
|
||||
|
||||
function getNextRangeSize() view public returns (uint) {
|
||||
return getRangeSize(nextEpoch);
|
||||
}
|
||||
|
||||
function getRangeSize(uint _epoch) view public returns (uint) {
|
||||
return states[_epoch].rangeSize;
|
||||
}
|
||||
|
||||
function getNonce() view public returns (uint) {
|
||||
return getNonce(epoch);
|
||||
}
|
||||
|
||||
function getNonce(uint _epoch) view public returns (uint) {
|
||||
return states[_epoch].nonce;
|
||||
}
|
||||
|
||||
function getX() view public returns (uint) {
|
||||
return states[epoch].x;
|
||||
}
|
||||
|
@ -171,22 +242,28 @@ contract Bridge {
|
|||
return states[nextEpoch].validators;
|
||||
}
|
||||
|
||||
function voteAddValidator(address validator) public ready currentValidator {
|
||||
require(getNextPartyId(validator) == 0, "Already a validator");
|
||||
require(!votes[keccak256(abi.encodePacked(uint(1), nextEpoch, msg.sender, validator))], "Already voted");
|
||||
function startVoting() public readyOrVoting currentValidator {
|
||||
if (tryVote(Vote.START_VOTING)) {
|
||||
nextEpoch++;
|
||||
status = Status.VOTING;
|
||||
states[nextEpoch].threshold = getThreshold();
|
||||
states[nextEpoch].validators = getValidators();
|
||||
states[nextEpoch].rangeSize = getRangeSize();
|
||||
}
|
||||
}
|
||||
|
||||
votes[keccak256(abi.encodePacked(uint(1), nextEpoch, msg.sender, validator))] = true;
|
||||
if (++votesCount[keccak256(abi.encodePacked(uint(1), nextEpoch, validator))] == getThreshold() + 1) {
|
||||
function voteAddValidator(address validator) public voting currentValidator {
|
||||
require(getNextPartyId(validator) == 0, "Already a validator");
|
||||
|
||||
if (tryVote(Vote.ADD_VALIDATOR, validator)) {
|
||||
states[nextEpoch].validators.push(validator);
|
||||
}
|
||||
}
|
||||
|
||||
function voteRemoveValidator(address validator) public ready currentValidator {
|
||||
function voteRemoveValidator(address validator) public voting currentValidator {
|
||||
require(getNextPartyId(validator) != 0, "Already not a validator");
|
||||
require(!votes[keccak256(abi.encodePacked(uint(2), nextEpoch, msg.sender, validator))], "Already voted");
|
||||
|
||||
votes[keccak256(abi.encodePacked(uint(2), nextEpoch, msg.sender, validator))] = true;
|
||||
if (++votesCount[keccak256(abi.encodePacked(uint(2), nextEpoch, validator))] == getThreshold() + 1) {
|
||||
if (tryVote(Vote.REMOVE_VALIDATOR, validator)) {
|
||||
_removeValidator(validator);
|
||||
}
|
||||
}
|
||||
|
@ -194,7 +271,7 @@ contract Bridge {
|
|||
function _removeValidator(address validator) private {
|
||||
for (uint i = 0; i < getNextParties() - 1; i++) {
|
||||
if (states[nextEpoch].validators[i] == validator) {
|
||||
states[nextEpoch].validators[i] = states[nextEpoch].validators[getNextParties() - 1];
|
||||
states[nextEpoch].validators[i] = getNextValidators()[getNextParties() - 1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -202,36 +279,93 @@ contract Bridge {
|
|||
states[nextEpoch].validators.length--;
|
||||
}
|
||||
|
||||
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), nextEpoch, msg.sender, threshold))] = true;
|
||||
if (++votesCount[keccak256(abi.encodePacked(uint(3), nextEpoch, threshold))] == getThreshold() + 1) {
|
||||
function voteChangeThreshold(uint threshold) public voting currentValidator {
|
||||
if (tryVote(Vote.CHANGE_THRESHOLD, threshold)) {
|
||||
states[nextEpoch].threshold = threshold;
|
||||
}
|
||||
}
|
||||
|
||||
function voteStartKeygen() public ready currentValidator {
|
||||
require(!votes[keccak256(abi.encodePacked(uint(4), nextEpoch + 1, msg.sender))], "Voted already");
|
||||
function voteChangeRangeSize(uint rangeSize) public voting currentValidator {
|
||||
if (tryVote(Vote.CHANGE_RANGE_SIZE, rangeSize)) {
|
||||
states[nextEpoch].rangeSize = rangeSize;
|
||||
}
|
||||
}
|
||||
|
||||
votes[keccak256(abi.encodePacked(uint(4), nextEpoch + 1, msg.sender))] = true;
|
||||
if (++votesCount[keccak256(abi.encodePacked(uint(4), nextEpoch + 1))] == getThreshold() + 1) {
|
||||
status = 1;
|
||||
function voteStartKeygen() public voting currentValidator {
|
||||
if (tryVote(Vote.START_KEYGEN)) {
|
||||
status = Status.KEYGEN;
|
||||
|
||||
nextEpoch++;
|
||||
states[nextEpoch].validators = getValidators();
|
||||
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;
|
||||
if (tryVote(Vote.CANCEL_KEYGEN)) {
|
||||
status = Status.VOTING;
|
||||
|
||||
emit NewEpochCancelled(nextEpoch);
|
||||
}
|
||||
}
|
||||
|
||||
function tryVote(Vote voteType) private returns (bool) {
|
||||
bytes32 vote = keccak256(abi.encodePacked(voteType, nextEpoch));
|
||||
return putVote(vote);
|
||||
}
|
||||
|
||||
function tryVote(Vote voteType, address addr) private returns (bool) {
|
||||
bytes32 vote = keccak256(abi.encodePacked(voteType, nextEpoch, addr));
|
||||
return putVote(vote);
|
||||
}
|
||||
|
||||
function tryVote(Vote voteType, uint num) private returns (bool) {
|
||||
bytes32 vote = keccak256(abi.encodePacked(voteType, nextEpoch, num));
|
||||
return putVote(vote);
|
||||
}
|
||||
|
||||
function tryVote(Vote voteType, bytes32 hash, address to, uint value) private returns (bool) {
|
||||
bytes32 vote = keccak256(abi.encodePacked(voteType, hash, to, value));
|
||||
return putVote(vote);
|
||||
}
|
||||
|
||||
function tryConfirm(Vote voteType) private returns (bool) {
|
||||
bytes32 vote = keccak256(abi.encodePacked(voteType, nextEpoch));
|
||||
return putConfirm(vote);
|
||||
}
|
||||
|
||||
function tryConfirm(Vote voteType, uint x, uint y) private returns (bool) {
|
||||
bytes32 vote = keccak256(abi.encodePacked(voteType, nextEpoch, x, y));
|
||||
return putConfirm(vote);
|
||||
}
|
||||
|
||||
function putVote(bytes32 vote) private returns (bool) {
|
||||
bytes32 personalVote = personalizeVote(vote);
|
||||
require(!votes[personalVote], "Voted already");
|
||||
|
||||
votes[personalVote] = true;
|
||||
if (votesCount[vote] == getThreshold()) {
|
||||
votesCount[vote] = 2 ** 255;
|
||||
return true;
|
||||
} else {
|
||||
votesCount[vote]++;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function putConfirm(bytes32 vote) private returns (bool) {
|
||||
bytes32 personalVote = personalizeVote(vote);
|
||||
require(!votes[personalVote], "Confirmed already");
|
||||
|
||||
votes[personalVote] = true;
|
||||
if (votesCount[vote] == getNextThreshold()) {
|
||||
votesCount[vote] = 2 ** 255;
|
||||
return true;
|
||||
} else {
|
||||
votesCount[vote]++;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function personalizeVote(bytes32 vote) private view returns (bytes32) {
|
||||
return keccak256(abi.encodePacked(vote, msg.sender));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,23 +1,20 @@
|
|||
const Bridge = artifacts.require('Bridge')
|
||||
|
||||
const addresses = Object.entries(process.env)
|
||||
.filter(([ key ]) => key.startsWith('VALIDATOR_ADDRESS'))
|
||||
.map(([ , value ]) => value)
|
||||
|
||||
const {
|
||||
THRESHOLD, PARTIES, VALIDATOR_ADDRESS_1, VALIDATOR_ADDRESS_2, VALIDATOR_ADDRESS_3, VALIDATOR_ADDRESS_4,
|
||||
VALIDATOR_ADDRESS_5, VALIDATOR_ADDRESS_6, TOKEN_ADDRESS
|
||||
THRESHOLD, HOME_TOKEN_ADDRESS, MIN_TX_LIMIT, MAX_TX_LIMIT, BLOCKS_RANGE_SIZE
|
||||
} = process.env
|
||||
|
||||
module.exports = deployer => {
|
||||
deployer.deploy(
|
||||
Bridge,
|
||||
THRESHOLD,
|
||||
//PARTIES,
|
||||
[
|
||||
VALIDATOR_ADDRESS_1,
|
||||
VALIDATOR_ADDRESS_2,
|
||||
VALIDATOR_ADDRESS_3,
|
||||
//VALIDATOR_ADDRESS_4,
|
||||
//VALIDATOR_ADDRESS_5,
|
||||
//VALIDATOR_ADDRESS_6
|
||||
],
|
||||
TOKEN_ADDRESS
|
||||
addresses,
|
||||
HOME_TOKEN_ADDRESS,
|
||||
[ MIN_TX_LIMIT, MAX_TX_LIMIT ],
|
||||
BLOCKS_RANGE_SIZE
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
const PrivateKeyProvider = require('truffle-hdwallet-provider')
|
||||
|
||||
const { RPC_URL, PRIVATE_KEY, RPC_URL_DEV, PRIVATE_KEY_DEV } = process.env
|
||||
const { HOME_RPC_URL, HOME_PRIVATE_KEY } = 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'
|
||||
home: {
|
||||
provider: new PrivateKeyProvider(HOME_PRIVATE_KEY, HOME_RPC_URL),
|
||||
network_id: '*'
|
||||
}
|
||||
},
|
||||
compilers: {
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
RPC_URL=https://sokol.poa.network
|
||||
RPC_URL_DEV=http://ganache_side:8545
|
||||
|
||||
PRIVATE_KEY=e49fe947f224ae8e126c41b1be3e52be701509c2366e835ea8c08651f91030e0
|
||||
PRIVATE_KEY_DEV=e2aeb24eaa63102d0c0821717c3b6384abdabd7af2ad4ec8e650dce300798b27
|
|
@ -0,0 +1,3 @@
|
|||
SIDE_RPC_URL=http://ganache_side:8545
|
||||
|
||||
SIDE_PRIVATE_KEY=e2aeb24eaa63102d0c0821717c3b6384abdabd7af2ad4ec8e650dce300798b27
|
|
@ -0,0 +1,3 @@
|
|||
SIDE_RPC_URL=https://sokol.poa.network
|
||||
|
||||
#SIDE_PRIVATE_KEY is taken from src/deploy/.keys.staging
|
|
@ -26,4 +26,4 @@ RUN truffle compile
|
|||
COPY truffle-config.js /build/truffle-config.js
|
||||
COPY migrations /build/migrations
|
||||
|
||||
ENTRYPOINT truffle deploy
|
||||
ENTRYPOINT ["truffle", "deploy"]
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
const PrivateKeyProvider = require('truffle-hdwallet-provider')
|
||||
|
||||
const { RPC_URL, PRIVATE_KEY, RPC_URL_DEV, PRIVATE_KEY_DEV } = process.env
|
||||
const { SIDE_RPC_URL, SIDE_PRIVATE_KEY } = process.env
|
||||
|
||||
module.exports = {
|
||||
networks: {
|
||||
development: {
|
||||
provider: new PrivateKeyProvider(PRIVATE_KEY_DEV, RPC_URL_DEV),
|
||||
network_id: '33'
|
||||
},
|
||||
staging: {
|
||||
provider: new PrivateKeyProvider(PRIVATE_KEY, RPC_URL),
|
||||
network_id: '77'
|
||||
side: {
|
||||
provider: new PrivateKeyProvider(SIDE_PRIVATE_KEY, SIDE_RPC_URL),
|
||||
network_id: '*'
|
||||
}
|
||||
},
|
||||
compilers: {
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
RPC_URL=https://sokol.poa.network
|
||||
RPC_URL_DEV=http://ganache_home:8545
|
||||
|
||||
PRIVATE_KEY=e49fe947f224ae8e126c41b1be3e52be701509c2366e835ea8c08651f91030e0
|
||||
PRIVATE_KEY_DEV=e2aeb24eaa63102d0c0821717c3b6384abdabd7af2ad4ec8e650dce300798b27
|
||||
|
||||
TOKEN_INITIAL_MINT=31415000000000000000000000
|
|
@ -0,0 +1,5 @@
|
|||
HOME_RPC_URL=http://ganache_home:8545
|
||||
|
||||
HOME_PRIVATE_KEY=e2aeb24eaa63102d0c0821717c3b6384abdabd7af2ad4ec8e650dce300798b27
|
||||
|
||||
TOKEN_INITIAL_MINT=31415000000000000000000000
|
|
@ -0,0 +1,5 @@
|
|||
HOME_RPC_URL=https://kovan.infura.io/v3/5d7bd94c50ed43fab1cb8e74f58678b0
|
||||
|
||||
#HOME_PRIVATE_KEY is taken from src/deploy/.keys.staging
|
||||
|
||||
TOKEN_INITIAL_MINT=31415000000000000000000000
|
|
@ -30,4 +30,4 @@ RUN truffle compile
|
|||
COPY truffle-config.js /build/truffle-config.js
|
||||
COPY migrations /build/migrations
|
||||
|
||||
ENTRYPOINT truffle deploy
|
||||
ENTRYPOINT ["truffle", "deploy"]
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
const PrivateKeyProvider = require('truffle-hdwallet-provider')
|
||||
|
||||
const { RPC_URL, PRIVATE_KEY, RPC_URL_DEV, PRIVATE_KEY_DEV } = process.env
|
||||
const { HOME_RPC_URL, HOME_PRIVATE_KEY } = 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'
|
||||
home: {
|
||||
provider: new PrivateKeyProvider(HOME_PRIVATE_KEY, HOME_RPC_URL),
|
||||
network_id: '*'
|
||||
}
|
||||
},
|
||||
compilers: {
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
HOME_RPC_URL=http://ganache_home:8545
|
||||
HOME_CHAIN_ID=44
|
||||
HOME_BRIDGE_ADDRESS=0x44c158FE850821ae69DaF37AADF5c539e9d0025B
|
||||
HOME_TOKEN_ADDRESS=0xd5fE0D28e058D375b0b038fFbB446Da37E85fFdc
|
||||
|
||||
SIDE_RPC_URL=http://ganache_side:8545
|
||||
SIDE_CHAIN_ID=33
|
||||
SIDE_SHARED_DB_ADDRESS=0xd5fE0D28e058D375b0b038fFbB446Da37E85fFdc
|
||||
|
||||
FOREIGN_URL=https://testnet-dex.binance.org/
|
||||
FOREIGN_CHAIN_ID=Binance-Chain-Nile
|
||||
FOREIGN_ASSET=KFT-94F
|
||||
|
||||
VALIDATOR_PRIVATE_KEY=2be3f252e16541bf1bb2d4a517d2bf173e6d09f2d765d32c64dc50515aec63ea
|
||||
|
||||
VOTES_PROXY_PORT=5000
|
||||
SIGN_RESTART_PORT=6000
|
|
@ -5,10 +5,10 @@ WORKDIR /watcher
|
|||
RUN apk update && \
|
||||
apk add libssl1.1 libressl-dev curl
|
||||
|
||||
COPY package.json /watcher/
|
||||
COPY ./bncWatcher/package.json /watcher/
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY bncWatcher.js db.js /watcher/
|
||||
COPY ./bncWatcher/bncWatcher.js ./shared/db.js ./shared/logger.js ./shared/crypto.js /watcher/
|
||||
|
||||
ENTRYPOINT ["node", "bncWatcher.js"]
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
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 { computeAddress } = require('ethers').utils
|
||||
|
||||
const logger = require('./logger')
|
||||
const redis = require('./db')
|
||||
const { publicKeyToAddress } = require('./crypto')
|
||||
|
||||
const { FOREIGN_URL, PROXY_URL, FOREIGN_ASSET } = process.env
|
||||
|
||||
const foreignHttpClient = axios.create({ baseURL: FOREIGN_URL })
|
||||
|
@ -13,8 +14,8 @@ 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)
|
||||
logger.info('Set default foreign time')
|
||||
await redis.set('foreignTime', Date.now() - 2 * 30 * 24 * 60 * 60 * 1000)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,7 +27,10 @@ async function main () {
|
|||
return
|
||||
}
|
||||
|
||||
console.log(`Found ${newTransactions.length} new transactions`)
|
||||
if (newTransactions.length)
|
||||
logger.info(`Found ${newTransactions.length} new transactions`)
|
||||
else
|
||||
logger.debug(`Found 0 new transactions`)
|
||||
|
||||
for (const tx of newTransactions.reverse()) {
|
||||
if (tx.memo !== 'funding') {
|
||||
|
@ -54,12 +58,12 @@ function getTx(hash) {
|
|||
}
|
||||
|
||||
async function fetchNewTransactions () {
|
||||
console.log('Fetching new transactions')
|
||||
logger.debug('Fetching new transactions')
|
||||
const startTime = parseInt(await redis.get('foreignTime')) + 1
|
||||
const address = await getLastForeignAddress()
|
||||
const address = getLastForeignAddress()
|
||||
if (address === null)
|
||||
return null
|
||||
console.log('Sending api transactions request')
|
||||
logger.debug('Sending api transactions request')
|
||||
return foreignHttpClient
|
||||
.get('/api/v1/transactions', {
|
||||
params: {
|
||||
|
@ -84,20 +88,6 @@ function getLastForeignAddress () {
|
|||
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()
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
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
|
|
@ -6,7 +6,9 @@
|
|||
"axios": "0.19.0",
|
||||
"bech32": "1.1.3",
|
||||
"bignumber.js": "9.0.0",
|
||||
"ethers": "4.0.33"
|
||||
"ethers": "4.0.33",
|
||||
"pino": "5.13.4",
|
||||
"pino-pretty": "3.2.1"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
loglevel warning
|
|
@ -2,7 +2,9 @@ version: '3.7'
|
|||
services:
|
||||
proxy:
|
||||
image: blockchain-proxy
|
||||
build: ./proxy
|
||||
build:
|
||||
dockerfile: proxy/Dockerfile
|
||||
context: .
|
||||
environment:
|
||||
- HOME_RPC_URL
|
||||
- HOME_BRIDGE_ADDRESS
|
||||
|
@ -14,6 +16,9 @@ services:
|
|||
- VALIDATOR_PRIVATE_KEY
|
||||
- FOREIGN_URL
|
||||
- FOREIGN_ASSET
|
||||
- LOG_LEVEL
|
||||
- "GAS_LIMIT_FACTOR=3"
|
||||
- "MAX_GAS_LIMIT=6000000"
|
||||
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'
|
||||
|
@ -26,25 +31,31 @@ services:
|
|||
- blockchain_side
|
||||
keygen:
|
||||
image: keygen-client
|
||||
build: ./tss-keygen
|
||||
build:
|
||||
dockerfile: tss-keygen/Dockerfile
|
||||
context: .
|
||||
environment:
|
||||
- 'RABBITMQ_URL=amqp://rabbitmq:5672'
|
||||
- 'PROXY_URL=http://proxy:8001'
|
||||
- LOG_LEVEL
|
||||
volumes:
|
||||
- '${PWD}/keys:/keys'
|
||||
- '${PWD}/${TARGET_NETWORK}/keys:/keys'
|
||||
networks:
|
||||
- test_network
|
||||
signer:
|
||||
image: sign-client
|
||||
build: ./tss-sign
|
||||
build:
|
||||
dockerfile: tss-sign/Dockerfile
|
||||
context: .
|
||||
environment:
|
||||
- 'RABBITMQ_URL=amqp://rabbitmq:5672'
|
||||
- 'PROXY_URL=http://proxy:8001'
|
||||
- FOREIGN_CHAIN_ID
|
||||
- FOREIGN_URL
|
||||
- FOREIGN_ASSET
|
||||
- LOG_LEVEL
|
||||
volumes:
|
||||
- '${PWD}/keys:/keys'
|
||||
- '${PWD}/${TARGET_NETWORK}/keys:/keys'
|
||||
ports:
|
||||
- '${SIGN_RESTART_PORT}:8001'
|
||||
networks:
|
||||
|
@ -52,41 +63,53 @@ services:
|
|||
redis:
|
||||
image: redis:5.0.5-alpine
|
||||
volumes:
|
||||
- '${PWD}/db:/data'
|
||||
- '${PWD}/${TARGET_NETWORK}/db:/data'
|
||||
- './configs/redis.conf:/usr/local/etc/redis/redis.conf'
|
||||
command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
|
||||
networks:
|
||||
- test_network
|
||||
rabbitmq:
|
||||
hostname: rabbit
|
||||
image: rabbitmq:3.7.15-alpine
|
||||
environment:
|
||||
RABBITMQ_LOGS: 'false'
|
||||
volumes:
|
||||
- '${PWD}/queue:/var/lib/rabbitmq/mnesia'
|
||||
- '${PWD}/${TARGET_NETWORK}/queue:/var/lib/rabbitmq/mnesia'
|
||||
networks:
|
||||
- test_network
|
||||
eth-watcher:
|
||||
build: ethWatcher
|
||||
build:
|
||||
dockerfile: ethWatcher/Dockerfile
|
||||
context: .
|
||||
image: eth-watcher
|
||||
environment:
|
||||
- HOME_RPC_URL
|
||||
- HOME_BRIDGE_ADDRESS
|
||||
- HOME_TOKEN_ADDRESS
|
||||
- HOME_CHAIN_ID
|
||||
- HOME_START_BLOCK
|
||||
- BLOCKS_RANGE_SIZE
|
||||
- VALIDATOR_PRIVATE_KEY
|
||||
- 'RABBITMQ_URL=amqp://rabbitmq:5672'
|
||||
- LOG_LEVEL
|
||||
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
|
||||
- blockchain_home
|
||||
bnc-watcher:
|
||||
build: bncWatcher
|
||||
build:
|
||||
dockerfile: bncWatcher/Dockerfile
|
||||
context: .
|
||||
image: bnc-watcher
|
||||
environment:
|
||||
- FOREIGN_URL
|
||||
- FOREIGN_ASSET
|
||||
- 'RABBITMQ_URL=amqp://rabbitmq:5672'
|
||||
- 'PROXY_URL=http://proxy:8001'
|
||||
- LOG_LEVEL
|
||||
volumes:
|
||||
- '${PWD}/keys:/keys'
|
||||
- '${PWD}/${TARGET_NETWORK}/keys:/keys'
|
||||
networks:
|
||||
- test_network
|
||||
networks:
|
||||
|
|
|
@ -2,7 +2,9 @@ version: '3.7'
|
|||
services:
|
||||
proxy:
|
||||
image: blockchain-proxy
|
||||
build: ./proxy
|
||||
build:
|
||||
dockerfile: proxy/Dockerfile
|
||||
context: .
|
||||
environment:
|
||||
- HOME_RPC_URL
|
||||
- HOME_BRIDGE_ADDRESS
|
||||
|
@ -14,6 +16,9 @@ services:
|
|||
- VALIDATOR_PRIVATE_KEY
|
||||
- FOREIGN_URL
|
||||
- FOREIGN_ASSET
|
||||
- LOG_LEVEL
|
||||
- "GAS_LIMIT_FACTOR=3"
|
||||
- "MAX_GAS_LIMIT=6000000"
|
||||
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'
|
||||
|
@ -26,27 +31,33 @@ services:
|
|||
- bncwatcher-proxy-net
|
||||
keygen:
|
||||
image: keygen-client
|
||||
build: ./tss-keygen
|
||||
build:
|
||||
dockerfile: tss-keygen/Dockerfile
|
||||
context: .
|
||||
environment:
|
||||
- 'RABBITMQ_URL=amqp://rabbitmq:5672'
|
||||
- 'PROXY_URL=http://proxy:8001'
|
||||
- LOG_LEVEL
|
||||
volumes:
|
||||
- '${PWD}/keys:/keys'
|
||||
- '${PWD}/${TARGET_NETWORK}/keys:/keys'
|
||||
networks:
|
||||
- keygen-proxy-net
|
||||
- rabbit-keygen-net
|
||||
- redis-keygen-net
|
||||
signer:
|
||||
image: sign-client
|
||||
build: ./tss-sign
|
||||
build:
|
||||
dockerfile: tss-sign/Dockerfile
|
||||
context: .
|
||||
environment:
|
||||
- 'RABBITMQ_URL=amqp://rabbitmq:5672'
|
||||
- 'PROXY_URL=http://proxy:8001'
|
||||
- FOREIGN_CHAIN_ID
|
||||
- FOREIGN_URL
|
||||
- FOREIGN_ASSET
|
||||
- LOG_LEVEL
|
||||
volumes:
|
||||
- '${PWD}/keys:/keys'
|
||||
- '${PWD}/${TARGET_NETWORK}/keys:/keys'
|
||||
ports:
|
||||
- '${SIGN_RESTART_PORT}:8001'
|
||||
networks:
|
||||
|
@ -56,7 +67,9 @@ services:
|
|||
redis:
|
||||
image: redis:5.0.5-alpine
|
||||
volumes:
|
||||
- '${PWD}/db:/data'
|
||||
- '${PWD}/${TARGET_NETWORK}/db:/data'
|
||||
- './configs/redis.conf:/usr/local/etc/redis/redis.conf'
|
||||
command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
|
||||
networks:
|
||||
- redis-signer-net
|
||||
- redis-keygen-net
|
||||
|
@ -65,38 +78,47 @@ services:
|
|||
rabbitmq:
|
||||
hostname: rabbit
|
||||
image: rabbitmq:3.7.15-alpine
|
||||
environment:
|
||||
RABBITMQ_LOGS: 'false'
|
||||
volumes:
|
||||
- '${PWD}/queue:/var/lib/rabbitmq/mnesia'
|
||||
- '${PWD}/${TARGET_NETWORK}/queue:/var/lib/rabbitmq/mnesia'
|
||||
networks:
|
||||
- rabbit-signer-net
|
||||
- rabbit-keygen-net
|
||||
- rabbit-ethwatcher-net
|
||||
- rabbit-bncwatcher-net
|
||||
eth-watcher:
|
||||
build: ethWatcher
|
||||
build:
|
||||
dockerfile: ethWatcher/Dockerfile
|
||||
context: .
|
||||
image: eth-watcher
|
||||
environment:
|
||||
- HOME_RPC_URL
|
||||
- HOME_BRIDGE_ADDRESS
|
||||
- HOME_TOKEN_ADDRESS
|
||||
- HOME_CHAIN_ID
|
||||
- HOME_START_BLOCK
|
||||
- VALIDATOR_PRIVATE_KEY
|
||||
- 'RABBITMQ_URL=amqp://rabbitmq:5672'
|
||||
- LOG_LEVEL
|
||||
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:
|
||||
- rabbit-ethwatcher-net
|
||||
- redis-ethwatcher-net
|
||||
bnc-watcher:
|
||||
build: bncWatcher
|
||||
build:
|
||||
dockerfile: bncWatcher
|
||||
context: .
|
||||
image: bnc-watcher
|
||||
environment:
|
||||
- FOREIGN_URL
|
||||
- FOREIGN_ASSET
|
||||
- 'RABBITMQ_URL=amqp://rabbitmq:5672'
|
||||
- 'PROXY_URL=http://proxy:8001'
|
||||
- LOG_LEVEL
|
||||
volumes:
|
||||
- '${PWD}/keys:/keys'
|
||||
- '${PWD}/${TARGET_NETWORK}/keys:/keys'
|
||||
networks:
|
||||
- rabbit-bncwatcher-net
|
||||
- redis-bncwatcher-net
|
||||
|
|
|
@ -5,10 +5,10 @@ WORKDIR /watcher
|
|||
RUN apk update && \
|
||||
apk add libssl1.1 libressl-dev curl
|
||||
|
||||
COPY package.json /watcher/
|
||||
COPY ./ethWatcher/package.json /watcher/
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY ethWatcher.js db.js /watcher/
|
||||
COPY ./ethWatcher/ethWatcher.js ./shared/db.js ./shared/logger.js ./shared/amqp.js ./shared/crypto.js /watcher/
|
||||
|
||||
ENTRYPOINT ["node", "ethWatcher.js"]
|
||||
|
|
|
@ -1,72 +1,119 @@
|
|||
const amqp = require('amqplib')
|
||||
const Web3 = require('web3')
|
||||
const redis = require('./db')
|
||||
const crypto = require('crypto')
|
||||
const utils = require('ethers').utils
|
||||
const BN = require('bignumber.js')
|
||||
const bech32 = require('bech32')
|
||||
const axios = require('axios')
|
||||
|
||||
const logger = require('./logger')
|
||||
const redis = require('./db')
|
||||
const { connectRabbit, assertQueue } = require('./amqp')
|
||||
const { publicKeyToAddress } = require('./crypto')
|
||||
|
||||
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 { HOME_RPC_URL, HOME_BRIDGE_ADDRESS, RABBITMQ_URL, HOME_START_BLOCK, VALIDATOR_PRIVATE_KEY } = process.env
|
||||
|
||||
const web3Home = new Web3(HOME_RPC_URL)
|
||||
const bridge = new web3Home.eth.Contract(abiBridge, HOME_BRIDGE_ADDRESS)
|
||||
const token = new web3Home.eth.Contract(abiToken, HOME_TOKEN_ADDRESS)
|
||||
const homeWeb3 = new Web3(HOME_RPC_URL)
|
||||
const bridge = new homeWeb3.eth.Contract(abiBridge, HOME_BRIDGE_ADDRESS)
|
||||
const validatorAddress = homeWeb3.eth.accounts.privateKeyToAccount(`0x${VALIDATOR_PRIVATE_KEY}`).address
|
||||
|
||||
let channel
|
||||
let exchangeQueue
|
||||
let signQueue
|
||||
let keygenQueue
|
||||
let cancelKeygenQueue
|
||||
let blockNumber
|
||||
let foreignNonce = []
|
||||
let epoch
|
||||
let epochStart
|
||||
let redisTx
|
||||
let rangeSize
|
||||
let lastTransactionBlockNumber
|
||||
let isCurrentValidator
|
||||
|
||||
async function connectRabbit (url) {
|
||||
return amqp.connect(url).catch(() => {
|
||||
console.log('Failed to connect, reconnecting')
|
||||
return new Promise(resolve =>
|
||||
setTimeout(() => resolve(connectRabbit(url)), 1000)
|
||||
)
|
||||
})
|
||||
async function resetFutureMessages (queue) {
|
||||
logger.debug(`Resetting future messages in queue ${queue.name}`)
|
||||
const { messageCount } = await channel.checkQueue(queue.name)
|
||||
if (messageCount) {
|
||||
logger.info(`Filtering ${messageCount} reloaded messages from queue ${queue.name}`)
|
||||
const backup = await assertQueue(channel, `${queue.name}.backup`)
|
||||
do {
|
||||
const message = await queue.get()
|
||||
if (message === false)
|
||||
break
|
||||
const data = JSON.parse(message.content)
|
||||
if (data.blockNumber < blockNumber) {
|
||||
logger.debug('Saving message %o', data)
|
||||
backup.send(data)
|
||||
} else {
|
||||
logger.debug('Dropping message %o', data)
|
||||
}
|
||||
channel.ack(message)
|
||||
} while (true)
|
||||
|
||||
logger.debug('Dropped messages came from future')
|
||||
|
||||
do {
|
||||
const message = await backup.get()
|
||||
if (message === false)
|
||||
break
|
||||
const data = JSON.parse(message.content)
|
||||
logger.debug('Requeuing message %o', data)
|
||||
queue.send(data)
|
||||
channel.ack(message)
|
||||
} while (true)
|
||||
|
||||
logger.debug('Redirected messages back to initial queue')
|
||||
}
|
||||
}
|
||||
|
||||
async function initialize () {
|
||||
const connection = await connectRabbit(RABBITMQ_URL)
|
||||
channel = await connection.createChannel()
|
||||
signQueue = await channel.assertQueue('signQueue')
|
||||
keygenQueue = await channel.assertQueue('keygenQueue')
|
||||
cancelKeygenQueue = await channel.assertQueue('cancelKeygenQueue')
|
||||
channel = await connectRabbit(RABBITMQ_URL)
|
||||
exchangeQueue = await assertQueue(channel, 'exchangeQueue')
|
||||
signQueue = await assertQueue(channel, 'signQueue')
|
||||
keygenQueue = await assertQueue(channel, 'keygenQueue')
|
||||
cancelKeygenQueue = await assertQueue(channel, 'cancelKeygenQueue')
|
||||
|
||||
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 epochStart = events.length ? events[events.length - 1].blockNumber : 1
|
||||
const saved = (parseInt(await redis.get('homeBlock')) + 1) || 1
|
||||
console.log(epochStart, saved)
|
||||
logger.info(`Current epoch ${epoch}`)
|
||||
epochStart = events.length ? events[events.length - 1].blockNumber : 1
|
||||
const saved = (parseInt(await redis.get('homeBlock')) + 1) || parseInt(HOME_START_BLOCK)
|
||||
if (epochStart > saved) {
|
||||
console.log(`Data in db is outdated, starting from epoch ${epoch}, block #${epochStart}`)
|
||||
logger.info(`Data in db is outdated, starting from epoch ${epoch}, block #${epochStart}`)
|
||||
blockNumber = epochStart
|
||||
rangeSize = (await bridge.methods.getRangeSize().call()).toNumber()
|
||||
await redis.multi()
|
||||
.set('homeBlock', blockNumber - 1)
|
||||
.set(`foreignNonce${epoch}`, 0)
|
||||
.exec()
|
||||
foreignNonce[epoch] = 0
|
||||
} else {
|
||||
console.log('Restoring epoch and block number from local db')
|
||||
logger.info('Restoring epoch and block number from local db')
|
||||
blockNumber = saved
|
||||
foreignNonce[epoch] = parseInt(await redis.get(`foreignNonce${epoch}`)) || 0
|
||||
}
|
||||
isCurrentValidator = (await bridge.methods.getValidators().call()).includes(validatorAddress)
|
||||
if (isCurrentValidator) {
|
||||
logger.info(`${validatorAddress} is a current validator`)
|
||||
} else {
|
||||
logger.info(`${validatorAddress} is not a current validator`)
|
||||
}
|
||||
|
||||
await resetFutureMessages(keygenQueue)
|
||||
await resetFutureMessages(cancelKeygenQueue)
|
||||
await resetFutureMessages(exchangeQueue)
|
||||
await resetFutureMessages(signQueue)
|
||||
logger.debug(`Sending start commands`)
|
||||
await axios.get('http://keygen:8001/start')
|
||||
await axios.get('http://signer:8001/start')
|
||||
}
|
||||
|
||||
async function main () {
|
||||
console.log(`Watching events in block #${blockNumber}`)
|
||||
if (await web3Home.eth.getBlock(blockNumber) === null) {
|
||||
console.log('No block')
|
||||
logger.debug(`Watching events in block #${blockNumber}`)
|
||||
if (await homeWeb3.eth.getBlock(blockNumber) === null) {
|
||||
logger.debug('No block')
|
||||
await new Promise(r => setTimeout(r, 1000))
|
||||
return
|
||||
}
|
||||
|
@ -84,34 +131,33 @@ async function main () {
|
|||
await sendKeygen(event)
|
||||
break
|
||||
case 'NewEpochCancelled':
|
||||
sendKeygenCancelation(event)
|
||||
sendKeygenCancellation(event)
|
||||
break
|
||||
case 'NewFundsTransfer':
|
||||
await sendSignFundsTransfer(event)
|
||||
isCurrentValidator && await sendSignFundsTransfer(event)
|
||||
break
|
||||
case 'ExchangeRequest':
|
||||
isCurrentValidator && await sendSign(event)
|
||||
break
|
||||
case 'EpochStart':
|
||||
epoch = event.returnValues.epoch.toNumber()
|
||||
console.log(`Epoch ${epoch} started`)
|
||||
foreignNonce[epoch] = 0
|
||||
await processEpochStart(event)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const transferEvents = await token.getPastEvents('Transfer', {
|
||||
filter: {
|
||||
to: HOME_BRIDGE_ADDRESS
|
||||
},
|
||||
fromBlock: blockNumber,
|
||||
toBlock: blockNumber
|
||||
})
|
||||
if ((blockNumber + 1 - epochStart) % rangeSize === 0) {
|
||||
logger.info('Reached end of the current block range')
|
||||
|
||||
for (const event of transferEvents) {
|
||||
await sendSign(event)
|
||||
if (lastTransactionBlockNumber > blockNumber - rangeSize) {
|
||||
logger.info('Sending message to start signature generation for the ended range')
|
||||
await sendStartSign()
|
||||
}
|
||||
}
|
||||
|
||||
blockNumber++
|
||||
// Exec redis tx
|
||||
await redisTx.incr('homeBlock').exec()
|
||||
await redis.save()
|
||||
}
|
||||
|
||||
initialize().then(async () => {
|
||||
|
@ -122,45 +168,42 @@ initialize().then(async () => {
|
|||
|
||||
async function sendKeygen (event) {
|
||||
const newEpoch = event.returnValues.newEpoch.toNumber()
|
||||
channel.sendToQueue(keygenQueue.queue, Buffer.from(JSON.stringify({
|
||||
keygenQueue.send({
|
||||
epoch: newEpoch,
|
||||
blockNumber,
|
||||
threshold: (await bridge.methods.getThreshold(newEpoch).call()).toNumber(),
|
||||
parties: (await bridge.methods.getParties(newEpoch).call()).toNumber()
|
||||
})), {
|
||||
persistent: true
|
||||
})
|
||||
console.log('Sent keygen start event')
|
||||
logger.debug('Sent keygen start event')
|
||||
}
|
||||
|
||||
function sendKeygenCancelation (event) {
|
||||
function sendKeygenCancellation (event) {
|
||||
const epoch = event.returnValues.epoch.toNumber()
|
||||
channel.sendToQueue(cancelKeygenQueue.queue, Buffer.from(JSON.stringify({
|
||||
epoch
|
||||
})), {
|
||||
persistent: true
|
||||
cancelKeygenQueue.send({
|
||||
epoch,
|
||||
blockNumber
|
||||
})
|
||||
console.log('Sent keygen cancellation event')
|
||||
logger.debug('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({
|
||||
signQueue.send({
|
||||
epoch: oldEpoch,
|
||||
blockNumber,
|
||||
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')
|
||||
logger.debug('Sent sign funds transfer event')
|
||||
foreignNonce[oldEpoch]++
|
||||
redisTx.incr(`foreignNonce${oldEpoch}`)
|
||||
}
|
||||
|
||||
async function sendSign (event) {
|
||||
const tx = await web3Home.eth.getTransaction(event.transactionHash)
|
||||
const tx = await homeWeb3.eth.getTransaction(event.transactionHash)
|
||||
const msg = utils.serializeTransaction({
|
||||
nonce: tx.nonce,
|
||||
gasPrice: `0x${new BN(tx.gasPrice).toString(16)}`,
|
||||
|
@ -168,42 +211,51 @@ async function sendSign (event) {
|
|||
to: tx.to,
|
||||
value: `0x${new BN(tx.value).toString(16)}`,
|
||||
data: tx.input,
|
||||
chainId: parseInt(HOME_CHAIN_ID)
|
||||
chainId: await homeWeb3.eth.net.getId()
|
||||
})
|
||||
const hash = web3Home.utils.sha3(msg)
|
||||
const hash = homeWeb3.utils.sha3(msg)
|
||||
const publicKey = utils.recoverPublicKey(hash, { r: tx.r, s: tx.s, v: tx.v })
|
||||
const msgToQueue = JSON.stringify({
|
||||
const msgToQueue = {
|
||||
epoch,
|
||||
blockNumber,
|
||||
recipient: publicKeyToAddress({
|
||||
x: publicKey.substr(4, 64),
|
||||
y: publicKey.substr(68, 64)
|
||||
}),
|
||||
value: (new BN(event.returnValues.value)).dividedBy(10 ** 18).toFixed(8, 3),
|
||||
nonce: event.returnValues.nonce.toNumber()
|
||||
}
|
||||
|
||||
exchangeQueue.send(msgToQueue)
|
||||
logger.debug('Sent new sign event: %o', msgToQueue)
|
||||
|
||||
lastTransactionBlockNumber = blockNumber
|
||||
redisTx.set('lastTransactionBlockNumber', blockNumber)
|
||||
logger.debug(`Set lastTransactionBlockNumber to ${blockNumber}`)
|
||||
}
|
||||
|
||||
async function sendStartSign () {
|
||||
redisTx.incr(`foreignNonce${epoch}`)
|
||||
signQueue.send({
|
||||
epoch,
|
||||
nonce: foreignNonce[epoch],
|
||||
blockNumber,
|
||||
nonce: foreignNonce[epoch]++,
|
||||
threshold: (await bridge.methods.getThreshold(epoch).call()).toNumber(),
|
||||
parties: (await bridge.methods.getParties(epoch).call()).toNumber()
|
||||
})
|
||||
|
||||
channel.sendToQueue(signQueue.queue, Buffer.from(msgToQueue), {
|
||||
persistent: true
|
||||
})
|
||||
console.log('Sent new sign event')
|
||||
console.log(msgToQueue)
|
||||
|
||||
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
|
||||
async function processEpochStart (event) {
|
||||
epoch = event.returnValues.epoch.toNumber()
|
||||
epochStart = blockNumber
|
||||
logger.info(`Epoch ${epoch} started`)
|
||||
rangeSize = (await bridge.methods.getRangeSize().call()).toNumber()
|
||||
isCurrentValidator = (await bridge.methods.getValidators().call()).includes(validatorAddress)
|
||||
if (isCurrentValidator) {
|
||||
logger.info(`${validatorAddress} is a current validator`)
|
||||
} else {
|
||||
logger.info(`${validatorAddress} is not a current validator`)
|
||||
}
|
||||
logger.info(`Updated range size to ${rangeSize}`)
|
||||
foreignNonce[epoch] = 0
|
||||
}
|
||||
|
|
|
@ -7,6 +7,9 @@
|
|||
"web3": "1.0.0-beta.55",
|
||||
"ethers": "4.0.33",
|
||||
"bignumber.js": "9.0.0",
|
||||
"bech32": "1.1.3"
|
||||
"bech32": "1.1.3",
|
||||
"pino": "5.13.4",
|
||||
"pino-pretty": "3.2.1",
|
||||
"axios": "0.19.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,10 +2,10 @@ FROM node:10.16.0-alpine
|
|||
|
||||
WORKDIR /proxy
|
||||
|
||||
COPY package.json /proxy/
|
||||
COPY ./proxy/package.json /proxy/
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY index.js encode.js decode.js /proxy/
|
||||
COPY ./proxy/index.js ./proxy/encode.js ./proxy/decode.js ./proxy/sendTx.js ./shared/logger.js ./shared/crypto.js /proxy/
|
||||
|
||||
ENTRYPOINT ["node", "index.js"]
|
||||
|
|
|
@ -224,7 +224,5 @@ module.exports = function (isKeygen, round, value) {
|
|||
const tokenizer = Tokenizer(value)
|
||||
const roundNumber = parseInt(round[round.length - 1])
|
||||
const decoder = (isKeygen ? keygenDecoders : signDecoders)[roundNumber]
|
||||
const decoded = JSON.stringify(decoder(tokenizer))
|
||||
console.log(decoded)
|
||||
return decoded
|
||||
return JSON.stringify(decoder(tokenizer))
|
||||
}
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
const BN = require('bignumber.js')
|
||||
|
||||
function padZeros (s, len) {
|
||||
while (s.length < len)
|
||||
s = '0' + s
|
||||
return s
|
||||
}
|
||||
const { padZeros } = require('./crypto')
|
||||
|
||||
function makeBuffer (value, length = 32, base = 16) {
|
||||
return Buffer.from(padZeros(new BN(value, base).toString(16), length * 2), 'hex')
|
||||
|
@ -137,11 +133,5 @@ module.exports = function (isKeygen, round, value) {
|
|||
break
|
||||
buffers.push(next.value)
|
||||
}
|
||||
const encoded = Buffer.concat(buffers)
|
||||
console.log(`Raw data: ${value.length} bytes, encoded data: ${encoded.length} bytes`)
|
||||
return encoded
|
||||
return Buffer.concat(buffers)
|
||||
}
|
||||
|
||||
|
||||
module.exports(true, 'round2', '{"blind_factor":"11223344556677889900", "y_i":{"x":"00112233445566778899", "y":"00112233445566778899"}}')
|
||||
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
const express = require('express')
|
||||
const Web3 = require('web3')
|
||||
const AsyncLock = require('async-lock')
|
||||
const crypto = require('crypto')
|
||||
const bech32 = require('bech32')
|
||||
const axios = require('axios')
|
||||
const BN = require('bignumber.js')
|
||||
const { utils } = require('ethers')
|
||||
|
||||
const encode = require('./encode')
|
||||
const decode = require('./decode')
|
||||
const { createSender, waitForReceipt } = require('./sendTx')
|
||||
const logger = require('./logger')
|
||||
const { publicKeyToAddress } = require('./crypto')
|
||||
|
||||
const {
|
||||
HOME_RPC_URL, HOME_BRIDGE_ADDRESS, SIDE_RPC_URL, SIDE_SHARED_DB_ADDRESS, VALIDATOR_PRIVATE_KEY, HOME_CHAIN_ID,
|
||||
|
@ -30,6 +32,8 @@ const lock = new AsyncLock()
|
|||
|
||||
let homeValidatorNonce
|
||||
let sideValidatorNonce
|
||||
let homeSender
|
||||
let sideSender
|
||||
|
||||
const app = express()
|
||||
app.use(express.json())
|
||||
|
@ -48,22 +52,29 @@ const votesProxyApp = express()
|
|||
votesProxyApp.use(express.json())
|
||||
votesProxyApp.use(express.urlencoded({ extended: true }))
|
||||
|
||||
votesProxyApp.get('/vote/startVoting', voteStartVoting)
|
||||
votesProxyApp.get('/vote/startKeygen', voteStartKeygen)
|
||||
votesProxyApp.get('/vote/cancelKeygen', voteCancelKeygen)
|
||||
votesProxyApp.get('/vote/addValidator/:validator', voteAddValidator)
|
||||
votesProxyApp.get('/vote/removeValidator/:validator', voteRemoveValidator)
|
||||
votesProxyApp.get('/vote/changeThreshold/:threshold', voteChangeThreshold)
|
||||
votesProxyApp.get('/info', info)
|
||||
|
||||
async function main () {
|
||||
homeValidatorNonce = await homeWeb3.eth.getTransactionCount(validatorAddress)
|
||||
sideValidatorNonce = await sideWeb3.eth.getTransactionCount(validatorAddress)
|
||||
|
||||
homeSender = await createSender(HOME_RPC_URL, VALIDATOR_PRIVATE_KEY)
|
||||
sideSender = await createSender(SIDE_RPC_URL, VALIDATOR_PRIVATE_KEY)
|
||||
|
||||
logger.warn(`My validator address in home and side networks is ${validatorAddress}`)
|
||||
|
||||
app.listen(8001, () => {
|
||||
console.log('Proxy is listening on port 8001')
|
||||
logger.debug('Proxy is listening on port 8001')
|
||||
})
|
||||
|
||||
votesProxyApp.listen(8002, () => {
|
||||
console.log('Votes proxy is listening on port 8001')
|
||||
logger.debug('Votes proxy is listening on port 8002')
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -78,8 +89,7 @@ function Err (data) {
|
|||
}
|
||||
|
||||
async function get (req, res) {
|
||||
console.log('Get call')
|
||||
console.log(req.body.key)
|
||||
logger.debug('Get call, %o', req.body.key)
|
||||
const round = req.body.key.second
|
||||
const uuid = req.body.key.third
|
||||
let from
|
||||
|
@ -95,59 +105,60 @@ async function get (req, res) {
|
|||
const data = await sharedDb.methods.getData(from, sideWeb3.utils.sha3(uuid), key).call()
|
||||
|
||||
if (data.length > 2) {
|
||||
console.log(data)
|
||||
logger.trace(`Received encoded data: ${data}`)
|
||||
const decoded = decode(uuid[0] === 'k', round, data)
|
||||
console.log(decoded)
|
||||
logger.trace('Decoded data: %o', decoded)
|
||||
res.send(Ok({ key: req.body.key, value: decoded }))
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
setTimeout(() => res.send(Err(null)), 1000)
|
||||
}
|
||||
|
||||
console.log('Get end')
|
||||
logger.debug('Get end')
|
||||
}
|
||||
|
||||
async function set (req, res) {
|
||||
console.log('Set call')
|
||||
logger.debug('Set call')
|
||||
const round = req.body.key.second
|
||||
const uuid = req.body.key.third
|
||||
const to = Number(req.body.key.fourth)
|
||||
const key = homeWeb3.utils.sha3(`${round}_${to}`)
|
||||
|
||||
console.log(req.body.value)
|
||||
logger.trace('Received data: %o', req.body.value)
|
||||
const encoded = encode(uuid[0] === 'k', round, req.body.value)
|
||||
console.log(encoded.toString('hex'))
|
||||
|
||||
logger.trace(`Encoded data: ${encoded.toString('hex')}`)
|
||||
logger.trace(`Received data: ${req.body.value.length} bytes, encoded data: ${encoded.length} bytes`)
|
||||
const query = sharedDb.methods.setData(sideWeb3.utils.sha3(uuid), key, encoded)
|
||||
await sideSendQuery(query)
|
||||
|
||||
res.send(Ok(null))
|
||||
console.log('Set end')
|
||||
logger.debug('Set end')
|
||||
}
|
||||
|
||||
async function signupKeygen (req, res) {
|
||||
console.log('SignupKeygen call')
|
||||
logger.debug('SignupKeygen call')
|
||||
const epoch = (await bridge.methods.nextEpoch().call()).toNumber()
|
||||
const partyId = (await bridge.methods.getNextPartyId(validatorAddress).call()).toNumber()
|
||||
|
||||
if (partyId === 0) {
|
||||
res.send(Err({ message: 'Not a validator' }))
|
||||
logger.debug('Not a validator')
|
||||
} else {
|
||||
res.send(Ok({ uuid: `k${epoch}`, number: partyId }))
|
||||
console.log('SignupKeygen end')
|
||||
logger.debug('SignupKeygen end')
|
||||
}
|
||||
}
|
||||
|
||||
async function signupSign (req, res) {
|
||||
console.log('SignupSign call')
|
||||
logger.debug('SignupSign call')
|
||||
const hash = sideWeb3.utils.sha3(`0x${req.body.third}`)
|
||||
const query = sharedDb.methods.signupSign(hash)
|
||||
const receipt = await sideSendQuery(query)
|
||||
const { txHash } = await sideSendQuery(query)
|
||||
const receipt = await waitForReceipt(SIDE_RPC_URL, txHash)
|
||||
|
||||
// Already have signup
|
||||
if (receipt === false) {
|
||||
console.log('Already have signup')
|
||||
if (receipt.status === false) {
|
||||
res.send(Ok({ uuid: hash, number: 0 }))
|
||||
logger.debug('Already have signup')
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -155,98 +166,48 @@ async function signupSign (req, res) {
|
|||
const id = (await sharedDb.methods.getSignupNumber(hash, validators, validatorAddress).call()).toNumber()
|
||||
|
||||
res.send(Ok({ uuid: hash, number: id }))
|
||||
console.log('SignupSign end')
|
||||
logger.debug('SignupSign end')
|
||||
}
|
||||
|
||||
async function confirmKeygen (req, res) {
|
||||
console.log('Confirm keygen call')
|
||||
logger.debug('Confirm keygen call')
|
||||
const { x, y } = req.body[5]
|
||||
const query = bridge.methods.confirmKeygen(`0x${x}`, `0x${y}`)
|
||||
await homeSendQuery(query)
|
||||
res.send()
|
||||
console.log('Confirm keygen end')
|
||||
logger.debug('Confirm keygen end')
|
||||
}
|
||||
|
||||
async function confirmFundsTransfer (req, res) {
|
||||
console.log('Confirm funds transfer call')
|
||||
logger.debug('Confirm funds transfer call')
|
||||
const query = bridge.methods.confirmFundsTransfer()
|
||||
await homeSendQuery(query)
|
||||
res.send()
|
||||
console.log('Confirm funds transfer end')
|
||||
logger.debug('Confirm funds transfer end')
|
||||
}
|
||||
|
||||
function sideSendQuery (query) {
|
||||
return lock.acquire('side', async () => {
|
||||
console.log('Sending query')
|
||||
return lock.acquire('home', async () => {
|
||||
logger.debug('Sending side query')
|
||||
const encodedABI = query.encodeABI()
|
||||
const tx = {
|
||||
return await sideSender({
|
||||
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)
|
||||
|
||||
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')
|
||||
return true
|
||||
} else {
|
||||
console.log('Home tx failed', e.message)
|
||||
return false
|
||||
}
|
||||
})
|
||||
})
|
||||
.then(result => {
|
||||
if (result === true)
|
||||
return sideSendQuery(query)
|
||||
return result
|
||||
nonce: sideValidatorNonce++
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function homeSendQuery (query) {
|
||||
return lock.acquire('home', async () => {
|
||||
logger.debug('Sending home query')
|
||||
const encodedABI = query.encodeABI()
|
||||
const tx = {
|
||||
return await homeSender({
|
||||
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)
|
||||
console.log(tx)
|
||||
const signedTx = await homeWeb3.eth.accounts.signTransaction(tx, VALIDATOR_PRIVATE_KEY)
|
||||
|
||||
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')
|
||||
return true
|
||||
} else {
|
||||
console.log('Home tx failed', e.message)
|
||||
return false
|
||||
}
|
||||
})
|
||||
})
|
||||
.then(result => {
|
||||
if (result === true)
|
||||
return homeSendQuery(query)
|
||||
return result
|
||||
nonce: homeValidatorNonce++
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function parseReason (message) {
|
||||
|
@ -259,99 +220,160 @@ function parseError (message) {
|
|||
return result ? result[0] : ''
|
||||
}
|
||||
|
||||
async function voteStartKeygen (req, res) {
|
||||
console.log('Voting for starting new epoch keygen')
|
||||
const query = bridge.methods.voteStartKeygen()
|
||||
async function sendVote (query, req, res, waitFlag = false) {
|
||||
try {
|
||||
await homeSendQuery(query)
|
||||
let { txHash, gasLimit } = await homeSendQuery(query)
|
||||
if (txHash) {
|
||||
while (waitFlag) {
|
||||
const { status, gasUsed } = await waitForReceipt(HOME_RPC_URL, txHash)
|
||||
if (status === '0x1') {
|
||||
logger.debug('Receipt status is OK')
|
||||
break
|
||||
}
|
||||
if (gasLimit === gasUsed) {
|
||||
logger.info('Sending vote failed due to out of gas revert, retrying with more gas')
|
||||
const nexTx = await homeSendQuery(query)
|
||||
txHash = nexTx.txHash
|
||||
gasLimit = nexTx.gasLimit
|
||||
} else {
|
||||
logger.warn(`Vote tx was reverted, txHash ${txHash}`)
|
||||
break
|
||||
}
|
||||
}
|
||||
res.send('Voted\n')
|
||||
logger.info('Voted successfully')
|
||||
} else {
|
||||
res.send('Failed\n')
|
||||
logger.info('Failed to vote')
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
logger.debug(e)
|
||||
}
|
||||
res.send('Voted')
|
||||
console.log('Voted successfully')
|
||||
}
|
||||
|
||||
async function voteStartVoting (req, res) {
|
||||
logger.info('Voting for starting new epoch voting process')
|
||||
const query = bridge.methods.startVoting()
|
||||
sendVote(query, req, res, true)
|
||||
}
|
||||
|
||||
async function voteStartKeygen (req, res) {
|
||||
logger.info('Voting for starting new epoch keygen')
|
||||
const query = bridge.methods.voteStartKeygen()
|
||||
sendVote(query, req, res)
|
||||
}
|
||||
|
||||
async function voteCancelKeygen (req, res) {
|
||||
console.log('Voting for cancelling new epoch keygen')
|
||||
logger.info('Voting for cancelling new epoch keygen')
|
||||
const query = bridge.methods.voteCancelKeygen()
|
||||
try {
|
||||
await homeSendQuery(query)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
res.send('Voted')
|
||||
console.log('Voted successfully')
|
||||
sendVote(query, req, res)
|
||||
}
|
||||
|
||||
async function voteAddValidator (req, res) {
|
||||
console.log('Voting for adding new validator')
|
||||
logger.info('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')
|
||||
sendVote(query, req, res)
|
||||
}
|
||||
|
||||
async function voteChangeThreshold (req, res) {
|
||||
logger.info('Voting for changing threshold')
|
||||
const query = bridge.methods.voteChangeThreshold(req.params.threshold)
|
||||
sendVote(query, req, res)
|
||||
}
|
||||
|
||||
async function voteRemoveValidator (req, res) {
|
||||
console.log('Voting for removing validator')
|
||||
logger.info('Voting for removing validator')
|
||||
const query = bridge.methods.voteRemoveValidator(req.params.validator)
|
||||
try {
|
||||
await homeSendQuery(query)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
sendVote(query, req, res, true)
|
||||
}
|
||||
|
||||
function decodeStatus (status) {
|
||||
switch (status) {
|
||||
case 0:
|
||||
return 'ready'
|
||||
case 1:
|
||||
return 'voting'
|
||||
case 2:
|
||||
return 'keygen'
|
||||
case 3:
|
||||
return 'funds_transfer'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function boundX(x) {
|
||||
try {
|
||||
return x.toNumber()
|
||||
} catch (e) {
|
||||
return -1
|
||||
}
|
||||
res.send('Voted')
|
||||
console.log('Voted successfully')
|
||||
}
|
||||
|
||||
async function info (req, res) {
|
||||
console.log('Info start')
|
||||
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,
|
||||
nextEpoch,
|
||||
threshold,
|
||||
nextThreshold,
|
||||
homeBridgeAddress: HOME_BRIDGE_ADDRESS,
|
||||
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')
|
||||
logger.debug('Info start')
|
||||
try {
|
||||
const [ x, y, epoch, rangeSize, nextRangeSize, epochStartBlock, foreignNonce, nextEpoch, threshold, nextThreshold, validators, nextValidators, status, homeBalance ] = 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.getRangeSize().call().then(x => x.toNumber()),
|
||||
bridge.methods.getNextRangeSize().call().then(x => x.toNumber()),
|
||||
bridge.methods.getStartBlock().call().then(x => x.toNumber()),
|
||||
bridge.methods.getNonce().call().then(boundX),
|
||||
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(),
|
||||
bridge.methods.status().call(),
|
||||
token.methods.balanceOf(HOME_BRIDGE_ADDRESS).call().then(x => parseFloat(new BN(x).dividedBy(10 ** 18).toFixed(8, 3)))
|
||||
])
|
||||
const [ confirmationsForFundsTransfer, votesForVoting, votesForKeygen, votesForCancelKeygen ] = await Promise.all([
|
||||
bridge.methods.votesCount(homeWeb3.utils.sha3(utils.solidityPack([ 'uint8', 'uint256' ], [ 1, nextEpoch ]))).call().then(boundX),
|
||||
bridge.methods.votesCount(homeWeb3.utils.sha3(utils.solidityPack([ 'uint8', 'uint256' ], [ 2, nextEpoch ]))).call().then(boundX),
|
||||
bridge.methods.votesCount(homeWeb3.utils.sha3(utils.solidityPack([ 'uint8', 'uint256' ], [ 7, nextEpoch ]))).call().then(boundX),
|
||||
bridge.methods.votesCount(homeWeb3.utils.sha3(utils.solidityPack([ 'uint8', 'uint256' ], [ 8, nextEpoch ]))).call().then(boundX)
|
||||
])
|
||||
const foreignAddress = publicKeyToAddress({ x, y })
|
||||
const balances = await getForeignBalances(foreignAddress)
|
||||
res.send({
|
||||
epoch,
|
||||
rangeSize,
|
||||
nextRangeSize,
|
||||
epochStartBlock,
|
||||
nextEpoch,
|
||||
threshold,
|
||||
nextThreshold,
|
||||
homeBridgeAddress: HOME_BRIDGE_ADDRESS,
|
||||
foreignBridgeAddress: foreignAddress,
|
||||
foreignNonce,
|
||||
validators,
|
||||
nextValidators,
|
||||
homeBalance,
|
||||
foreignBalanceTokens: parseFloat(balances[FOREIGN_ASSET]) || 0,
|
||||
foreignBalanceNative: parseFloat(balances['BNB']) || 0,
|
||||
bridgeStatus: decodeStatus(status),
|
||||
votesForVoting,
|
||||
votesForKeygen,
|
||||
votesForCancelKeygen,
|
||||
confirmationsForFundsTransfer
|
||||
})
|
||||
} catch (e) {
|
||||
res.send({ message: 'Something went wrong, resend request', error: e })
|
||||
}
|
||||
logger.debug('Info end')
|
||||
}
|
||||
|
||||
async function transfer (req, res) {
|
||||
console.log('Transfer start')
|
||||
logger.info('Transfer start')
|
||||
const { hash, to, value } = req.body
|
||||
if (homeWeb3.utils.isAddress(to)) {
|
||||
console.log(`Calling transfer to ${to}, ${value} tokens`)
|
||||
logger.info(`Calling transfer to ${to}, ${value} tokens`)
|
||||
const query = bridge.methods.transfer(hash, to, '0x' + (new BN(value).toString(16)))
|
||||
await homeSendQuery(query)
|
||||
} else {
|
||||
// return funds ?
|
||||
}
|
||||
res.send()
|
||||
console.log('Transfer end')
|
||||
logger.info('Transfer end')
|
||||
}
|
||||
|
||||
function getForeignBalances (address) {
|
||||
|
@ -363,19 +385,3 @@ function getForeignBalances (address) {
|
|||
}, {}))
|
||||
.catch(err => ({}))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -8,6 +8,9 @@
|
|||
"async-lock": "1.2.0",
|
||||
"axios": "0.19.0",
|
||||
"bignumber.js": "9.0.0",
|
||||
"bn.js": "5.0.0"
|
||||
"bn.js": "5.0.0",
|
||||
"ethers": "4.0.37",
|
||||
"pino": "5.13.4",
|
||||
"pino-pretty": "3.2.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
const Web3 = require('web3')
|
||||
const axios = require('axios')
|
||||
const ethers = require('ethers')
|
||||
const BN = require('bignumber.js')
|
||||
|
||||
const logger = require('./logger')
|
||||
|
||||
const { GAS_LIMIT_FACTOR, MAX_GAS_LIMIT } = process.env
|
||||
|
||||
function sendRpcRequest (url, method, params) {
|
||||
return axios.post(url, {
|
||||
jsonrpc: '2.0',
|
||||
method,
|
||||
params,
|
||||
id: 1
|
||||
})
|
||||
.then(res => res.data)
|
||||
.catch(async e => {
|
||||
logger.warn(`Request to ${url}, method ${method} failed, retrying`)
|
||||
await new Promise(res => setTimeout(res, 1000))
|
||||
return sendRpcRequest(url, method, params)
|
||||
})
|
||||
}
|
||||
|
||||
async function createSender (url, privateKey) {
|
||||
const web3 = new Web3(url, null, { transactionConfirmationBlocks: 1 })
|
||||
const signer = new ethers.utils.SigningKey(privateKey)
|
||||
|
||||
const chainId = await web3.eth.net.getId()
|
||||
return async function (tx) {
|
||||
tx = {
|
||||
data: tx.data,
|
||||
to: tx.to,
|
||||
nonce: tx.nonce,
|
||||
chainId,
|
||||
value: `0x${new BN(tx.value || 0).toString(16)}`,
|
||||
gasPrice: `0x${new BN(tx.gasPrice || 1000000000).toString(16)}`
|
||||
}
|
||||
|
||||
try {
|
||||
logger.trace(`Preparing and sending transaction %o on ${url}`, tx)
|
||||
const estimate = await sendRpcRequest(url, 'eth_estimateGas', [ {
|
||||
from: signer.address,
|
||||
to: tx.to,
|
||||
data: tx.data,
|
||||
gasPrice: tx.gasPrice,
|
||||
value: tx.value,
|
||||
gas: `0x${new BN(MAX_GAS_LIMIT).toString(16)}`
|
||||
} ])
|
||||
|
||||
if (estimate.error) {
|
||||
logger.debug('Gas estimate failed %o', estimate.error)
|
||||
return false
|
||||
}
|
||||
const gasLimit = BN.min(new BN(estimate.result, 16).multipliedBy(GAS_LIMIT_FACTOR), MAX_GAS_LIMIT)
|
||||
tx.gasLimit = `0x${new BN(gasLimit).toString(16)}`
|
||||
logger.trace(`Estimated gas to ${gasLimit}`)
|
||||
|
||||
const hash = web3.utils.sha3(ethers.utils.serializeTransaction(tx))
|
||||
const signature = signer.signDigest(hash)
|
||||
const signedTx = ethers.utils.serializeTransaction(tx, signature)
|
||||
|
||||
const { result, error } = await sendRpcRequest(url, 'eth_sendRawTransaction', [ signedTx ])
|
||||
// handle nonce error
|
||||
// handle insufficient funds error
|
||||
if (error) {
|
||||
logger.debug('Sending signed tx %o failed, %o', tx, error)
|
||||
return false
|
||||
}
|
||||
|
||||
return { txHash: result, gasLimit: tx.gasLimit }
|
||||
} catch (e) {
|
||||
logger.warn('Something failed, %o', e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function waitForReceipt (url, txHash) {
|
||||
while (true) {
|
||||
const { result, error } = await sendRpcRequest(url, 'eth_getTransactionReceipt', [ txHash ])
|
||||
|
||||
if (result === null || error) {
|
||||
await new Promise(res => setTimeout(res, 1000))
|
||||
} else {
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { createSender, waitForReceipt }
|
|
@ -0,0 +1,11 @@
|
|||
FROM node:10.16.0-alpine
|
||||
|
||||
WORKDIR /test
|
||||
|
||||
COPY package.json /test/
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY resetToBlock.js /test/
|
||||
|
||||
ENTRYPOINT ["node", "resetToBlock.js"]
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"name": "reset-to-block",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"ioredis": "4.14.1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
const Redis = require('ioredis')
|
||||
|
||||
const redis = new Redis({
|
||||
port: 6379,
|
||||
host: 'redis',
|
||||
family: 4,
|
||||
db: 0
|
||||
})
|
||||
|
||||
redis.on('error', () => {
|
||||
console.log('Error: Cannot connect to redis')
|
||||
})
|
||||
|
||||
redis.on('connect', async () => {
|
||||
await redis.set('homeBlock', parseInt(process.argv[2]))
|
||||
await redis.save()
|
||||
redis.disconnect()
|
||||
})
|
|
@ -0,0 +1,9 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
cd $(dirname "$0")
|
||||
|
||||
docker build -t reset-to-block . > /dev/null
|
||||
|
||||
docker run --rm --network $1 reset-to-block $2
|
|
@ -0,0 +1,31 @@
|
|||
const amqp = require('amqplib')
|
||||
|
||||
const logger = require('./logger')
|
||||
|
||||
function _connectRabbit (url) {
|
||||
return amqp.connect(url).catch(() => {
|
||||
logger.debug('Failed to connect to rabbitmqServer, reconnecting')
|
||||
return new Promise(resolve =>
|
||||
setTimeout(() => resolve(_connectRabbit(url)), 2000)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
async function connectRabbit(url) {
|
||||
const connection = await _connectRabbit(url)
|
||||
return await connection.createChannel()
|
||||
}
|
||||
|
||||
async function assertQueue (channel, name) {
|
||||
const queue = await channel.assertQueue(name)
|
||||
return {
|
||||
name: queue.queue,
|
||||
send: msg => channel.sendToQueue(queue.queue, Buffer.from(JSON.stringify(msg)), {
|
||||
persistent: true
|
||||
}),
|
||||
get: consumer => channel.get(queue.queue, consumer),
|
||||
consume: consumer => channel.consume(queue.queue, consumer)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { connectRabbit, assertQueue }
|
|
@ -0,0 +1,26 @@
|
|||
const crypto = require('crypto')
|
||||
const bech32 = require('bech32')
|
||||
|
||||
function publicKeyToAddress ({ x, y }) {
|
||||
const compact = (parseInt(y[y.length - 1], 16) % 2 ? '03' : '02') + padZeros(x, 64)
|
||||
const sha256Hash = sha256(Buffer.from(compact, 'hex'))
|
||||
const hash = ripemd160(Buffer.from(sha256Hash, '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
|
||||
}
|
||||
|
||||
function sha256 (bytes) {
|
||||
return crypto.createHash('sha256').update(bytes).digest('hex')
|
||||
}
|
||||
|
||||
function ripemd160 (bytes) {
|
||||
return crypto.createHash('ripemd160').update(bytes).digest('hex')
|
||||
}
|
||||
|
||||
module.exports = { publicKeyToAddress, padZeros, sha256 }
|
|
@ -1,21 +1,21 @@
|
|||
const Redis = require('ioredis')
|
||||
|
||||
console.log('Connecting to redis')
|
||||
const logger = require('./logger')
|
||||
logger.info('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')
|
||||
logger.info('Connected to redis')
|
||||
})
|
||||
|
||||
redis.on('error', () => {
|
||||
console.log('Redis error')
|
||||
redis.on('error', e => {
|
||||
logger.warn('Redis error %o', e)
|
||||
})
|
||||
|
||||
module.exports = redis
|
|
@ -0,0 +1,13 @@
|
|||
const pino = require('pino')
|
||||
|
||||
const logger = pino({
|
||||
name: 'logger',
|
||||
prettyPrint: {
|
||||
colorize: true,
|
||||
ignore: 'time,pid,name,hostname'
|
||||
},
|
||||
level: process.env.LOG_LEVEL || 'debug',
|
||||
base: {}
|
||||
})
|
||||
|
||||
module.exports = logger
|
|
@ -5,11 +5,11 @@ WORKDIR /tss
|
|||
RUN apt-get update && \
|
||||
apt-get install -y libssl1.1 libssl-dev curl procps
|
||||
|
||||
COPY package.json /tss/
|
||||
COPY ./tss-keygen/package.json /tss/
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY keygen-entrypoint.sh keygen.js /tss/
|
||||
COPY ./tss-keygen/keygen-entrypoint.sh ./tss-keygen/keygen.js ./shared/logger.js ./shared/amqp.js ./shared/crypto.js /tss/
|
||||
|
||||
COPY --from=tss /tss/target/release/gg18_keygen_client /tss/
|
||||
|
||||
|
|
|
@ -1,57 +1,71 @@
|
|||
const exec = require('child_process')
|
||||
const fs = require('fs')
|
||||
const crypto = require('crypto')
|
||||
const bech32 = require('bech32')
|
||||
const amqp = require('amqplib')
|
||||
const express = require('express')
|
||||
|
||||
const logger = require('./logger')
|
||||
const { connectRabbit, assertQueue } = require('./amqp')
|
||||
const { publicKeyToAddress } = require('./crypto')
|
||||
|
||||
const { RABBITMQ_URL, PROXY_URL } = process.env
|
||||
|
||||
const app = express()
|
||||
app.get('/start', (req, res) => {
|
||||
logger.info('Ready to start')
|
||||
ready = true
|
||||
res.send()
|
||||
})
|
||||
app.listen(8001, () => logger.debug('Listening on 8001'))
|
||||
|
||||
let currentKeygenEpoch = null
|
||||
let ready = false
|
||||
|
||||
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 keygenQueue = await channel.assertQueue('keygenQueue')
|
||||
const cancelKeygenQueue = await channel.assertQueue('cancelKeygenQueue')
|
||||
logger.info('Connecting to RabbitMQ server')
|
||||
const channel = await connectRabbit(RABBITMQ_URL)
|
||||
logger.info('Connecting to epoch events queue')
|
||||
const keygenQueue = await assertQueue(channel, 'keygenQueue')
|
||||
const cancelKeygenQueue = await assertQueue(channel, 'cancelKeygenQueue')
|
||||
|
||||
while (!ready) {
|
||||
await new Promise(res => setTimeout(res, 1000))
|
||||
}
|
||||
|
||||
channel.prefetch(1)
|
||||
channel.consume(keygenQueue.queue, msg => {
|
||||
keygenQueue.consume(msg => {
|
||||
const { epoch, parties, threshold } = JSON.parse(msg.content)
|
||||
console.log(`Consumed new epoch event, starting keygen for epoch ${epoch}`)
|
||||
logger.info(`Consumed new epoch event, starting keygen for epoch ${epoch}`)
|
||||
|
||||
const keysFile = `/keys/keys${epoch}.store`
|
||||
|
||||
console.log('Running ./keygen-entrypoint.sh')
|
||||
logger.info('Running ./keygen-entrypoint.sh')
|
||||
currentKeygenEpoch = epoch
|
||||
|
||||
console.log('Writing params')
|
||||
logger.debug('Writing params')
|
||||
fs.writeFileSync('./params', JSON.stringify({ parties: parties.toString(), threshold: threshold.toString() }))
|
||||
const cmd = exec.execFile('./keygen-entrypoint.sh', [PROXY_URL, keysFile], async () => {
|
||||
const cmd = exec.execFile('./keygen-entrypoint.sh', [ PROXY_URL, keysFile ], async () => {
|
||||
currentKeygenEpoch = null
|
||||
if (fs.existsSync(keysFile)) {
|
||||
console.log(`Finished keygen for epoch ${epoch}`)
|
||||
logger.info(`Finished keygen for epoch ${epoch}`)
|
||||
const publicKey = JSON.parse(fs.readFileSync(keysFile))[5]
|
||||
console.log(`Generated multisig account in binance chain: ${publicKeyToAddress(publicKey)}`)
|
||||
logger.warn(`Generated multisig account in binance chain: ${publicKeyToAddress(publicKey)}`)
|
||||
|
||||
console.log('Sending keys confirmation')
|
||||
logger.info('Sending keys confirmation')
|
||||
await confirmKeygen(keysFile)
|
||||
} else {
|
||||
console.log(`Keygen for epoch ${epoch} failed`)
|
||||
logger.warn(`Keygen for epoch ${epoch} failed`)
|
||||
}
|
||||
console.log('Ack for keygen message')
|
||||
logger.debug('Ack for keygen message')
|
||||
channel.ack(msg)
|
||||
})
|
||||
cmd.stdout.on('data', data => console.log(data.toString()))
|
||||
cmd.stderr.on('data', data => console.error(data.toString()))
|
||||
cmd.stdout.on('data', data => logger.debug(data.toString()))
|
||||
cmd.stderr.on('data', data => logger.debug(data.toString()))
|
||||
})
|
||||
|
||||
channel.consume(cancelKeygenQueue.queue, async msg => {
|
||||
cancelKeygenQueue.consume(async msg => {
|
||||
const { epoch } = JSON.parse(msg.content)
|
||||
console.log(`Consumed new cancel event for epoch ${epoch} keygen`)
|
||||
logger.info(`Consumed new cancel event for epoch ${epoch} keygen`)
|
||||
if (currentKeygenEpoch === epoch) {
|
||||
console.log('Cancelling current keygen')
|
||||
logger.info('Cancelling current keygen')
|
||||
exec.execSync('pkill gg18_keygen || true')
|
||||
}
|
||||
channel.ack(msg)
|
||||
|
@ -60,29 +74,6 @@ async function main () {
|
|||
|
||||
main()
|
||||
|
||||
async function connectRabbit (url) {
|
||||
return amqp.connect(url).catch(() => {
|
||||
console.log('Failed to connect, reconnecting')
|
||||
return new Promise(resolve =>
|
||||
setTimeout(() => resolve(connectRabbit(url)), 1000)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
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 }) {
|
||||
const compact = (parseInt(y[y.length - 1], 16) % 2 ? '03' : '02') + padZeros(x, 64)
|
||||
const sha256Hash = crypto.createHash('sha256').update(Buffer.from(compact, 'hex')).digest('hex')
|
||||
const hash = crypto.createHash('ripemd160').update(Buffer.from(sha256Hash, 'hex')).digest('hex')
|
||||
const words = bech32.toWords(Buffer.from(hash, 'hex'))
|
||||
return bech32.encode('tbnb', words)
|
||||
}
|
||||
|
||||
function padZeros (s, len) {
|
||||
while (s.length < len)
|
||||
s = '0' + s
|
||||
return s
|
||||
}
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"amqplib": "0.5.3",
|
||||
"bech32": "1.1.3"
|
||||
"bech32": "1.1.3",
|
||||
"pino": "5.13.4",
|
||||
"pino-pretty": "3.2.1",
|
||||
"express": "4.17.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,11 +6,11 @@ RUN apt-get update && \
|
|||
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/
|
||||
COPY ./tss-sign/package.json /tss/
|
||||
|
||||
RUN npm install --no-optional
|
||||
|
||||
COPY sign-entrypoint.sh signer.js tx.js /tss/
|
||||
COPY ./tss-sign/sign-entrypoint.sh ./tss-sign/signer.js ./tss-sign/tx.js ./shared/logger.js ./shared/amqp.js ./shared/crypto.js /tss/
|
||||
|
||||
COPY --from=tss /tss/target/release/gg18_sign_client /tss/
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
"amqplib": "0.5.3",
|
||||
"axios": "0.19.0",
|
||||
"bignumber.js": "9.0.0",
|
||||
"express": "4.17.1"
|
||||
"express": "4.17.1",
|
||||
"pino": "5.13.4",
|
||||
"pino-pretty": "3.2.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,20 @@
|
|||
const exec = require('child_process')
|
||||
const fs = require('fs')
|
||||
const amqp = require('amqplib')
|
||||
const crypto = require('crypto')
|
||||
const bech32 = require('bech32')
|
||||
const BN = require('bignumber.js')
|
||||
const express = require('express')
|
||||
|
||||
const logger = require('./logger')
|
||||
const { connectRabbit, assertQueue } = require('./amqp')
|
||||
const { publicKeyToAddress, sha256 } = require('./crypto')
|
||||
|
||||
const app = express()
|
||||
app.get('/restart/:attempt', restart)
|
||||
app.listen(8001, () => console.log('Listening on 8001'))
|
||||
app.get('/start', (req, res) => {
|
||||
logger.info('Ready to start')
|
||||
ready = true
|
||||
res.send()
|
||||
})
|
||||
app.listen(8001, () => logger.debug('Listening on 8001'))
|
||||
|
||||
const { RABBITMQ_URL, FOREIGN_URL, PROXY_URL, FOREIGN_ASSET } = process.env
|
||||
const Transaction = require('./tx')
|
||||
|
@ -19,75 +25,96 @@ const httpClient = axios.create({ baseURL: FOREIGN_URL })
|
|||
let attempt
|
||||
let nextAttempt = null
|
||||
let cancelled
|
||||
let ready = false
|
||||
let exchangeQueue
|
||||
let channel
|
||||
|
||||
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 signQueue = await channel.assertQueue('signQueue')
|
||||
logger.info('Connecting to RabbitMQ server')
|
||||
channel = await connectRabbit(RABBITMQ_URL)
|
||||
logger.info('Connecting to signature events queue')
|
||||
exchangeQueue = await assertQueue(channel, 'exchangeQueue')
|
||||
const signQueue = await assertQueue(channel, 'signQueue')
|
||||
|
||||
while (!ready) {
|
||||
await new Promise(res => setTimeout(res, 1000))
|
||||
}
|
||||
|
||||
channel.prefetch(1)
|
||||
channel.consume(signQueue.queue, async msg => {
|
||||
signQueue.consume(async msg => {
|
||||
const data = JSON.parse(msg.content)
|
||||
|
||||
console.log('Consumed sign event')
|
||||
console.log(data)
|
||||
const { recipient, value, nonce, epoch, newEpoch, parties, threshold } = data
|
||||
logger.info('Consumed sign event: %o', data)
|
||||
const { nonce, epoch, newEpoch, parties, threshold } = data
|
||||
|
||||
const keysFile = `/keys/keys${epoch}.store`
|
||||
const { address: from, publicKey } = await getAccountFromFile(keysFile)
|
||||
const { address: from, publicKey } = getAccountFromFile(keysFile)
|
||||
if (from === '') {
|
||||
logger.info('No keys found, acking message')
|
||||
channel.ack(msg)
|
||||
return
|
||||
}
|
||||
const account = await getAccount(from)
|
||||
|
||||
console.log('Writing params')
|
||||
logger.debug('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}`
|
||||
})
|
||||
if (!newEpoch) {
|
||||
const exchanges = await getExchangeMessages(nonce)
|
||||
const exchangesData = exchanges.map(msg => JSON.parse(msg.content))
|
||||
|
||||
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 (exchanges.length > 0 && account.sequence <= nonce) {
|
||||
const recipients = exchangesData.map(({ value, recipient }) => ({ to: recipient, tokens: value }))
|
||||
|
||||
if (done) {
|
||||
break
|
||||
while (true) {
|
||||
logger.info(`Building corresponding transfer transaction, nonce ${nonce}`)
|
||||
|
||||
const tx = new Transaction({
|
||||
from,
|
||||
accountNumber: account.account_number,
|
||||
sequence: nonce,
|
||||
recipients,
|
||||
asset: FOREIGN_ASSET,
|
||||
memo: `Attempt ${attempt}`
|
||||
})
|
||||
|
||||
const hash = sha256(tx.getSignBytes())
|
||||
logger.info(`Starting signature generation for transaction hash ${hash}`)
|
||||
const done = await sign(keysFile, hash, tx, publicKey) && await waitForAccountNonce(from, nonce + 1)
|
||||
|
||||
if (done) {
|
||||
exchanges.forEach(msg => channel.ack(msg))
|
||||
break
|
||||
}
|
||||
attempt = nextAttempt ? nextAttempt : attempt + 1
|
||||
logger.warn(`Sign failed, starting next attempt ${attempt}`)
|
||||
nextAttempt = null
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
}
|
||||
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)
|
||||
const { address: to } = getAccountFromFile(newKeysFile)
|
||||
|
||||
while (true) {
|
||||
console.log(`Building corresponding transaction for transferring all funds, nonce ${nonce}, recipient ${to}`)
|
||||
while (to !== '') {
|
||||
logger.info(`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,
|
||||
recipients: [ {
|
||||
to,
|
||||
tokens: account.balances.find(x => x.symbol === FOREIGN_ASSET).free,
|
||||
bnbs: new BN(account.balances.find(x => x.symbol === 'BNB').free).minus(new BN(60000).div(10 ** 8)),
|
||||
} ],
|
||||
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 hash = sha256(tx.getSignBytes())
|
||||
logger.info(`Starting signature generation for transaction hash ${hash}`)
|
||||
const done = await sign(keysFile, hash, tx, publicKey) && await waitForAccountNonce(from, nonce + 1)
|
||||
|
||||
if (done) {
|
||||
|
@ -95,74 +122,84 @@ async function main () {
|
|||
break
|
||||
}
|
||||
attempt = nextAttempt ? nextAttempt : attempt + 1
|
||||
console.log(`Sign failed, starting next attempt ${attempt}`)
|
||||
logger.warn(`Sign failed, starting next attempt ${attempt}`)
|
||||
nextAttempt = null
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
}
|
||||
} else {
|
||||
logger.debug('Tx has been already sent')
|
||||
}
|
||||
else {
|
||||
console.log('Tx has been already sent')
|
||||
}
|
||||
console.log('Acking message')
|
||||
logger.info('Acking message')
|
||||
channel.ack(msg)
|
||||
})
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
async function getExchangeMessages (nonce) {
|
||||
logger.debug('Getting exchange messages')
|
||||
const messages = []
|
||||
do {
|
||||
const msg = await exchangeQueue.get()
|
||||
if (msg === false) {
|
||||
break
|
||||
}
|
||||
const data = JSON.parse(msg.content)
|
||||
logger.debug('Got message %o', data)
|
||||
if (data.nonce !== nonce) {
|
||||
channel.nack(msg, false, true)
|
||||
break
|
||||
}
|
||||
messages.push(msg)
|
||||
} while (true)
|
||||
logger.debug(`Found ${messages.length} messages`)
|
||||
return messages
|
||||
}
|
||||
|
||||
function sign (keysFile, hash, tx, publicKey) {
|
||||
return new Promise(resolve => {
|
||||
const cmd = exec.execFile('./sign-entrypoint.sh', [PROXY_URL, keysFile, hash], async (error) => {
|
||||
const cmd = exec.execFile('./sign-entrypoint.sh', [ PROXY_URL, keysFile, hash ], async (error) => {
|
||||
if (fs.existsSync('signature')) {
|
||||
console.log('Finished signature generation')
|
||||
logger.info('Finished signature generation')
|
||||
const signature = JSON.parse(fs.readFileSync('signature'))
|
||||
console.log(signature)
|
||||
logger.debug('%o', signature)
|
||||
|
||||
console.log('Building signed transaction')
|
||||
logger.info('Building signed transaction')
|
||||
const signedTx = tx.addSignature(publicKey, { r: signature[1], s: signature[3] })
|
||||
|
||||
console.log('Sending transaction')
|
||||
console.log(signedTx)
|
||||
logger.info('Sending transaction')
|
||||
logger.debug(signedTx)
|
||||
await sendTx(signedTx)
|
||||
resolve(true)
|
||||
} else if (error === null || error.code === 0) {
|
||||
resolve(true)
|
||||
} else {
|
||||
console.log('Sign failed')
|
||||
logger.warn('Sign failed')
|
||||
resolve(false)
|
||||
}
|
||||
})
|
||||
cmd.stdout.on('data', data => console.log(data.toString()))
|
||||
cmd.stderr.on('data', data => console.error(data.toString()))
|
||||
cmd.stdout.on('data', data => logger.debug(data.toString()))
|
||||
cmd.stderr.on('data', data => logger.debug(data.toString()))
|
||||
})
|
||||
}
|
||||
|
||||
function restart (req, res) {
|
||||
console.log('Cancelling current sign')
|
||||
logger.info('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 =>
|
||||
setTimeout(() => resolve(connectRabbit(url)), 1000)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
function confirmFundsTransfer () {
|
||||
exec.execSync(`curl -X POST -H "Content-Type: application/json" "${PROXY_URL}/confirmFundsTransfer"`, { stdio: 'pipe' })
|
||||
}
|
||||
|
||||
async function getAccountFromFile (file) {
|
||||
console.log(`Reading ${file}`)
|
||||
while (!fs.existsSync(file)) {
|
||||
console.log('Waiting for needed epoch key', file)
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
function getAccountFromFile (file) {
|
||||
logger.debug(`Reading ${file}`)
|
||||
if (!fs.existsSync(file)) {
|
||||
logger.debug('No keys found, skipping')
|
||||
return { address: '' }
|
||||
}
|
||||
const publicKey = JSON.parse(fs.readFileSync(file))[5]
|
||||
return {
|
||||
|
@ -173,25 +210,25 @@ async function getAccountFromFile (file) {
|
|||
|
||||
async function waitForAccountNonce (address, nonce) {
|
||||
cancelled = false
|
||||
console.log(`Waiting for account ${address} to have nonce ${nonce}`)
|
||||
logger.info(`Waiting for account ${address} to have nonce ${nonce}`)
|
||||
while (!cancelled) {
|
||||
const sequence = (await getAccount(address)).sequence
|
||||
if (sequence >= nonce)
|
||||
break
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
console.log('Waiting for needed account nonce')
|
||||
logger.debug('Waiting for needed account nonce')
|
||||
}
|
||||
console.log('Account nonce is OK')
|
||||
logger.info('Account nonce is OK')
|
||||
return !cancelled
|
||||
}
|
||||
|
||||
function getAccount (address) {
|
||||
console.log(`Getting account ${address} data`)
|
||||
logger.info(`Getting account ${address} data`)
|
||||
return httpClient
|
||||
.get(`/api/v1/account/${address}`)
|
||||
.then(res => res.data)
|
||||
.catch(() => {
|
||||
console.log('Retrying')
|
||||
logger.debug('Retrying')
|
||||
return getAccount(address)
|
||||
})
|
||||
}
|
||||
|
@ -205,25 +242,10 @@ function sendTx (tx) {
|
|||
})
|
||||
.catch(err => {
|
||||
if (err.response.data.message.includes('Tx already exists in cache'))
|
||||
console.log('Tx already exists in cache')
|
||||
logger.debug('Tx already exists in cache')
|
||||
else {
|
||||
console.log(err.response)
|
||||
console.log('Something failed, restarting')
|
||||
logger.info('Something failed, restarting: %o', err.response)
|
||||
return new Promise(resolve => setTimeout(() => resolve(sendTx(tx)), 1000))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -2,52 +2,68 @@ const TransactionBnc = require('@binance-chain/javascript-sdk/lib/tx').default
|
|||
const { crypto } = require('@binance-chain/javascript-sdk')
|
||||
const BN = require('bignumber.js')
|
||||
|
||||
const logger = require('./logger')
|
||||
const { padZeros } = require('./crypto')
|
||||
|
||||
const { FOREIGN_CHAIN_ID } = process.env
|
||||
|
||||
const BNB_ASSET = 'BNB'
|
||||
|
||||
class Transaction {
|
||||
constructor (options) {
|
||||
const { from, accountNumber, sequence, to, tokens, asset, bnbs, memo = '' } = options
|
||||
const accCode = crypto.decodeAddress(from)
|
||||
const toAccCode = crypto.decodeAddress(to)
|
||||
const { from, accountNumber, sequence, recipients, asset, memo = '' } = options
|
||||
|
||||
const coins = []
|
||||
|
||||
if (tokens && tokens !== '0' && asset) {
|
||||
coins.push({
|
||||
const totalTokens = recipients.reduce((sum, { tokens }) => sum.plus(new BN(tokens || 0)), new BN(0))
|
||||
const totalBnbs = recipients.reduce((sum, { bnbs }) => sum.plus(new BN(bnbs || 0)), new BN(0))
|
||||
const senderCoins = []
|
||||
if (asset && totalTokens.isGreaterThan(0)) {
|
||||
senderCoins.push({
|
||||
denom: asset,
|
||||
amount: new BN(tokens).multipliedBy(10 ** 8).toNumber(),
|
||||
amount: totalTokens.multipliedBy(10 ** 8).toNumber(),
|
||||
})
|
||||
}
|
||||
if (bnbs && bnbs !== '0') {
|
||||
coins.push({
|
||||
denom: 'BNB',
|
||||
amount: new BN(bnbs).multipliedBy(10 ** 8).toNumber(),
|
||||
if (totalBnbs.isGreaterThan(0)) {
|
||||
senderCoins.push({
|
||||
denom: BNB_ASSET,
|
||||
amount: totalBnbs.multipliedBy(10 ** 8).toNumber(),
|
||||
})
|
||||
}
|
||||
senderCoins.sort((a, b) => a.denom > b.denom)
|
||||
|
||||
coins.sort((a, b) => a.denom > b.denom)
|
||||
const inputs = [ {
|
||||
address: from,
|
||||
coins: senderCoins
|
||||
} ]
|
||||
const outputs = recipients.map(({ to, tokens, bnbs }) => {
|
||||
const receiverCoins = []
|
||||
if (asset && tokens) {
|
||||
receiverCoins.push({
|
||||
denom: asset,
|
||||
amount: new BN(tokens).multipliedBy(10 ** 8).toNumber(),
|
||||
})
|
||||
}
|
||||
if (bnbs) {
|
||||
receiverCoins.push({
|
||||
denom: BNB_ASSET,
|
||||
amount: new BN(bnbs).multipliedBy(10 ** 8).toNumber(),
|
||||
})
|
||||
}
|
||||
receiverCoins.sort((a, b) => a.denom > b.denom)
|
||||
return {
|
||||
address: to,
|
||||
coins: receiverCoins
|
||||
}
|
||||
})
|
||||
|
||||
const msg = {
|
||||
inputs: [{
|
||||
address: accCode,
|
||||
coins
|
||||
}],
|
||||
outputs: [{
|
||||
address: toAccCode,
|
||||
coins
|
||||
}],
|
||||
inputs: inputs.map((x) => ({...x, address: crypto.decodeAddress(x.address)})),
|
||||
outputs: outputs.map((x) => ({...x, address: crypto.decodeAddress(x.address)})),
|
||||
msgType: 'MsgSend'
|
||||
}
|
||||
|
||||
this.signMsg = {
|
||||
inputs: [{
|
||||
address: from,
|
||||
coins
|
||||
}],
|
||||
outputs: [{
|
||||
address: to,
|
||||
coins
|
||||
}]
|
||||
inputs,
|
||||
outputs
|
||||
}
|
||||
|
||||
this.tx = new TransactionBnc({
|
||||
|
@ -69,24 +85,18 @@ class Transaction {
|
|||
const n = new BN('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141', 16)
|
||||
const s = new BN(signature.s, 16)
|
||||
if (s.gt(n.div(2))) {
|
||||
console.log('Normalizing s')
|
||||
logger.debug('Normalizing s')
|
||||
signature.s = n.minus(s).toString(16)
|
||||
}
|
||||
const publicKeyEncoded = Buffer.from('eb5ae98721' + (yLast % 2 ? '03' : '02') + padZeros(publicKey.x, 64), 'hex')
|
||||
this.tx.signatures = [{
|
||||
this.tx.signatures = [ {
|
||||
pub_key: publicKeyEncoded,
|
||||
signature: Buffer.from(padZeros(signature.r, 64) + padZeros(signature.s, 64), 'hex'),
|
||||
account_number: this.tx.account_number,
|
||||
sequence: this.tx.sequence,
|
||||
}]
|
||||
} ]
|
||||
return this.tx.serialize()
|
||||
}
|
||||
}
|
||||
|
||||
function padZeros (s, len) {
|
||||
while (s.length < len)
|
||||
s = '0' + s
|
||||
return s
|
||||
}
|
||||
|
||||
module.exports = Transaction
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
FOREIGN_PRIVATE_KEY=0000000000000000000000000000000000000000000000000000000000000000
|
|
@ -0,0 +1,3 @@
|
|||
HOME_PRIVATE_KEY=0000000000000000000000000000000000000000000000000000000000000000
|
||||
SIDE_PRIVATE_KEY=1111111111111111111111111111111111111111111111111111111111111111
|
||||
FOREIGN_PRIVATE_KEY=2222222222222222222222222222222222222222222222222222222222222222
|
|
@ -0,0 +1,2 @@
|
|||
FOREIGN_URL=https://testnet-dex.binance.org/
|
||||
FOREIGN_ASSET=KFT-94F
|
|
@ -4,6 +4,9 @@ set -e
|
|||
|
||||
cd $(dirname "$0")
|
||||
|
||||
# either development or staging
|
||||
TARGET_NETWORK=${TARGET_NETWORK:=development}
|
||||
|
||||
docker build -t binance-balance . > /dev/null
|
||||
|
||||
docker run --rm --env-file .env binance-balance $@
|
||||
docker run --rm --env-file ".env.$TARGET_NETWORK" binance-balance $@
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
FOREIGN_URL=https://testnet-dex.binance.org/
|
||||
FOREIGN_ASSET=KFT-94F
|
||||
|
||||
FOREIGN_PRIVATE_KEY=b92a59209e28149e5cee8e54dfceb80a08ea08e654261bdb9d264b15dee2525c
|
|
@ -0,0 +1,4 @@
|
|||
FOREIGN_URL=https://testnet-dex.binance.org/
|
||||
FOREIGN_ASSET=KFT-94F
|
||||
|
||||
#FOREIGN_PRIVATE_KEY is taken from src/test-services/.keys.development
|
|
@ -0,0 +1,4 @@
|
|||
FOREIGN_URL=https://testnet-dex.binance.org/
|
||||
FOREIGN_ASSET=KFT-94F
|
||||
|
||||
#FOREIGN_PRIVATE_KEY is taken from src/test-services/.keys.staging
|
|
@ -4,6 +4,9 @@ set -e
|
|||
|
||||
cd $(dirname "$0")
|
||||
|
||||
# either development or staging
|
||||
TARGET_NETWORK=${TARGET_NETWORK:=development}
|
||||
|
||||
docker build -t binance-send . > /dev/null
|
||||
|
||||
docker run --rm --env-file .env binance-send $@
|
||||
docker run --rm --env-file ".env.$TARGET_NETWORK" --env-file "../.keys.$TARGET_NETWORK" -e "PRIVATE_KEY=$PRIVATE_KEY" binance-send $@
|
||||
|
|
|
@ -2,11 +2,13 @@ const Bnc = require('@binance-chain/javascript-sdk')
|
|||
|
||||
const { FOREIGN_URL, FOREIGN_ASSET, FOREIGN_PRIVATE_KEY } = process.env
|
||||
|
||||
const PRIVATE_KEY = process.env.PRIVATE_KEY || FOREIGN_PRIVATE_KEY
|
||||
|
||||
const client = new Bnc(FOREIGN_URL)
|
||||
|
||||
async function main () {
|
||||
client.chooseNetwork('testnet')
|
||||
await client.setPrivateKey(FOREIGN_PRIVATE_KEY)
|
||||
await client.setPrivateKey(PRIVATE_KEY)
|
||||
|
||||
await client.initChain()
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
HOME_RPC_URL=https://kovan.infura.io/v3/5d7bd94c50ed43fab1cb8e74f58678b0
|
||||
HOME_TOKEN_ADDRESS=0x57d2533B640cfb58f8f1F69C14c089968Da9fdFc
|
|
@ -2,6 +2,7 @@
|
|||
"name": "ethereum-balance",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"web3": "1.0.0-beta.55"
|
||||
"web3": "1.0.0-beta.55",
|
||||
"bignumber.js": "9.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@ set -e
|
|||
|
||||
cd $(dirname "$0")
|
||||
|
||||
docker build -t ethreum-balance . > /dev/null
|
||||
# either development or staging
|
||||
TARGET_NETWORK=${TARGET_NETWORK:=development}
|
||||
|
||||
docker run --network blockchain_home --rm --env-file .env ethreum-balance $@
|
||||
docker build -t ethereum-balance . > /dev/null
|
||||
|
||||
docker run --network blockchain_home --rm --env-file ".env.$TARGET_NETWORK" ethereum-balance $@
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const Web3 = require('web3')
|
||||
const BN = require('bignumber.js')
|
||||
|
||||
const { HOME_RPC_URL, HOME_TOKEN_ADDRESS } = process.env
|
||||
|
||||
|
@ -9,6 +10,9 @@ const token = new web3.eth.Contract(abiToken, HOME_TOKEN_ADDRESS)
|
|||
|
||||
const address = process.argv[2]
|
||||
|
||||
web3.eth.getBalance(address).then(x => console.log(`${x.toString()} wei`))
|
||||
|
||||
token.methods.balanceOf(address).call()
|
||||
.then(x => console.log(x.toString()))
|
||||
.catch(() => console.log(0))
|
||||
.then(x => parseFloat(new BN(x).dividedBy(10 ** 18).toFixed(8, 3)))
|
||||
.then(x => console.log(`${x.toString()} tokens`))
|
||||
.catch(() => console.log('0 tokens'))
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
HOME_RPC_URL=http://ganache_home:8545
|
||||
HOME_BRIDGE_ADDRESS=0x44c158FE850821ae69DaF37AADF5c539e9d0025B
|
||||
|
||||
HOME_CHAIN_ID=44
|
||||
HOME_TOKEN_ADDRESS=0xd5fE0D28e058D375b0b038fFbB446Da37E85fFdc
|
||||
|
||||
HOME_PRIVATE_KEY=e2aeb24eaa63102d0c0821717c3b6384abdabd7af2ad4ec8e650dce300798b27
|
|
@ -0,0 +1,5 @@
|
|||
HOME_RPC_URL=https://kovan.infura.io/v3/5d7bd94c50ed43fab1cb8e74f58678b0
|
||||
HOME_BRIDGE_ADDRESS=0x6ADCa5e691341fb9de8927d15c0a89B83A4E665e
|
||||
HOME_TOKEN_ADDRESS=0x57d2533B640cfb58f8f1F69C14c089968Da9fdFc
|
||||
|
||||
#HOME_PRIVATE_KEY is taken from src/test-services/.keys.staging
|
File diff suppressed because one or more lines are too long
|
@ -6,6 +6,6 @@ COPY package.json /test/
|
|||
|
||||
RUN npm install
|
||||
|
||||
COPY testEthereumSend.js IERC20.json /test/
|
||||
COPY testEthereumSend.js IERC20.json Bridge.json /test/
|
||||
|
||||
ENTRYPOINT ["node", "testEthereumSend.js"]
|
||||
|
|
|
@ -4,6 +4,9 @@ set -e
|
|||
|
||||
cd $(dirname "$0")
|
||||
|
||||
docker build -t ethreum-send . > /dev/null
|
||||
# either development or staging
|
||||
TARGET_NETWORK=${TARGET_NETWORK:=development}
|
||||
|
||||
docker run --network blockchain_home --rm --env-file .env ethreum-send $@
|
||||
docker build -t ethereum-send . > /dev/null
|
||||
|
||||
docker run --network blockchain_home --rm --env-file ".env.$TARGET_NETWORK" --env-file "../.keys.$TARGET_NETWORK" -e "PRIVATE_KEY=$PRIVATE_KEY" ethereum-send $@
|
||||
|
|
|
@ -1,61 +1,99 @@
|
|||
const Web3 = require('web3')
|
||||
const BN = require('bignumber.js')
|
||||
|
||||
const { HOME_RPC_URL, HOME_BRIDGE_ADDRESS, HOME_CHAIN_ID, HOME_PRIVATE_KEY, HOME_TOKEN_ADDRESS } = process.env
|
||||
const { HOME_RPC_URL, HOME_BRIDGE_ADDRESS, HOME_PRIVATE_KEY, HOME_TOKEN_ADDRESS } = process.env
|
||||
|
||||
const abiToken = require('./IERC20').abi
|
||||
const abiBridge = require('./Bridge').abi
|
||||
|
||||
const PRIVATE_KEY = process.env.PRIVATE_KEY || HOME_PRIVATE_KEY
|
||||
|
||||
const web3 = new Web3(HOME_RPC_URL, null, { transactionConfirmationBlocks: 1 })
|
||||
const token = new web3.eth.Contract(abiToken, HOME_TOKEN_ADDRESS)
|
||||
const bridge = new web3.eth.Contract(abiBridge, HOME_BRIDGE_ADDRESS)
|
||||
|
||||
const sender = web3.eth.accounts.privateKeyToAccount(`0x${HOME_PRIVATE_KEY}`).address
|
||||
const sender = web3.eth.accounts.privateKeyToAccount(`0x${PRIVATE_KEY}`).address
|
||||
|
||||
async function main () {
|
||||
const HOME_CHAIN_ID = await web3.eth.net.getId()
|
||||
const blockGasLimit = (await web3.eth.getBlock("latest", false)).gasLimit
|
||||
|
||||
let to = process.argv[2]
|
||||
|
||||
if (to === "bridge") {
|
||||
to = HOME_BRIDGE_ADDRESS
|
||||
}
|
||||
const to = process.argv[2]
|
||||
|
||||
const amount = parseInt(process.argv[3])
|
||||
let coins = process.argv[4]
|
||||
|
||||
if (amount !== 0) {
|
||||
console.log(`Transfer from ${sender} to ${to}, ${amount} tokens`)
|
||||
const txCount = await web3.eth.getTransactionCount(sender)
|
||||
|
||||
const query = token.methods.transfer(to, '0x'+(new BN(amount).toString(16)))
|
||||
const encodedABI = query.encodeABI()
|
||||
const tx = {
|
||||
data: encodedABI,
|
||||
if (to === "bridge" && amount !== 0) {
|
||||
console.log(`Transfer from ${sender} to ${HOME_BRIDGE_ADDRESS}, ${amount} tokens`)
|
||||
|
||||
const queryApprove = token.methods.approve(HOME_BRIDGE_ADDRESS, '0x'+(new BN(amount).multipliedBy(10 ** 18).toString(16)))
|
||||
const txApprove = {
|
||||
data: queryApprove.encodeABI(),
|
||||
from: sender,
|
||||
to: HOME_TOKEN_ADDRESS,
|
||||
nonce: await web3.eth.getTransactionCount(sender),
|
||||
chainId: parseInt(HOME_CHAIN_ID)
|
||||
nonce: txCount,
|
||||
chainId: HOME_CHAIN_ID
|
||||
}
|
||||
tx.gas = Math.min(Math.ceil(await query.estimateGas(tx) * 1.5), 6721975)
|
||||
let signedTx = await web3.eth.accounts.signTransaction(tx, HOME_PRIVATE_KEY)
|
||||
txApprove.gas = Math.min(Math.ceil(await queryApprove.estimateGas({
|
||||
from: sender
|
||||
}) * 1.5), blockGasLimit)
|
||||
const signedTxApprove = await web3.eth.accounts.signTransaction(txApprove, PRIVATE_KEY)
|
||||
|
||||
let receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction)
|
||||
console.log('txHash: ' + receipt.transactionHash)
|
||||
const receiptApprove = await web3.eth.sendSignedTransaction(signedTxApprove.rawTransaction)
|
||||
console.log('txHash approve: ' + receiptApprove.transactionHash)
|
||||
|
||||
const queryExchange = bridge.methods.exchange('0x'+(new BN(amount).multipliedBy(10 ** 18).toString(16)))
|
||||
const txExchange = {
|
||||
data: queryExchange.encodeABI(),
|
||||
from: sender,
|
||||
to: HOME_BRIDGE_ADDRESS,
|
||||
nonce: txCount + 1,
|
||||
chainId: HOME_CHAIN_ID
|
||||
}
|
||||
txExchange.gas = Math.min(Math.ceil(await queryExchange.estimateGas({
|
||||
from: sender
|
||||
}) * 1.5), blockGasLimit)
|
||||
const signedTxExchange = await web3.eth.accounts.signTransaction(txExchange, PRIVATE_KEY)
|
||||
|
||||
const receiptExchange = await web3.eth.sendSignedTransaction(signedTxExchange.rawTransaction)
|
||||
console.log('txHash exchange: ' + receiptExchange.transactionHash)
|
||||
} else if (amount !== 0) {
|
||||
console.log(`Transfer from ${sender} to ${to}, ${amount} tokens`)
|
||||
|
||||
const query = token.methods.transfer(to, '0x'+(new BN(amount).multipliedBy(10 ** 18).toString(16)))
|
||||
const tx = {
|
||||
data: query.encodeABI(),
|
||||
from: sender,
|
||||
to: HOME_TOKEN_ADDRESS,
|
||||
nonce: txCount,
|
||||
chainId: HOME_CHAIN_ID
|
||||
}
|
||||
tx.gas = Math.min(Math.ceil(await query.estimateGas({
|
||||
from: sender
|
||||
}) * 1.5), blockGasLimit)
|
||||
const signedTx = await web3.eth.accounts.signTransaction(tx, PRIVATE_KEY)
|
||||
const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction)
|
||||
console.log('txHash transfer: ' + receipt.transactionHash)
|
||||
}
|
||||
|
||||
if (coins) {
|
||||
coins = parseFloat(coins)
|
||||
console.log(`Transfer from ${sender} to ${to}, ${coins} coins`)
|
||||
|
||||
const tx_coins = {
|
||||
const tx = {
|
||||
data: '0x',
|
||||
from: sender,
|
||||
to: to,
|
||||
nonce: await web3.eth.getTransactionCount(sender),
|
||||
chainId: parseInt(HOME_CHAIN_ID),
|
||||
chainId: HOME_CHAIN_ID,
|
||||
value: web3.utils.toWei(new BN(coins).toString(), 'ether'),
|
||||
gas: 21000
|
||||
}
|
||||
signedTx = await web3.eth.accounts.signTransaction(tx_coins, HOME_PRIVATE_KEY)
|
||||
const signedTx = await web3.eth.accounts.signTransaction(tx, PRIVATE_KEY)
|
||||
|
||||
receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction)
|
||||
const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction)
|
||||
console.log('txHash: ' + receipt.transactionHash)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
FROM node:10.16.0-alpine
|
||||
|
||||
WORKDIR /test
|
||||
|
||||
COPY package.json /test/
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY testGetAddresses.js /test/
|
||||
|
||||
ENTRYPOINT ["node", "testGetAddresses.js"]
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "get-addresses",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"ethers": "4.0.37",
|
||||
"bech32": "1.1.3"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
cd $(dirname "$0")
|
||||
|
||||
docker build -t get-addresses . > /dev/null
|
||||
|
||||
docker run --rm get-addresses $@
|
|
@ -0,0 +1,25 @@
|
|||
const { utils } = require('ethers')
|
||||
const bech32 = require('bech32')
|
||||
const crypto = require('crypto')
|
||||
|
||||
const privateKey = process.argv[2].startsWith('0x') ? process.argv[2] : '0x' + process.argv[2]
|
||||
|
||||
const ethAddress = utils.computeAddress(privateKey)
|
||||
const publicKey = utils.computePublicKey(privateKey, true)
|
||||
|
||||
console.log(`Eth address: ${ethAddress}\nBnc address: ${publicKeyToAddress(publicKey)}`)
|
||||
|
||||
function publicKeyToAddress (publicKey) {
|
||||
const sha256Hash = sha256(Buffer.from(publicKey.substr(2), 'hex'))
|
||||
const hash = ripemd160(Buffer.from(sha256Hash, 'hex'))
|
||||
const words = bech32.toWords(Buffer.from(hash, 'hex'))
|
||||
return bech32.encode('tbnb', words)
|
||||
}
|
||||
|
||||
function sha256 (bytes) {
|
||||
return crypto.createHash('sha256').update(bytes).digest('hex')
|
||||
}
|
||||
|
||||
function ripemd160 (bytes) {
|
||||
return crypto.createHash('ripemd160').update(bytes).digest('hex')
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
SIDE_RPC_URL=http://ganache_side:8545
|
||||
|
||||
SIDE_PRIVATE_KEY=e2aeb24eaa63102d0c0821717c3b6384abdabd7af2ad4ec8e650dce300798b27
|
|
@ -0,0 +1,3 @@
|
|||
SIDE_RPC_URL=https://sokol.poa.network
|
||||
|
||||
#SIDE_PRIVATE_KEY is taken from src/test-services/.keys.staging
|
|
@ -0,0 +1,11 @@
|
|||
FROM node:10.16.0-alpine
|
||||
|
||||
WORKDIR /test
|
||||
|
||||
COPY package.json /test/
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY testEthereumSend.js /test/
|
||||
|
||||
ENTRYPOINT ["node", "testEthereumSend.js"]
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "side-prefund",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"bignumber.js": "9.0.0",
|
||||
"web3": "1.0.0-beta.55"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
cd $(dirname "$0")
|
||||
|
||||
# either development or staging
|
||||
TARGET_NETWORK=${TARGET_NETWORK:=development}
|
||||
|
||||
echo "Using $TARGET_NETWORK network"
|
||||
|
||||
docker build -t side-prefund . > /dev/null
|
||||
|
||||
docker run --network blockchain_side --rm --env-file ".env.$TARGET_NETWORK" --env-file "../.keys.$TARGET_NETWORK" side-prefund $@
|
|
@ -0,0 +1,34 @@
|
|||
const Web3 = require('web3')
|
||||
const BN = require('bignumber.js')
|
||||
|
||||
const { SIDE_RPC_URL, SIDE_PRIVATE_KEY } = process.env
|
||||
|
||||
const web3 = new Web3(SIDE_RPC_URL, null, { transactionConfirmationBlocks: 1 })
|
||||
|
||||
const sender = web3.eth.accounts.privateKeyToAccount(`0x${SIDE_PRIVATE_KEY}`).address
|
||||
|
||||
async function main () {
|
||||
const SIDE_CHAIN_ID = await web3.eth.net.getId()
|
||||
|
||||
const to = process.argv[2]
|
||||
const amount = parseFloat(process.argv[3])
|
||||
|
||||
console.log(`Transfer from ${sender} to ${to}, ${amount} eth`)
|
||||
|
||||
const tx_coins = {
|
||||
data: '0x',
|
||||
from: sender,
|
||||
to: to,
|
||||
nonce: await web3.eth.getTransactionCount(sender),
|
||||
chainId: SIDE_CHAIN_ID,
|
||||
value: web3.utils.toWei(new BN(amount).toString(), 'ether'),
|
||||
gas: 21000
|
||||
}
|
||||
const signedTx = await web3.eth.accounts.signTransaction(tx_coins, SIDE_PRIVATE_KEY)
|
||||
|
||||
const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction)
|
||||
console.log('txHash: ' + receipt.transactionHash)
|
||||
|
||||
}
|
||||
|
||||
main()
|
Loading…
Reference in New Issue