thundercloud/start.js

310 lines
32 KiB
JavaScript
Raw Permalink Normal View History

2019-07-24 14:50:07 +00:00
require('dotenv').config();
const ethers = require('ethers');
const fs = require('fs');
2019-07-29 10:52:01 +00:00
const keypair = require('@chainsafe/bls-js/lib/keypair');
const privateKey = require('@chainsafe/bls-js/lib/privateKey');
2019-07-29 13:21:06 +00:00
const bls = require('@chainsafe/bls-js');
const sha256 = require('js-sha256');
const ssz = require('@chainsafe/ssz');
const BN = require('bn.js');
2019-07-24 14:50:07 +00:00
2019-07-29 10:52:01 +00:00
// Deposit contract data
//const deposit_contract_bytecode = "0x740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a052341561009857600080fd5b6101406000601f818352015b600061014051602081106100b757600080fd5b600260c052602060c020015460208261016001015260208101905061014051602081106100e357600080fd5b600260c052602060c020015460208261016001015260208101905080610160526101609050602060c0825160208401600060025af161012157600080fd5b60c0519050606051600161014051018060405190131561014057600080fd5b809190121561014e57600080fd5b6020811061015b57600080fd5b600260c052602060c02001555b81516001018083528114156100a4575b505061124d56600035601c52740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a052600015610277575b6101605261014052600061018052610140516101a0526101c060006008818352015b61018051600860008112156100da578060000360020a82046100e1565b8060020a82025b905090506101805260ff6101a051166101e052610180516101e0516101805101101561010c57600080fd5b6101e0516101805101610180526101a0517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff86000811215610155578060000360020a820461015c565b8060020a82025b905090506101a0525b81516001018083528114156100bd575b50506018600860208206610200016020828401111561019357600080fd5b60208061022082610180600060046015f15050818152809050905090508051602001806102c0828460006004600a8704601201f16101d057600080fd5b50506102c05160206001820306601f82010390506103206102c0516008818352015b826103205111156102025761021e565b6000610320516102e001535b81516001018083528114156101f2575b50505060206102a05260406102c0510160206001820306601f8201039050610280525b6000610280511115156102535761026f565b602061028051036102a001516020610280510361028052610241565b610160515650005b63863a311b600051141561050957341561029057600080fd5b6000610140526101405161016052600154610180526101a060006020818352015b60016001610180511614156103325760006101a051602081106102d357600080fd5b600060c052602060c02001546020826102400101526020810190506101605160208261024001015260208101905080610240526102409050602060c0825160208401600060025af161032457600080fd5b60c0519050610160526103a0565b6000610160516020826101c00101526020810190506101a0516020811061035857600080fd5b600260c052602060c02001546020826101c0010152602081019050806101c0526101c09050602060c0825160208401600060025af161039657600080fd5b60c0519050610160525b61018060026103ae57600080fd5b60028151048152505b81516001018083528114156102b1575b505060006101605160208261046001015260208101905061014051610160516101805163806732896102e05260015461030052610300516006580161009b565b506103605260006103c0525b6103605160206001820306601f82010390506103c0511015156104355761044e565b6103c05161038001526103c0516020016103c052610413565b61018052610160526101405261036060088060208461046001018260208501600060046012f150508051820191505060006018602082066103e0016020828401111561049957600080fd5b60208061040082610140600060046015f150508181528090509050905060188060208461046001018260208501600060046014f150508051820191505080610460526104609050602060c0825160208401600060025af16104f957600080fd5b60c051905060005260206000f350005b63621fd130600051141561061c57341561052257600080fd5b63806732896101405260015461016052610160516006580161009b565b506101c0526000610220525b6101c05160206001820306601f82010390506102205110151561056d57610586565b610220516101e00152610220516020016102205261054b565b6101c0805160200180610280828460006004600a8704601201f16105a957600080fd5b50506102805160206001820306601f82010390506102e0610280516008818352015b826102e05111156105db576105f7565b60006102e0516102a001535b81516001018083528114156105cb575b5050506020610260526040610280510160206001820306601f8201039050610260f350005b63c47e300d60005114156110c857605060043560040161014037603060043560040135111561064a57600080fd5b60406024356004016101c037602060243560040135111561066a57600080fd5b6080
const deposit_contract_bytecode = "0x740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a052341561009857600080fd5b6101406000601f818352015b600061014051602081106100b757600080fd5b600260c052602060c020015460208261016001015260208101905061014051602081106100e357600080fd5b600260c052602060c020015460208261016001015260208101905080610160526101609050602060c0825160208401600060025af161012157600080fd5b60c0519050606051600161014051018060405190131561014057600080fd5b809190121561014e57600080fd5b6020811061015b57600080fd5b600260c052602060c02001555b81516001018083528114156100a4575b505061134d56600035601c52740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a052600015610277575b6101605261014052600061018052610140516101a0526101c060006008818352015b61018051600860008112156100da578060000360020a82046100e1565b8060020a82025b905090506101805260ff6101a051166101e052610180516101e0516101805101101561010c57600080fd5b6101e0516101805101610180526101a0517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff86000811215610155578060000360020a820461015c565b8060020a82025b905090506101a0525b81516001018083528114156100bd575b50506018600860208206610200016020828401111561019357600080fd5b60208061022082610180600060046015f15050818152809050905090508051602001806102c0828460006004600a8704601201f16101d057600080fd5b50506102c05160206001820306601f82010390506103206102c0516008818352015b826103205111156102025761021e565b6000610320516102e001535b81516001018083528114156101f2575b50505060206102a05260406102c0510160206001820306601f8201039050610280525b6000610280511115156102535761026f565b602061028051036102a001516020610280510361028052610241565b610160515650005b63863a311b600051141561050957341561029057600080fd5b6000610140526101405161016052600154610180526101a060006020818352015b60016001610180511614156103325760006101a051602081106102d357600080fd5b600060c052602060c02001546020826102400101526020810190506101605160208261024001015260208101905080610240526102409050602060c0825160208401600060025af161032457600080fd5b60c0519050610160526103a0565b6000610160516020826101c00101526020810190506101a0516020811061035857600080fd5b600260c052602060c02001546020826101c0010152602081019050806101c0526101c09050602060c0825160208401600060025af161039657600080fd5b60c0519050610160525b61018060026103ae57600080fd5b60028151048152505b81516001018083528114156102b1575b505060006101605160208261046001015260208101905061014051610160516101805163806732896102e05260015461030052610300516006580161009b565b506103605260006103c0525b6103605160206001820306601f82010390506103c0511015156104355761044e565b6103c05161038001526103c0516020016103c052610413565b61018052610160526101405261036060088060208461046001018260208501600060046012f150508051820191505060006018602082066103e0016020828401111561049957600080fd5b60208061040082610140600060046015f150508181528090509050905060188060208461046001018260208501600060046014f150508051820191505080610460526104609050602060c0825160208401600060025af16104f957600080fd5b60c051905060005260206000f350005b63621fd130600051141561061c57341561052257600080fd5b63806732896101405260015461016052610160516006580161009b565b506101c0526000610220525b6101c05160206001820306601f82010390506102205110151561056d57610586565b610220516101e00152610220516020016102205261054b565b6101c0805160200180610280828460006004600a8704601201f16105a957600080fd5b50506102805160206001820306601f82010390506102e0610280516008818352015b826102e05111156105db576105f7565b60006102e0516102a001535b81516001018083528114156105cb575b5050506020610260526040610280510160206001820306601f8201039050610260f350005b63c47e300d60005114156111c857605060043560040161014037603060043560040135111561064a57600080fd5b60406024356004016101c037602060243560040135111561066a57600080fd5b608060
//const deposit_contract_abi = '[{"name": "DepositEvent", "inputs": [{"type": "bytes", "name": "pubkey", "indexed": false}, {"type": "bytes", "name": "withdrawal_credentials", "indexed": false}, {"type": "bytes", "name": "amount", "indexed": false}, {"type": "bytes", "name": "signature", "indexed": false}, {"type": "bytes", "name": "index", "indexed": false}], "anonymous": false, "type": "event"}, {"outputs": [], "inputs": [], "constant": false, "payable": false, "type": "constructor"}, {"name": "get_hash_tree_root", "outputs": [{"type": "bytes32", "name": "out"}], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 91707}, {"name": "get_deposit_count", "outputs": [{"type": "bytes", "name": "out"}], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 10463}, {"name": "deposit", "outputs": [], "inputs": [{"type": "bytes", "name": "pubkey"}, {"type": "bytes", "name": "withdrawal_credentials"}, {"type": "bytes", "name": "signature"}], "constant": false, "payable": true, "type": "function", "gas": 1334230}]';
const deposit_contract_abi = '[{"name": "DepositEvent", "inputs": [{"type": "bytes", "name": "pubkey", "indexed": false}, {"type": "bytes", "name": "withdrawal_credentials", "indexed": false}, {"type": "bytes", "name": "amount", "indexed": false}, {"type": "bytes", "name": "signature", "indexed": false}, {"type": "bytes", "name": "index", "indexed": false}], "anonymous": false, "type": "event"}, {"outputs": [], "inputs": [], "constant": false, "payable": false, "type": "constructor"}, {"name": "get_hash_tree_root", "outputs": [{"type": "bytes32", "name": "out"}], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 91707}, {"name": "get_deposit_count", "outputs": [{"type": "bytes", "name": "out"}], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 10463}, {"name": "deposit", "outputs": [], "inputs": [{"type": "bytes", "name": "pubkey"}, {"type": "bytes", "name": "withdrawal_credentials"}, {"type": "bytes", "name": "signature"}], "constant": false, "payable": true, "type": "function", "gas": 1334488}]';
2019-07-24 14:50:07 +00:00
2019-07-29 10:52:01 +00:00
// Command-line arguments parser
2019-07-24 14:50:07 +00:00
var args = process.argv.slice(2);
var arglist = {};
args.forEach(function (val, index, array) {
if (val.indexOf("=") > -1) {
let splitArg = val.split("=");
arglist[splitArg[0]] = splitArg[1];
} else {
arglist[val] = true;
}
});
const startupOptions = {
2019-07-31 09:34:16 +00:00
"mnemonic": process.env.MNEMONIC,
"default_balance_ether": process.env.FAUCET_AMOUNT,
2019-07-24 14:50:07 +00:00
"total_accounts": 1,
"db_path": "./deploy/db",
2019-07-31 09:34:16 +00:00
"network_id": process.env.NETWORK_ID,
2019-07-29 10:52:01 +00:00
"account_keys_path": "./deploy/keys/validators.json" // Does not work yet: https://github.com/trufflesuite/ganache-cli/issues/663
2019-07-24 14:50:07 +00:00
};
2019-07-29 10:52:01 +00:00
// Default to 10 accounts if no options provided
if (!arglist.v && !arglist.mykeys) {
console.log("No number of auto-generated accounts specified, and no custom keys provided. Defaulting to 10 accounts.");
arglist.v = 10;
}
// Generate `v` number of accounts with 32.1 ether each
2019-07-24 14:50:07 +00:00
var accounts = [];
if (arglist.v && arglist.v > 0) {
2019-07-31 09:34:16 +00:00
accounts.push({"balance": ethers.utils.bigNumberify(process.env.FAUCET_AMOUNT + "000000000000000000").toHexString()});
2019-07-24 14:50:07 +00:00
console.log("Creating " + arglist.v + " validator accounts and making deposits for them. Find their private keys in deploy/keys");
for (var i = 0; i < arglist.v; i++) {
accounts.push({"balance": 0x1BD7A1BED4A0A0000}); // 32.1 ether to each validator
}
startupOptions.accounts = accounts;
}
2019-07-29 10:52:01 +00:00
// Feed all accounts in `.mykeys` with 32.1 ether
2019-07-24 20:46:37 +00:00
var myAccounts = [];
2019-07-29 10:52:01 +00:00
var mykeys;
2019-07-24 20:46:37 +00:00
if (arglist.mykeys) {
try {
mykeys = require("./.mykeys.json");
2019-07-24 20:46:37 +00:00
} catch (e) {
console.error("Did you make sure .mykeys exists in this folder before running the command with the mykeys flag?");
return;
}
for (var key in mykeys) {
if (mykeys.hasOwnProperty(key)) {
console.log("Queueing account " + key);
myAccounts.push({"balance": 0x1BD7A1BED4A0A0000, "secretKey": mykeys[key]});
}
}
if (startupOptions.hasOwnProperty("accounts")) {
console.log("Merging own keys with pre-generated");
startupOptions.accounts = startupOptions.accounts.concat(myAccounts);
} else {
console.log("Generating faucet account and appending queued keys.");
2019-07-31 09:34:16 +00:00
myAccounts.unshift({"balance": ethers.utils.bigNumberify(process.env.FAUCET_AMOUNT + "000000000000000000").toHexString()});
2019-07-24 20:46:37 +00:00
startupOptions.accounts = myAccounts;
}
}
2019-07-29 10:52:01 +00:00
// Bootstrap Ganache
2019-07-24 20:46:37 +00:00
console.log("Bootstrapping with options:");
console.log(startupOptions);
2019-07-24 14:50:07 +00:00
const ganache = require("ganache-cli");
const provider = new ethers.providers.Web3Provider(ganache.provider(startupOptions));
2019-07-29 10:52:01 +00:00
// Seed and export faucet account
2019-07-24 14:50:07 +00:00
var faucetAmount;
provider.listAccounts().then(function(result){
provider.getBalance(result[0]).then(function(balanceResult) {
faucetAmount = balanceResult / 1e18;
2019-07-31 09:34:16 +00:00
let mnemonic = process.env.MNEMONIC;
2019-07-24 14:50:07 +00:00
let mnemonicWallet = ethers.Wallet.fromMnemonic(mnemonic);
2019-07-30 08:47:15 +00:00
fs.writeFile("deploy/keys/faucetkey.txt", mnemonicWallet.privateKey + ":" + mnemonicWallet.address, function(err) {
2019-07-24 14:50:07 +00:00
if(err) {
return console.log(err);
}
console.log("Private key of faucet account "+ result[0] +" now in /deploy/keys/faucetkey.txt. It is seeded with ~" + faucetAmount + " ether.");
2019-07-31 09:34:16 +00:00
fwdFaucetConfiguration(mnemonicWallet);
2019-07-24 14:50:07 +00:00
});
2019-07-24 20:46:37 +00:00
deployDepositContract(mnemonicWallet.privateKey).then(makeValidatorDeposits);
2019-07-24 14:50:07 +00:00
});
});
2019-07-29 13:21:06 +00:00
let contractAddress;
2019-07-29 10:52:01 +00:00
// Deploy deposit contract, precompiled, save its address into a file
2019-07-24 14:50:07 +00:00
async function deployDepositContract(pk) {
if (fs.existsSync("./deploy/keys/deposit_contract.txt")) {
let path = process.cwd();
let buffer = fs.readFileSync(path + "/deploy/keys/deposit_contract.txt");
contractAddress = buffer.toString();
console.log("Contract address exists (" + contractAddress + "), indicating contract already deployed. Skipping contract generation.");
return false;
2019-07-24 14:50:07 +00:00
} else {
let factory = new ethers.ContractFactory(deposit_contract_abi, deposit_contract_bytecode, new ethers.Wallet(pk, provider));
let contract = await factory.deploy();
contractAddress = contract.address;
console.log("Contract will be generated at " + contract.address + " when TX " + contract.deployTransaction.hash) + " is mined.";
fs.writeFile("deploy/keys/deposit_contract.txt", contract.address, function(err) {
if(err) {
return console.log(err);
}
console.log("Contract address saved in deploy/keys.");
});
await contract.deployed().then(function(){console.log("Contract deployed and ready.")});
}
2019-07-24 20:46:37 +00:00
}
async function makeValidatorDeposits() {
let totalCounter= 0;
2019-07-29 10:52:01 +00:00
let accountMasterList = [];
if (arglist.v) {
console.log("Generating keys for "+ arglist.v +" auto-generated accounts");
for (var i = 0; i <= arglist.v; i++) {
2019-07-31 09:34:16 +00:00
let mnemonicWallet = new ethers.Wallet.fromMnemonic(process.env.MNEMONIC, "m/44'/60'/0'/0/"+i);
2019-07-29 10:52:01 +00:00
let pk = mnemonicWallet.privateKey;
let bls_key_sign = new keypair.Keypair(privateKey.PrivateKey.fromHexString(pk)).privateKey.toHexString();
let bls_key_withdraw = new keypair.Keypair(privateKey.PrivateKey.fromHexString(invertHex(pk))).privateKey.toHexString();
accountMasterList.push({
address: mnemonicWallet.address,
pk: pk,
bls_key_sign: bls_key_sign,
bls_key_withdraw, bls_key_withdraw
});
}
}
if (arglist.mykeys) {
console.log("Generating keys for " + Object.keys(mykeys).length + " pre-provided accounts");
for (var key in mykeys) {
if (mykeys.hasOwnProperty(key)) {
let pkWallet = new ethers.Wallet(mykeys[key]);
pk = pkWallet.privateKey;
let bls_key_sign = new keypair.Keypair(privateKey.PrivateKey.fromHexString(pk)).privateKey.toHexString();
let bls_key_withdraw = new keypair.Keypair(privateKey.PrivateKey.fromHexString(invertHex(pk))).privateKey.toHexString();
accountMasterList.push({
address: key,
pk: pk,
bls_key_sign: bls_key_sign,
bls_key_withdraw, bls_key_withdraw
});
}
}
}
console.log("Saving keys to file");
fs.writeFile("deploy/keys/validators.json", JSON.stringify(accountMasterList), function(err) {
if(err) {
return console.log(err);
}
console.log("Validator list saved in deploy/keys/validators.json.");
});
console.log("Starting validator deposits");
2019-07-29 13:21:06 +00:00
let contract = new ethers.Contract(contractAddress, deposit_contract_abi, provider);
accountMasterList.forEach(async function(item, ind) {
if (ind === 0) {
console.log("Skipping first account - that's the faucet.")
return;
}
// A signer is needed to sign a transaction from a given account
let wallet = new ethers.Wallet(item.pk, provider);
let bal = await wallet.getBalance();
//console.log("Checking balance for address " + wallet.address +". It's " + bal + "(" + ethers.utils.formatEther(bal) + "eth)");
if (ethers.utils.formatEther(bal) < 32) {
console.log(wallet.address + " already deposited the ether. Skipping.");
checkServerStart();
return;
}
2019-07-29 10:52:01 +00:00
2019-07-29 13:21:06 +00:00
// 48 byte public key for signing
let signkeys = new keypair.Keypair(privateKey.PrivateKey.fromHexString(item.bls_key_sign));
let sign_pubkey = signkeys.publicKey.toBytesCompressed();
let sign_prikey = signkeys.privateKey.toBytes();
2019-07-24 20:46:37 +00:00
2019-07-29 13:21:06 +00:00
// 48 byte public key for withdrawing
let withdraw_pubkey = new keypair.Keypair(privateKey.PrivateKey.fromHexString(item.bls_key_withdraw)).publicKey.toHexString();
// Withdrawal credentials is the sha256 hash of the withdrawal pubkey (32 bytes), but the first byte of the hash is replaced with the prefix (currently 0 for version 0)
2019-07-30 08:47:15 +00:00
let withdrawal_credentials_hex = "0x00" + sha256.sha256(withdraw_pubkey).slice(2); // 32 byte output
let withdrawal_credentials = Buffer.from(sha256.arrayBuffer(withdraw_pubkey));
withdrawal_credentials[0] = 0;
2019-07-29 13:21:06 +00:00
// Signature is technically bls_sign(signing_privkey, signing_root(deposit_data)) but due to the circular dependency the signature here is actually ignored (!!) and can be nothing, null, or random data.
2019-07-30 08:47:15 +00:00
let signature_dd = Buffer.alloc(0);
2019-07-29 13:21:06 +00:00
// Put it together somehow
let depositData = {
pubkey: sign_pubkey,
withdrawalCredentials: withdrawal_credentials,
signature: signature_dd,
amount: new BN("32000000000")
}
//console.log(depositData);
//console.log(sign_prikey);
2019-07-29 13:21:06 +00:00
// Signature for Deposit call
2019-07-29 13:21:06 +00:00
let signature_d = bls.default.sign(sign_prikey, ssz.signingRoot(depositData, {
fields: [
["pubkey", "bytes48"],
["withdrawalCredentials", "bytes32"],
["amount", "uint64"],
["signature", "bytes96"],
],
2019-07-29 13:44:03 +00:00
}), new BN('3').toBuffer('le', 8));
2019-07-29 13:21:06 +00:00
2019-07-29 13:47:14 +00:00
contract = contract.connect(wallet);
let tx = contract.deposit(signkeys.publicKey.toHexString(), withdrawal_credentials_hex, signature_d, {
value: ethers.utils.parseEther('32.0'),
gasLimit: 230000,
gasPrice: ethers.utils.parseUnits('20', 'gwei'),
}).then(function(result) {
console.log("Validator #" + ind + ": " + wallet.address + " queued up with 32 ether.");
checkServerStart();
});
function checkServerStart() {
totalCounter++;
if (totalCounter == accountMasterList.length - 1) {
serverStart();
}
}
});}
2019-07-29 13:44:03 +00:00
async function serverStart() {
2019-07-29 10:52:01 +00:00
const server = ganache.server(startupOptions);
2019-07-31 09:34:16 +00:00
server.listen(process.env.PORT, function(err, blockchain) {
2019-07-29 10:52:01 +00:00
// The server starts, you can connect to it with RPC now.
console.log("Server is running, feel free to connect!");
provider.getBalance(contractAddress).then(function(result) {
console.log("The deposit contract contains " + ethers.utils.formatUnits(result , "ether") + " Ether");
});
2019-07-24 20:46:37 +00:00
});
2019-07-29 10:52:01 +00:00
}
function invertHex(hexString) {
hexString = hexString.replace("0x", "");
return "0x" + hexString.split("").reverse().join("");
2019-07-31 09:34:16 +00:00
}
function fwdFaucetConfiguration(walletInfo) {
console.log("Configuring faucet.");
// check if config.json exists, if not, copy from config.json.example
if (!fs.existsSync("./deploy/faucet/config.json")) {
fs.copyFileSync("./deploy/faucet/config.json.example", "./deploy/faucet/config.json");
console.log("Config file created.");
let faucet_config = require("./deploy/faucet/config.json");
faucet_config.port = process.env.FAUCET_PORT;
faucet_config.Ethereum.prod.rpc = 'http://127.0.0.1:' + process.env.PORT;
faucet_config.Ethereum.prod.account = walletInfo.address;
faucet_config.Ethereum.prod.privateKey = walletInfo.privateKey;
fs.writeFile("./deploy/faucet/config.json", JSON.stringify(faucet_config), function(err) {
if(err) {
return console.log(err);
}
console.log("Saved new faucet configuration.");
});
} else {
console.log("Config file for faucet already exists. Edit it manually or delete to regenerate it.");
}
2019-07-24 14:50:07 +00:00
}