Simple blockchain rpc proxy implementation instead of centralized server

This commit is contained in:
Kirill Fedoseev 2019-06-20 16:42:01 +07:00
commit 7defc1e814
37 changed files with 8840 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
.idea/
node_modules/
.DS_Store
**/keys*.store
**/signature
**/params
src/tss/multi-party-ecdsa/

3340
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

9
package.json Normal file
View File

@ -0,0 +1,9 @@
{
"name": "bridge",
"version": "0.0.1",
"dependencies": {
"dotenv": "8.0.0",
"truffle-hdwallet-provider": "1.0.10",
"web3": "1.0.0-beta.55"
}
}

0
src/README.md Normal file
View File

8
src/deploy/.env Normal file
View File

@ -0,0 +1,8 @@
RPC_URL=https://sokol.poa.network
RPC_URL_DEV=http://127.0.0.1:7545
PRIVATE_KEY=e49fe947f224ae8e126c41b1be3e52be701509c2366e835ea8c08651f91030e0
PRIVATE_KEY_DEV=dc8f77f02a4aba01c3ff0aa22ff13b7c9fd47da936db8d7f0a69abd3198a8c7e
THRESHOLD=2
PARTIES=6

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,24 @@
pragma solidity ^0.5.9;
contract Migrations {
address public owner;
uint public last_completed_migration;
modifier restricted() {
if (msg.sender == owner) _;
}
constructor() public {
owner = msg.sender;
}
function setCompleted(uint completed) restricted public {
last_completed_migration = completed;
}
function upgrade(address new_address) restricted public {
Migrations upgraded = Migrations(new_address);
upgraded.setCompleted(last_completed_migration);
}
}

View File

@ -0,0 +1,47 @@
pragma solidity ^0.5.0;
contract SharedDB {
uint32 threshold;
uint32 parties;
uint32 signupKeygenID;
uint32 signupKeygenCurrent;
uint32 signupSignID;
uint32 signupSignCurrent;
mapping(bytes32 => string) public db;
event SignupKeygen(address indexed from, uint32 uuid, uint32 number);
event SignupSign(address indexed from, uint32 uuid, uint32 number);
constructor(uint32 _threshold, uint32 _parties) public {
threshold = _threshold;
parties = _parties;
signupKeygenID = 1;
signupSignID = 0x80000000;
}
function set(bytes32 key, string memory value) public {
db[key] = value;
}
function signupKeygen() public {
if (signupKeygenCurrent < parties) {
signupKeygenCurrent++;
}
else {
signupKeygenID++;
signupKeygenCurrent = 1;
}
emit SignupKeygen(msg.sender, signupKeygenID, signupKeygenCurrent);
}
function signupSign() public {
if (signupSignCurrent < threshold + 1) {
signupSignCurrent++;
}
else {
signupSignID++;
signupSignCurrent = 1;
}
emit SignupSign(msg.sender, signupSignID, signupSignCurrent);
}
}

View File

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

View File

@ -0,0 +1,9 @@
require('dotenv').config()
const Migrations = artifacts.require('SharedDB')
const { THRESHOLD, PARTIES } = process.env
module.exports = deployer => {
deployer.deploy(Migrations, THRESHOLD, PARTIES)
}

View File

@ -0,0 +1,29 @@
require('dotenv').config()
const PrivateKeyProvider = require('truffle-hdwallet-provider')
const { RPC_URL, PRIVATE_KEY, RPC_URL_DEV, PRIVATE_KEY_DEV } = process.env
module.exports = {
networks: {
development: {
provider: new PrivateKeyProvider(PRIVATE_KEY_DEV, RPC_URL_DEV),
network_id: '33'
},
staging: {
provider: new PrivateKeyProvider(PRIVATE_KEY, RPC_URL),
network_id: '77'
}
},
compilers: {
solc: {
version: '0.5.9',
settings: {
optimizer: {
enabled: true,
runs: 3
}
}
}
}
}

9
src/index.js Normal file
View File

