-
Notifications
You must be signed in to change notification settings - Fork 46
BlockHeaderRegistry #123
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
BlockHeaderRegistry #123
Changes from 13 commits
f8f9cbe
35d6c1b
caa3287
9d32df8
5488489
ab884bf
cb6079c
9f4288a
edf0723
d838980
7a6b716
47040cb
3de0b53
7f81d05
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| [{"inputs":[{"internalType":"address","name":"_consensus","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"blockchainId","type":"uint256"},{"internalType":"string","name":"rpc","type":"string"}],"name":"addBlockchain","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes","name":"rlpHeader","type":"bytes"},{"components":[{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"vs","type":"bytes32"}],"internalType":"struct Signature","name":"signature","type":"tuple"},{"internalType":"uint256","name":"blockchainId","type":"uint256"},{"internalType":"bytes32","name":"blockHash","type":"bytes32"},{"internalType":"uint256","name":"cycleEnd","type":"uint256"},{"internalType":"address[]","name":"validators","type":"address[]"}],"internalType":"struct Block[]","name":"blocks","type":"tuple[]"}],"name":"addSignedBlocks","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"blockHashes","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"blockchains","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getRpcs","outputs":[{"components":[{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"string","name":"rpc","type":"string"}],"internalType":"struct BlockHeaderRegistry.Rpc[]","name":"_rpcs","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"blockchainId","type":"uint256"},{"internalType":"uint256","name":"number","type":"uint256"}],"name":"getSignedBlock","outputs":[{"internalType":"bytes32","name":"blockHash","type":"bytes32"},{"components":[{"internalType":"bytes32","name":"parentHash","type":"bytes32"},{"internalType":"bytes32","name":"uncleHash","type":"bytes32"},{"internalType":"address","name":"coinbase","type":"address"},{"internalType":"bytes32","name":"root","type":"bytes32"},{"internalType":"bytes32","name":"txHash","type":"bytes32"},{"internalType":"bytes32","name":"receiptHash","type":"bytes32"},{"internalType":"bytes32[8]","name":"bloom","type":"bytes32[8]"},{"internalType":"uint256","name":"difficulty","type":"uint256"},{"internalType":"uint256","name":"number","type":"uint256"},{"internalType":"uint256","name":"gasLimit","type":"uint256"},{"internalType":"uint256","name":"gasUsed","type":"uint256"},{"internalType":"uint256","name":"time","type":"uint256"},{"internalType":"bytes32","name":"mixDigest","type":"bytes32"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"baseFee","type":"uint256"},{"internalType":"bytes","name":"extra","type":"bytes"}],"internalType":"struct BlockHeader","name":"blockHeader","type":"tuple"},{"components":[{"internalType":"address","name":"creator","type":"address"},{"internalType":"bytes[]","name":"signatures","type":"bytes[]"},{"internalType":"uint256","name":"cycleEnd","type":"uint256"},{"internalType":"address[]","name":"validators","type":"address[]"}],"internalType":"struct SignedBlock","name":"signedBlock","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"}],"name":"hasValidatorSigned","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"rpcs","outputs":[{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"string","name":"rpc","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"signedBlocks","outputs":[{"internalType":"address","name":"creator","type":"address"},{"internalType":"uint256","name":"cycleEnd","type":"uint256"}],"stateMutability":"view","type":"function"}] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,13 +5,18 @@ const fs = require('fs') | |
| const HDWalletProvider = require('truffle-hdwallet-provider') | ||
| const EthWallet = require('ethereumjs-wallet') | ||
| const Web3 = require('web3') | ||
| const ethers = require('ethers') | ||
| const { sign, signFuse } = require('./utils') | ||
|
|
||
| const configDir = path.join(cwd, process.env.CONFIG_DIR || 'config/') | ||
|
|
||
| const {ETH_RPC, BSC_RPC, RPC: FUSE_RPC} = process.env | ||
|
|
||
| let web3 | ||
| let walletProvider | ||
| let account | ||
| let consensus, blockReward | ||
| let consensus, blockReward, blockRegistry | ||
| let blockchains = {} | ||
|
|
||
| function initWalletProvider() { | ||
| logger.info(`initWalletProvider`) | ||
|
|
@@ -34,6 +39,35 @@ function initWalletProvider() { | |
| web3 = new Web3(walletProvider) | ||
| } | ||
| } | ||
| function initBlockchain(chainId, rpc) { | ||
| logger.info('initBlockchain') | ||
| try { | ||
| blockchains[chainId] = { | ||
| account: walletProvider.addresses[0], | ||
| web3: new Web3(walletProvider), | ||
| rpc, | ||
| signer: new ethers.Wallet(pkey), | ||
| blocks: {}, | ||
| } | ||
| blockchains[chainId].web3.eth.subscribe('newBlockHeaders', async (block) => { | ||
| try { | ||
| if (chainId == 122) { | ||
| let cycleEnd = (await consensus.methods.getCurrentCycleEndBlock.call()).toNumber() | ||
| let validators = await consensus.methods.currentValidators().call() | ||
| const numValidators = validators.length | ||
| blockchains[chainId].blocks[block.hash] = await signFuse(block, chainId, blockchain.provider, blockchain.signer, cycleEnd, validators) | ||
| } | ||
| else { | ||
| blockchains[chainId].blocks[block.hash] = await sign(block, chainId, blockchain.provider, blockchain.signer) | ||
| } | ||
| } catch(e) { | ||
| logger.error(`newBlockHeaders: ${e.toString()}`) | ||
| } | ||
| }) | ||
| } catch(e) { | ||
| throw `initBlockchain(${chainId}, ${rpc}) failed: ${e.toString()}` | ||
| } | ||
| } | ||
|
|
||
| async function getNonce() { | ||
| try { | ||
|
|
@@ -55,6 +89,13 @@ function initBlockRewardContract() { | |
| logger.info(`initBlockRewardContract`, process.env.BLOCK_REWARD_ADDRESS) | ||
| blockReward = new web3.eth.Contract(require(path.join(cwd, 'abi/blockReward')), process.env.BLOCK_REWARD_ADDRESS) | ||
| } | ||
| function initBlockRegistryContract() { | ||
| logger.info(`initBlockRegistryContract`, process.env.BLOCK_REGISTRY_ADDRESS) | ||
| blockRegistry = new web3.eth.Contract(require(path.join(cwd, 'abi/blockRegistry')), process.env.BLOCK_REGISTRY_ADDRESS) | ||
| initBlockchain(1, process.env.ETH_RPC || throw "Missing ETH_RPC in environment") | ||
| initBlockchain(56, process.env.BSC_RPC || throw "Missing BSC_RPC in environment")) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. bsc has public rpc |
||
| initBlockchain(122, process.env.FUSE_RPC || 'https://rpc.fuse.io/') | ||
| } | ||
|
|
||
| function emitInitiateChange() { | ||
| return new Promise(async (resolve, reject) => { | ||
|
|
@@ -120,6 +161,40 @@ function emitRewardedOnCycle() { | |
| }) | ||
| } | ||
|
|
||
| async function emitRegistry() { | ||
| try { | ||
| logger.info('emitRegistry') | ||
| const currentBlock = (await web3.eth.getBlockNumber()).toNumber() | ||
| const chains = await blockRegistry.methods.getRpcs.call() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should be getRpcs() |
||
| await Promise.all(chains.filter(chain => !blockchains[chain[0]] || blockchains[chain[0]].rpc != chain[1]).map(async (chain) => initBlockchain(...chain))) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if one initblockchain fail it shouldnt fail the whole function There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. still not done |
||
| const blocks = {} | ||
| const chainIds = {} | ||
| Object.entries(blockchains).forEach((chainId, blockchain) => { | ||
| Object.entries(blockchain.blocks).forEach((hash, signed) => { | ||
| blocks[hash] = signed | ||
| chainIds[hash] = chainId | ||
| delete blockchain.blocks[hash] | ||
| }) | ||
| }) | ||
| } catch(e) { | ||
| throw `emitRegistry failed trying to update rpcs` | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i dont think that should prevent from calling addSignedBlocks part |
||
| } | ||
| try { | ||
| const receipt = await blockRegistry.methods.addSignedBlocks(Object.values(blocks)).send({ from: account }) | ||
| logger.info(`transactionHash: ${receipt.transactionHash}`) | ||
| logger.debug(`receipt: ${JSON.stringify(receipt)}`) | ||
| } catch(e) { | ||
| if (!e.data) throw e | ||
| else { | ||
| logger.error(e) | ||
| const data = e.data; | ||
| const txHash = Object.keys(data)[0]; | ||
| const reason = data[txHash].reason; | ||
| Object.entries(blocks).filter((hash, signed) => hash != reason).forEach((hash, signed) => blockchains[chainIds[hash]].blocks[hash] = signed) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| async function runMain() { | ||
| try { | ||
| logger.info(`runMain`) | ||
|
|
@@ -132,13 +207,17 @@ async function runMain() { | |
| if (!blockReward) { | ||
| initBlockRewardContract() | ||
| } | ||
| if (!blockRegistry) { | ||
| initBlockRegistryContract() | ||
| } | ||
| const isValidator = await consensus.methods.isValidator(web3.utils.toChecksumAddress(account)).call() | ||
| if (!isValidator) { | ||
| logger.warn(`${account} is not a validator, skipping`) | ||
| return | ||
| } | ||
| await emitInitiateChange() | ||
| await emitRewardedOnCycle() | ||
| await emitRegistry() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i dont think an exception here should cause the fuseapp to exit |
||
| } catch (e) { | ||
| logger.error(e) | ||
| process.exit(1) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| const ethers = require('ethers') | ||
|
|
||
| export async function sign(header, chainId, signer) { | ||
| const rlpHeader = hashHeader(header) | ||
| const payload = ethers.utils.keccak256(rlpHeader); | ||
| const { _vs: vs, r } = ethers.utils.splitSignature( | ||
| await signer.signMessage(ethers.utils.arrayify(payload)) | ||
| ); | ||
| return [rlpHeader,[vs,r],chainId,payload,0,[]] | ||
| } | ||
| export function hashHeader(web3Header) { | ||
| const rlpHeader = ethers.utils.RLP.encode( | ||
| Object.values(header).map((v) => (v === 0 ? "0x" : v)) | ||
| ); | ||
| return rlpHeader | ||
| } | ||
|
|
||
| export async function signFuse(header, chainId, signer, cycleEnd, validators) { | ||
| const rlpHeader = hashHeader(header) | ||
| const packed = ethers.utils.solidityPack( | ||
| ["bytes32", "address[]", "uint256"], | ||
| [blockHash, validators, cycleEnd] | ||
| ); | ||
| const payload = ethers.utils.keccak256(packed); | ||
| const { _vs: vs, r } = ethers.utils.splitSignature( | ||
| await signer.signMessage(ethers.utils.arrayify(payload)) | ||
| ); | ||
| return [rlpHeader,[vs,r],chainId,payload,cycleEnd,validators] | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use cloudflare eth endpoint