@ -0,0 +1,9 @@
const Web3 = require('web3')
const web3 = new Web3('https://sokol.poa.network')
async function main () {
console.log(await web3.eth.getBalance('0x48138BEC745673Fe2CE28C62c9944Ab0Fa56b495'))
}
main()

14
src/oracle/.env Normal file
View File

@ -0,0 +1,14 @@
THRESHOLD=2
PARTIES=6
KEY_FILE=keys.store
#RPC_URL=https://sokol.poa.network
RPC_URL=http://host.docker.internal:7545
SHARED_DB_ADDRESS=0x213b9cF674ef08E1E30A55f679Dfa55a16494975
#VALIDATOR_PRIVATE_KEY=d1e7b8ff274e517e1a332f2bc0ac051e30db196ba31c68c2efcd022e8ec358f1
VALIDATOR_PRIVATE_KEY=72c16a6812258f999190ccb11ff8bfc1e9f07438c8411f8840dbbe18137785e2
#SSM_URL=http://127.0.0.1:8001

View File

@ -0,0 +1,135 @@
version: '3.7'
services:
proxy1:
image: blockchain-proxy
build: ./proxy
environment:
- RPC_URL
- SHARED_DB_ADDRESS
- VALIDATOR_PRIVATE_KEY=e2c1349f1d13f22cd011a9cfef7007388b8df16a6e1f318517cb93dbadcd9c2e
volumes:
- '../deploy/build/contracts:/proxy/contracts_data'
networks:
- keygen-proxy-net1
proxy2:
image: blockchain-proxy
build: ./proxy
environment:
- RPC_URL
- SHARED_DB_ADDRESS
- VALIDATOR_PRIVATE_KEY=7562a3ea655c2aad92a819069d334e7cf6edc9d2ae0fcc9a6498bd38842fafd4
volumes:
- '../deploy/build/contracts:/proxy/contracts_data'
networks:
- keygen-proxy-net2
proxy3:
image: blockchain-proxy
build: ./proxy
environment:
- RPC_URL
- SHARED_DB_ADDRESS
- VALIDATOR_PRIVATE_KEY=955b1f58a207160459e3be344e8a9cae2c3db47a0cb8275eb771a51da84de244
volumes:
- '../deploy/build/contracts:/proxy/contracts_data'
networks:
- keygen-proxy-net3
proxy4:
image: blockchain-proxy
build: ./proxy
environment:
- RPC_URL
- SHARED_DB_ADDRESS
- VALIDATOR_PRIVATE_KEY=a1c08300192fdd27aa92cd836ca98601c35184319c6873b1d1478f21cdb1c867
volumes:
- '../deploy/build/contracts:/proxy/contracts_data'
networks:
- keygen-proxy-net4
proxy5:
image: blockchain-proxy
build: ./proxy
environment:
- RPC_URL
- SHARED_DB_ADDRESS
- VALIDATOR_PRIVATE_KEY=3aef900c61246653308ed0770f3bd67a1c21342cf2a7dbf0ee5bcc787b627de0
volumes:
- '../deploy/build/contracts:/proxy/contracts_data'
networks:
- keygen-proxy-net5
proxy6:
image: blockchain-proxy
build: ./proxy
environment:
- RPC_URL
- SHARED_DB_ADDRESS
- VALIDATOR_PRIVATE_KEY=abe7f999209549463e7da719eddd6bb3e00fe77832f5166c1e57d067452954b6
volumes:
- '../deploy/build/contracts:/proxy/contracts_data'
networks:
- keygen-proxy-net6
keygen1:
image: keygen-client
build: ./tss-keygen
command: 'http://proxy1:8001'
volumes:
- './keys1.store:/tss/keys.store'
- './params:/tss/params'
networks:
- keygen-proxy-net1
keygen2:
image: keygen-client
build: ./tss-keygen
command: 'http://proxy2:8001'
volumes:
- './keys2.store:/tss/keys.store'
- './params:/tss/params'
networks:
- keygen-proxy-net2
keygen3:
image: keygen-client
build: ./tss-keygen
command: 'http://proxy3:8001'
volumes:
- './keys3.store:/tss/keys.store'
- './params:/tss/params'
networks:
- keygen-proxy-net3
keygen4:
image: keygen-client
build: ./tss-keygen
command: 'http://proxy4:8001'
environment:
- SKIP_SIGN=true
volumes:
- './keys4.store:/tss/keys.store'
- './params:/tss/params'
networks:
- keygen-proxy-net4
keygen5:
image: keygen-client
build: ./tss-keygen
command: 'http://proxy5:8001'
environment:
- SKIP_SIGN=true
volumes:
- './keys5.store:/tss/keys.store'
- './params:/tss/params'
networks:
- keygen-proxy-net5
keygen6:
image: keygen-client
build: ./tss-keygen
command: 'http://proxy6:8001'
environment:
- SKIP_SIGN=true
volumes:
- './keys6.store:/tss/keys.store'
- './params:/tss/params'
networks:
- keygen-proxy-net6
networks:
keygen-proxy-net1:
keygen-proxy-net2:
keygen-proxy-net3:
keygen-proxy-net4:
keygen-proxy-net5:
keygen-proxy-net6:

View File

@ -0,0 +1,24 @@
version: '3.7'
services:
proxy:
image: blockchain-proxy
build: ./proxy
environment:
- RPC_URL
- SHARED_DB_ADDRESS
- VALIDATOR_PRIVATE_KEY
volumes:
- '../deploy/build/contracts:/proxy/contracts_data'
networks:
- keygen-proxy-net
keygen:
image: keygen-client
build: ./tss-keygen
command: 'http://proxy:8001'
volumes:
- './${KEY_FILE}:/tss/keys.store'
- './params:/tss/params'
networks:
- keygen-proxy-net
networks:
keygen-proxy-net:

View File

@ -0,0 +1,7 @@
version: '3.7'
services:
sign-client:
image: sign-client
build:
context: .
dockerfile:

32
src/oracle/keygen-all.sh Executable file
View File

@ -0,0 +1,32 @@
#!/bin/bash
set -e
cd $(dirname "$0")
set -o allexport
source ./.env
set +o allexport
echo Writing params
echo \{\"parties\":\""$PARTIES"\",\"threshold\":\""$THRESHOLD"\"\} > ./params
if [[ -z "$LOCAL" ]]; then
echo Building tss source from git
docker build -t tss ../tss > /dev/null
else
echo Building tss local source
docker build -t tss -f ../tss/Dockerfile-local ../tss > /dev/null
fi
echo Building tss keygen client
docker build -t tss-keygen-client ./tss-keygen > /dev/null
for (( i = 1 ; i <= $PARTIES ; i++ )) do
touch keys"$i".store
done
docker-compose -f ./docker-compose-keygen-all.yml kill $(eval echo proxy{1..${PARTIES}})
echo Running keygen
docker-compose -f ./docker-compose-keygen-all.yml up $@

30
src/oracle/keygen.sh Executable file
View File

@ -0,0 +1,30 @@
#!/bin/bash
set -e
cd $(dirname "$0")
set -o allexport
source ./.env
set +o allexport
echo Writing params
echo \{\"parties\":\""$PARTIES"\",\"threshold\":\""$THRESHOLD"\"\} > ./params
if [[ -z "$LOCAL" ]]; then
echo Building tss source from git
docker build -t tss ../tss > /dev/null
else
echo Building tss local source
docker build -t tss -f ../tss/Dockerfile-local ../tss > /dev/null
fi
echo Building tss keygen client
docker build -t tss-keygen-client ./tss-keygen > /dev/null
touch "$KEY_FILE"
docker-compose -f ./docker-compose-keygen.yml kill proxy
echo Running keygen
docker-compose -f ./docker-compose-keygen.yml up $@

View File

@ -0,0 +1,11 @@
FROM node:10.16.0-alpine
WORKDIR /proxy
COPY package.json /proxy/
RUN npm install
COPY index.js /proxy/
ENTRYPOINT ["node", "index.js"]

119
src/oracle/proxy/index.js Normal file
View File

@ -0,0 +1,119 @@
const express = require('express')
const Web3 = require('web3')
const { RPC_URL, SHARED_DB_ADDRESS, VALIDATOR_PRIVATE_KEY } = process.env
const abi = require('./contracts_data/SharedDB.json').abi
const web3 = new Web3(RPC_URL, null, { transactionConfirmationBlocks: 1 })
const contract = new web3.eth.Contract(abi, SHARED_DB_ADDRESS)
const validatorAddress = web3.eth.accounts.privateKeyToAccount(`0x${VALIDATOR_PRIVATE_KEY}`).address
let validatorNonce
const app = express()
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
app.post('/get', get)
app.post('/set', set)
app.post('/signupkeygen', signupKeygen)
app.post('/signupsign', signupSign)
async function main () {
validatorNonce = await web3.eth.getTransactionCount(validatorAddress)
app.listen(8001, () => {
console.log('Listening on port 8001')
})
}
function Ok (data) {
return { Ok: data }
}
function hash (key) {
return web3.utils.sha3(JSON.stringify(key))
}
async function get (req, res) {
console.log('Get call')
while (true) {
const result = await contract.methods.db(hash(req.body.key)).call()
if (result !== '') {
res.send(Ok({ key: req.body.key, value: result }))
break
}
}
console.log('Get end')
}
async function set (req, res) {
console.log('Set call')
const query = contract.methods.set(hash(req.body.key), req.body.value)
await sendQuery(query)
res.send(Ok(null))
console.log('Set end')
}
async function signupKeygen (req, res) {
console.log('SignupKeygen call')
const query = contract.methods.signupKeygen()
const receipt = await sendQuery(query)
while (true) {
const events = await contract.getPastEvents('SignupKeygen', {
filter: { from: validatorAddress },
fromBlock: receipt.blockNumber,
toBlock: receipt.blockNumber
})
const event = events[0]
if (event) {
res.send(Ok({ uuid: event.returnValues.uuid.toString(), number: event.returnValues.number }))
break
}
}
console.log('SignupKeygen end')
}
async function signupSign (req, res) {
console.log('SignupSign call')
const query = contract.methods.signupSign()
const receipt = await sendQuery(query)
while (true) {
const events = await contract.getPastEvents('SignupSign', {
filter: { from: validatorAddress },
fromBlock: receipt.blockNumber,
toBlock: receipt.blockNumber
})
const event = events[0]
if (event) {
res.send(Ok({ uuid: event.returnValues.uuid.toString(), number: event.returnValues.number }))
break
}
}
console.log('SignupSign call')
}
async function sendQuery (query) {
const encodedABI = query.encodeABI()
const tx = {
data: encodedABI,
from: validatorAddress,
to: SHARED_DB_ADDRESS,
nonce: validatorNonce++,
chainId: 33
}
tx.gas = await query.estimateGas(tx)
const signedTx = await web3.eth.accounts.signTransaction(tx, VALIDATOR_PRIVATE_KEY)
const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction)
return receipt
}
main()

View File

@ -0,0 +1,9 @@
{
"name": "proxy",
"version": "0.0.1",
"dependencies": {
"web3": "1.0.0-beta.55",
"dotenv": "8.0.0",
"express": "4.17.1"
}
}

View File

@ -0,0 +1,12 @@
FROM ubuntu:19.10
WORKDIR /tss
RUN apt-get update && \
apt-get install -y libssl1.1 libssl-dev curl
COPY params /tss/
COPY --from=tss /tss/target/release/gg18_sign_client /tss/
ENTRYPOINT ["./gg18_sign_client"]

View File

@ -0,0 +1,13 @@
{
"redis": {
"port": 6379,
"host": "127.0.0.1",
"family": 4,
"password": "auth",
"db": 0
},
"eth": {
"rpcUrl": "https://sokol.poa.network",
"pollingInterval": 5000
}
}

View File

@ -0,0 +1,17 @@
import Redis from 'ioredis'
import { redis as redisConfig } from 'config'
console.log('Connecting to redis')
const redis = Redis(redisConfig)
redis.on('connect', () => {
console.log('Connected to redis')
})
redis.on('error', () => {
console.log('Redis error')
})
export const get = redis.get
export const set = redis.set

View File

@ -0,0 +1,22 @@
import Web3 from 'web3'
import { eth } from 'config'
import {get, set} from 'db'
const {rpcUrl, pollingInterval} = eth
const web3 = new Web3(rpcUrl)
async function main () {
const lastProcessedBlock = await get('lastProcessedBlock')
try {
const block = await web3.eth.getBlock(lastProcessedBlock + 1)
block.transactions.forEach(transaction => {
})
} catch (e) {
}
}
setInterval(main, pollingInterval)

32
src/oracle/tss-client/sign.sh Executable file
View File

@ -0,0 +1,32 @@
#!/bin/bash
set -e
cd $(dirname "$0")
if [[ -z "$SKIP_ENV" ]]; then
set -o allexport
source ../.env
set +o allexport
fi
echo Writing params
echo \{\"parties\":\""$PARTIES"\",\"threshold\":\""$THRESHOLD"\"\} > ./params
if [[ -z "$LOCAL" ]]; then
echo Building tss source from git
docker build -t tss ../../tss > /dev/null
else
echo Building tss local source
docker build -t tss -f ../../tss/Dockerfile-local ../../tss > /dev/null
fi
echo Building tss sign client
docker build -t tss-sign-client . > /dev/null
touch signature
echo Signing message using ssm server at "$SSM_URL"
docker run --rm -v "$(cd ..; pwd)/$KEY_FILE:/tss/keys.store" -v "$(pwd)/signature:/tss/signature" --network host tss-sign-client "$SSM_URL" keys.store "$1"
echo Signed message

View File

@ -0,0 +1,12 @@
FROM ubuntu:19.10
WORKDIR /tss
RUN apt-get update && \
apt-get install -y libssl1.1 libssl-dev curl
COPY keygen-entrypoint.sh /tss/
COPY --from=tss /tss/target/release/gg18_keygen_client /tss/target/release/gg18_sign_client /tss/
ENTRYPOINT ["./keygen-entrypoint.sh"]

View File

@ -0,0 +1,27 @@
#!/bin/bash
set -e
echo "Connecting to $1"
until curl "$1" > /dev/null 2>&1; do
sleep 1;
done
#curl "$1"
echo "Generating key using server $1"
./gg18_keygen_client "$1" keys.store
echo "Generated keys for all parties"
echo "Signing message"
if [[ -z "$SKIP_SIGN" ]]; then
./gg18_sign_client "$1" keys.store some_message
echo "Signed message"
fi

40
src/oracle/tss-keygen/keygen.sh Executable file
View File

@ -0,0 +1,40 @@
#!/bin/bash
set -e
cd $(dirname "$0")
set -o allexport
source ../.env
set +o allexport
echo Writing params
echo \{\"parties\":\""$PARTIES"\",\"threshold\":\""$THRESHOLD"\"\} > ./params
if [[ -z "$LOCAL" ]]; then
echo Building tss source from git
docker build -t tss ../../tss > /dev/null
else
echo Building tss local source
docker build -t tss -f ../../tss/Dockerfile-local ../../tss > /dev/null
fi
echo Building tss keygen client
docker build -t tss-keygen-client . > /dev/null
touch ../"$KEY_FILE"
echo Generating keys using ssm server at "$SSM_URL"
docker run --rm -v "$(cd ..; pwd)/$KEY_FILE:/tss/keys.store" --network host tss-keygen-client "$SSM_URL"
echo ==========================================================
echo All keys generated, ready to test sign
if [[ -z "$SKIP_SIGN" ]]; then
sleep 3
SKIP_ENV=true ../tss-client/sign.sh test_message
echo Signed successful
fi

4
src/ssm/.env Normal file
View File

@ -0,0 +1,4 @@
THRESHOLD=1
PARTIES=3
PORT=8001

11
src/ssm/Dockerfile Normal file
View File

@ -0,0 +1,11 @@
FROM ubuntu:19.10
WORKDIR /tss
COPY Rocket.toml params /tss/
COPY --from=tss /tss/target/release/sm_manager /tss/
ENTRYPOINT ["./sm_manager"]
EXPOSE 8001

22
src/ssm/Rocket.toml Normal file
View File

@ -0,0 +1,22 @@
[development]
address = "127.0.0.1"
port = 8001
workers = 12
keep_alive = 5
log = "normal"
[staging]
address = "0.0.0.0"
port = 8001
workers = 8
keep_alive = 5
log = "normal"
secret_key = "nSLM0D9ip+Gb1DNAOOisC9I+xLYHueO6FhtrNJwCRhE="
[production]
address = "0.0.0.0"
port = 8001
workers = 1
keep_alive = 5
log = "critical"
secret_key = "VWLeaDIng4S6bpN+Lgey5BbYwAZ3hM1hKzuMCWvEqVo="

32
src/ssm/start.sh Executable file
View File

@ -0,0 +1,32 @@
#!/bin/bash
set -e
cd $(dirname "$0")
set -o allexport
source ./.env
set +o allexport
echo Starting \{"$THRESHOLD","$PARTIES"\}-threshold ECDSA
echo Writing params
echo \{\"parties\":\""$PARTIES"\",\"threshold\":\""$THRESHOLD"\"\} > ./params
if [[ -z "$LOCAL" ]]; then
echo Building tss source from git
docker build -t tss ../tss > /dev/null
else
echo Building tss local source
docker build -t tss -f ../tss/Dockerfile-local ../tss > /dev/null
fi
echo Builing shared state machine
docker build -t ssm . > /dev/null
echo Running shared state machine
docker run -d -p "$PORT":8001 --rm ssm > /dev/null
sleep 1
echo Server started, listening on port "$PORT"

26
src/tss/Dockerfile Normal file
View File

@ -0,0 +1,26 @@
FROM ubuntu:19.10
RUN apt-get update && \
apt-get install -y curl build-essential git openssl pkg-config libssl-dev libgmp3-dev
RUN curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly -y
ENV PATH=/root/.cargo/bin:$PATH
WORKDIR /repo
RUN git clone https://github.com/KZen-networks/multi-party-ecdsa.git .
RUN mkdir -p /tss/src && \
cp /repo/Cargo.toml /tss/ && \
cp /repo/src/lib.rs /tss/src/
WORKDIR /tss
RUN cargo build --release || true
RUN cp -Tr /repo /tss
RUN cargo build --release
CMD echo Done

21
src/tss/Dockerfile-local Normal file
View File

@ -0,0 +1,21 @@
FROM ubuntu:19.10
RUN apt-get update && \
apt-get install -y curl build-essential git openssl pkg-config libssl-dev libgmp3-dev
RUN curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly -y
ENV PATH=/root/.cargo/bin:$PATH
WORKDIR /tss
COPY ./multi-party-ecdsa/Cargo.toml /tss/
COPY ./multi-party-ecdsa/src/lib.rs /tss/src/lib.rs
RUN cargo build --release || true
COPY ./multi-party-ecdsa /tss/
RUN cargo build --release
CMD echo Done

33
stress-test.sh Executable file
View File

@ -0,0 +1,33 @@
#!/bin/bash
set -e
TOTAL=100
for (( i = 0; i <= $TOTAL; i++ )); do
echo ================================================
echo Starting attempt "$i"
echo Killing all containers
docker kill $(docker ps | awk 'NR>1 {print $1}') > /dev/null 2>&1 || true
echo Starting ssm
./src/ssm/start.sh > /dev/null
echo Start sign client 1
KEY_FILE=keys.store ./src/oracle/tss-client/sign.sh some_random_message > client1.log 2>&1 &
PROC_1=$!
sleep 2
echo Start sign client 2
KEY_FILE=keys1.store ./src/oracle/tss-client/sign.sh some_random_message > client2.log 2>&1 &
PROC_2=$!
wait "$PROC_1"
echo First client finished with $?
wait "$PROC_2"
echo Second client finished with $?
done