From f8f9cbe49f0aff7ada5390871b27e060224e9560 Mon Sep 17 00:00:00 2001 From: "Parker M. Lambert" <> Date: Fri, 25 Mar 2022 08:41:34 +0000 Subject: [PATCH 01/13] BlockHeaderRegistry --- app/index.js | 54 ++++- app/utils.js | 29 +++ .../BlockHeaderRegistry.sol | 149 ++++++++++++ .../block-header-registry/fuse/IConsensus.sol | 5 + .../block-header-registry/parseBlock.sol | 219 ++++++++++++++++++ contracts/block-header-registry/structs.sol | 42 ++++ .../test/BlockHeaderRegistryMock.sol | 7 + .../test/ConsensusMock.sol | 12 + .../block-header-registry/test/VotingMock.sol | 15 ++ 9 files changed, 529 insertions(+), 3 deletions(-) create mode 100644 app/utils.js create mode 100644 contracts/block-header-registry/BlockHeaderRegistry.sol create mode 100644 contracts/block-header-registry/fuse/IConsensus.sol create mode 100644 contracts/block-header-registry/parseBlock.sol create mode 100644 contracts/block-header-registry/structs.sol create mode 100644 contracts/block-header-registry/test/BlockHeaderRegistryMock.sol create mode 100644 contracts/block-header-registry/test/ConsensusMock.sol create mode 100644 contracts/block-header-registry/test/VotingMock.sol diff --git a/app/index.js b/app/index.js index 9b66b70..41b5b4b 100644 --- a/app/index.js +++ b/app/index.js @@ -5,15 +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/') let web3 let walletProvider let account -let consensus, blockReward +let consensus, blockReward, blockRegistry +let blockchains = {} -function initWalletProvider() { +function initWalletProvider(rpc) { logger.info(`initWalletProvider`) let keystoreDir = path.join(configDir, 'keys/FuseNetwork') let keystore @@ -25,7 +28,7 @@ function initWalletProvider() { let password = fs.readFileSync(path.join(configDir, 'pass.pwd')).toString().trim() let wallet = EthWallet.fromV3(keystore, password) let pkey = wallet.getPrivateKeyString() - walletProvider = new HDWalletProvider(pkey, process.env.RPC) + walletProvider = new HDWalletProvider(pkey, rpc || process.env.RPC) if (!walletProvider) { throw new Error(`Could not set walletProvider for unknown reason`) } else { @@ -34,6 +37,25 @@ function initWalletProvider() { web3 = new Web3(walletProvider) } } +function initBlockchain(chainId, rpc) { + logger.info('initBlockchain') + let keystoreDir = path.join(configDir, 'keys/FuseNetwork') + let keystore + fs.readdirSync(keystoreDir).forEach(file => { + if (file.startsWith('UTC')) { + keystore = fs.readFileSync(path.join(keystoreDir, file)).toString() + } + }) + let password = fs.readFileSync(path.join(configDir, 'pass.pwd')).toString().trim() + let wallet = EthWallet.fromV3(keystore, password) + let pkey = wallet.getPrivateKeyString() + blockchains[chainId] = { + account: walletProvider.addresses[0], + web3: new Web3(walletProvider), + rpc, + signer: new ethers.Wallet(pkey) + } +} async function getNonce() { try { @@ -55,6 +77,10 @@ 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) +} function emitInitiateChange() { return new Promise(async (resolve, reject) => { @@ -120,6 +146,24 @@ function emitRewardedOnCycle() { }) } +async function emitRegistry() { + logger.info('emitRegistry') + const chains = await blockRegistry.getPastEvents('Blockchain', {fromBlock:0,toBlock:'latest'}) + await Promise.all(chains.filter(chain => blockchains[chain[0]].rpc != chain[1] || !blockchains[chain[0]]).map(async (chain) => initBlockchain(...chain))) + const blocks = await Promise.all(Object.entries(blockchains).map(async ([chainId, blockchain]) => { + const { web3: _web3, signer } = blockchain + const latestBlock = await _web3.eth.getBlock('latest') + if (chainId == 122) { + let cycleEnd = (await consensus.methods.getCurrentCycleEndBlock.call()).toNumber() + let numValidators = (await consensus.methods.currentValidatorsLength.call()).toNumber() + const validators = await Promise.all(new Array(numValidators).map(async (_, i) => await consensus.methods.currentValidatorsAtPosition.call())) + return await signFuse(latestBlock, chainId, provider, signer, cycleEnd, validators) + } + return await sign(latestBlock, chainId, provider, signer) + })) + await blockRegistry.addSignedBlocks.call(blocks) +} + async function runMain() { try { logger.info(`runMain`) @@ -132,6 +176,9 @@ 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`) @@ -139,6 +186,7 @@ async function runMain() { } await emitInitiateChange() await emitRewardedOnCycle() + await emitRegistry() } catch (e) { logger.error(e) process.exit(1) diff --git a/app/utils.js b/app/utils.js new file mode 100644 index 0000000..fa1e0fc --- /dev/null +++ b/app/utils.js @@ -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] +} diff --git a/contracts/block-header-registry/BlockHeaderRegistry.sol b/contracts/block-header-registry/BlockHeaderRegistry.sol new file mode 100644 index 0000000..38f87a8 --- /dev/null +++ b/contracts/block-header-registry/BlockHeaderRegistry.sol @@ -0,0 +1,149 @@ +pragma solidity ^0.8.0; +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import "./structs.sol"; +import "./parseBlock.sol"; +import "./fuse/IConsensus.sol"; + +/** + + The purpose of this contract is to store on Fuse block headers + from different blockchains signed by the Fuse validators. + +**/ +contract BlockHeaderRegistry { + // To prevent double signatures + mapping(bytes32 => mapping(address => bool)) hasValidatorSigned; + + // Block hashes per block number for blockchain + mapping(uint256 => mapping(uint256 => bytes32[])) public blockHashes; + + // Validator signatures per blockHash + mapping(bytes32 => SignedBlock) public signedBlocks; + + // Block header for blockHash + mapping(bytes32 => BlockHeader) blockHeaders; + + mapping(uint256 => string) public blockchains; + + address immutable voting; + address immutable consensus; + + constructor(address _voting, address _consensus) { + voting = _voting; + consensus = _consensus; + } + + event Blockchain(uint256 blockchainId, string rpc); + + modifier onlyVoting() { + require(msg.sender == voting, "onlyVoting"); + _; + } + + function addBlockchain(uint256 blockchainId, string memory rpc) + external + onlyVoting + { + blockchains[blockchainId] = rpc; + emit Blockchain(blockchainId, rpc); + } + + modifier onlyValidator() { + require(_isValidator(msg.sender), "onlyValidator"); + _; + } + + /** + @notice Add a signed block from any blockchain. + @notice Costs slightly more if the block has never been registered before. + @notice Processes fuse blocks slightly differently. + @param blocks List of block headers and signatures to add. + */ + function addSignedBlocks(Block[] calldata blocks) external onlyValidator { + for (uint256 i = 0; i < blocks.length; i++) { + Block calldata _block = blocks[i]; + bytes32 rlpHeaderHash = keccak256(_block.rlpHeader); + require(rlpHeaderHash == _block.blockHash, "rlpHeaderHash"); + bool isFuse = _isFuse(_block.blockchainId); + bytes32 payload = isFuse + ? keccak256( + abi.encodePacked( + rlpHeaderHash, + _block.validators, + _block.cycleEnd + ) + ) + : rlpHeaderHash; + address signer = ECDSA.recover( + ECDSA.toEthSignedMessageHash(payload), + _block.signature.r, + _block.signature.vs + ); + require(msg.sender == signer, "msg.sender == signer"); + require(!hasValidatorSigned[payload][msg.sender], "hasSigned"); + hasValidatorSigned[payload][signer] = true; + if (_isNewBlock(payload)) { + BlockHeader memory blockHeader = parseBlock(_block.rlpHeader); + blockHeaders[payload] = blockHeader; + blockHashes[_block.blockchainId][blockHeader.number].push( + payload + ); + if (isFuse) { + signedBlocks[payload].validators = _block.validators; + signedBlocks[payload].cycleEnd = _block.cycleEnd; + } + signedBlocks[payload].creator = msg.sender; + } + signedBlocks[payload].signatures.push( + abi.encodePacked(_block.signature.r, _block.signature.vs) + ); + } + } + + function getSignedBlock(uint256 blockchainId, uint256 number) + public + view + returns ( + bytes32 blockHash, + BlockHeader memory blockHeader, + SignedBlock memory signedBlock + ) + { + bytes32[] memory _blockHashes = blockHashes[blockchainId][number]; + require(_blockHashes.length != 0, "_blockHashes.length"); + blockHash = _blockHashes[0]; + uint256 _signatures = signedBlocks[blockHash].signatures.length; + for (uint256 i = 1; i < _blockHashes.length; i++) { + uint256 _sigs = signedBlocks[_blockHashes[i]].signatures.length; + if (_sigs > _signatures) { + _signatures = _sigs; + blockHash = _blockHashes[i]; + } + } + SignedBlock storage _block = signedBlocks[blockHash]; + signedBlock.signatures = _block.signatures; + signedBlock.creator = _block.creator; + if (_isFuse(blockchainId)) { + signedBlock.validators = _block.validators; + signedBlock.cycleEnd = _block.cycleEnd; + } + blockHeader = blockHeaders[blockHash]; + } + + function _isValidator(address person) internal virtual returns (bool) { + return IConsensus(consensus).isValidator(person); + } + + function _isNewBlock(bytes32 key) private view returns (bool) { + return signedBlocks[key].signatures.length == 0; + } + + function _isFuse(uint256 blockchainId) + internal + view + virtual + returns (bool) + { + return blockchainId == 0x7a; + } +} diff --git a/contracts/block-header-registry/fuse/IConsensus.sol b/contracts/block-header-registry/fuse/IConsensus.sol new file mode 100644 index 0000000..ae1145e --- /dev/null +++ b/contracts/block-header-registry/fuse/IConsensus.sol @@ -0,0 +1,5 @@ +pragma solidity ^0.8.0; + +interface IConsensus { + function isValidator(address) external returns (bool); +} diff --git a/contracts/block-header-registry/parseBlock.sol b/contracts/block-header-registry/parseBlock.sol new file mode 100644 index 0000000..b428254 --- /dev/null +++ b/contracts/block-header-registry/parseBlock.sol @@ -0,0 +1,219 @@ +pragma solidity ^0.8.0; + +import {BlockHeader} from './structs.sol'; + +function parseBlock(bytes calldata rlpHeader) pure returns (BlockHeader memory header) { + assembly { + // input should be a pointer to start of a calldata slice + function decode_length(input, length) -> offset, strLen, isList { + if iszero(length) { revert(0, 1) } + + let prefix := byte(0, calldataload(input)) + + function getcd(start, len) -> val { + mstore(0, 0) + let dst := sub(32, len) + calldatacopy(dst, start, len) + val := mload(0) + mstore(0, 0) + } + + if lt(prefix, 0x80) { + offset := 0 + strLen := 1 + isList := 0 + leave + } + + if lt(prefix, 0xb8) { + if iszero(gt(length, sub(prefix, 0x80))) { revert(0, 0xff) } + strLen := sub(prefix, 0x80) + offset := 1 + isList := 0 + leave + } + + if lt(prefix, 0xc0) { + if iszero(and( + gt(length, sub(prefix, 0xb7)), + gt(length, add(sub(prefix, 0xb7), getcd(add(input, 1), sub(prefix, 0xb7)))) + )) { revert(0, 0xff) } + + let lenOfStrLen := sub(prefix, 0xb7) + strLen := getcd(add(input, 1), lenOfStrLen) + offset := add(1, lenOfStrLen) + isList := 0 + leave + } + + if lt(prefix, 0xf8) { + if iszero(gt(length, sub(prefix, 0xc0))) { revert(0, 0xff) } + // listLen + strLen := sub(prefix, 0xc0) + offset := 1 + isList := 1 + leave + } + + if lt(prefix, 0x0100) { + if iszero(and( + gt(length, sub(prefix, 0xf7)), + gt(length, add(sub(prefix, 0xf7), getcd(add(input, 1), sub(prefix, 0xf7)))) + )) { revert(0, 0xff) } + + let lenOfListLen := sub(prefix, 0xf7) + // listLen + strLen := getcd(add(input, 1), lenOfListLen) + offset := add(1, lenOfListLen) + isList := 1 + leave + } + + revert(0, 2) + } + + // Initialize rlp variables with the block's list + let iptr := rlpHeader.offset + let ilen := rlpHeader.length + let offset + let len + let isList + offset,len,isList := decode_length(iptr, ilen) + + // There's only 1 list in the Ethereum block RLP encoding (the block itself) + // If the first param isn't a list, revert + switch isList + case 0 { revert(0, 3) } + + // The returned offset + length refer to the list's payload + // We pass those values to begin extracting block properties + iptr := add(iptr, offset) + ilen := len + + // bytes32 parentHash; + offset,len,isList := decode_length(iptr, ilen) + if isList { revert(0, 4) } + calldatacopy(add(header, sub(0x20, len)), add(iptr, offset), len) + iptr := add(iptr, add(len, offset)) + ilen := sub(ilen, len) + + // bytes32 uncleHash; + offset,len,isList := decode_length(iptr, ilen) + if isList { revert(0, 4) } + calldatacopy(add(add(header, 0x20), sub(0x20, len)), add(iptr, offset), len) + iptr := add(iptr, add(len, offset)) + ilen := sub(ilen, len) + + // address coinbase; + offset,len,isList := decode_length(iptr, ilen) + if isList { revert(0, 4) } + calldatacopy(add(add(header, 0x40), sub(0x20, len)), add(iptr, offset), len) + iptr := add(iptr, add(len, offset)) + ilen := sub(ilen, len) + + // bytes32 root; + offset,len,isList := decode_length(iptr, ilen) + if isList { revert(0, 4) } + calldatacopy(add(add(header, 0x60), sub(0x20, len)), add(iptr, offset), len) + iptr := add(iptr, add(len, offset)) + ilen := sub(ilen, len) + + // bytes32 txHash; + offset,len,isList := decode_length(iptr, ilen) + if isList { revert(0, 4) } + calldatacopy(add(add(header, 0x80), sub(0x20, len)), add(iptr, offset), len) + iptr := add(iptr, add(len, offset)) + ilen := sub(ilen, len) + + // bytes32 receiptHash; + offset,len,isList := decode_length(iptr, ilen) + if isList { revert(0, 4) } + calldatacopy(add(add(header, 0xa0), sub(0x20, len)), add(iptr, offset), len) + iptr := add(iptr, add(len, offset)) + ilen := sub(ilen, len) + + // bytes32[8] bloom; + offset,len,isList := decode_length(iptr, ilen) + if isList { revert(0, 4) } + calldatacopy(mload(add(header, 0xc0)), add(iptr, offset), len) + iptr := add(iptr, add(len, offset)) + ilen := sub(ilen, len) + + // uint256 difficulty; + offset,len,isList := decode_length(iptr, ilen) + if isList { revert(0, 4) } + calldatacopy(add(add(header, 0xe0), sub(0x20, len)), add(iptr, offset), len) + iptr := add(iptr, add(len, offset)) + ilen := sub(ilen, len) + +// function write(iptr, len, dst_ptr, base_len) { +// calldatacopy(add(dst_ptr, sub(base_len, len)), iptr, len) +// } + + // uint256 number; + offset,len,isList := decode_length(iptr, ilen) + if isList { revert(0, 4) } + calldatacopy(add(add(header, 0x100), sub(0x20, len)), add(iptr, offset), len) + iptr := add(iptr, add(len, offset)) + ilen := sub(ilen, len) + + // uint256 gasLimit; + offset,len,isList := decode_length(iptr, ilen) + if isList { revert(0, 4) } + calldatacopy(add(add(header, 0x120), sub(0x20, len)), add(iptr, offset), len) + iptr := add(iptr, add(len, offset)) + ilen := sub(ilen, len) + + // uint256 gasUsed; + offset,len,isList := decode_length(iptr, ilen) + if isList { revert(0, 4) } + calldatacopy(add(add(header, 0x140), sub(0x20, len)), add(iptr, offset), len) + iptr := add(iptr, add(len, offset)) + ilen := sub(ilen, len) + + // uint256 time; + offset,len,isList := decode_length(iptr, ilen) + if isList { revert(0, 4) } + calldatacopy(add(add(header, 0x160), sub(0x20, len)), add(iptr, offset), len) + iptr := add(iptr, add(len, offset)) + ilen := sub(ilen, len) + + // bytes extra; + offset,len,isList := decode_length(iptr, ilen) + if isList { revert(0, 4) } + let free := mload(0x40) + mstore(add(header, 0x1e0), free) + mstore(free, len) + mstore(0x40, add(free, add(0x20, len))) + calldatacopy(add(free, 0x20), add(iptr, offset), len) + iptr := add(iptr, add(len, offset)) + ilen := sub(ilen, len) + + // bytes32 mixDigest; + offset,len,isList := decode_length(iptr, ilen) + if isList { revert(0, 4) } + calldatacopy(add(add(header, 0x180), sub(0x20, len)), add(iptr, offset), len) + iptr := add(iptr, add(len, offset)) + ilen := sub(ilen, len) + + // uint64 nonce; + offset,len,isList := decode_length(iptr, ilen) + if isList { revert(0, 4) } + calldatacopy(add(add(header, 0x1a0), sub(0x20, len)), add(iptr, offset), len) + iptr := add(iptr, add(len, offset)) + ilen := sub(ilen, len) + + // uint256 baseFee; + // This might not exist on some chains and legacy blocks + switch gt(iptr, add(rlpHeader.length, rlpHeader.offset)) + case 0 { + offset,len,isList := decode_length(iptr, ilen) + if isList { revert(0, 4) } + calldatacopy(add(add(header, 0x1c0), sub(0x20, len)), add(iptr, offset), len) + iptr := add(iptr, add(len, offset)) + ilen := sub(ilen, len) + } + } +} + + diff --git a/contracts/block-header-registry/structs.sol b/contracts/block-header-registry/structs.sol new file mode 100644 index 0000000..7c6122e --- /dev/null +++ b/contracts/block-header-registry/structs.sol @@ -0,0 +1,42 @@ +pragma solidity ^0.8.0; + +struct Signature { + bytes32 r; + bytes32 vs; +} + +struct SignedBlock { + address creator; + bytes[] signatures; + // Just for fuse + uint256 cycleEnd; + address[] validators; +} + +struct BlockHeader { + bytes32 parentHash; + bytes32 uncleHash; + address coinbase; + bytes32 root; + bytes32 txHash; + bytes32 receiptHash; + bytes32[8] bloom; + uint256 difficulty; + uint256 number; + uint256 gasLimit; + uint256 gasUsed; + uint256 time; + bytes32 mixDigest; + uint256 nonce; + uint256 baseFee; + bytes extra; +} + +struct Block { + bytes rlpHeader; + Signature signature; + uint256 blockchainId; + bytes32 blockHash; + uint256 cycleEnd; + address[] validators; +} diff --git a/contracts/block-header-registry/test/BlockHeaderRegistryMock.sol b/contracts/block-header-registry/test/BlockHeaderRegistryMock.sol new file mode 100644 index 0000000..e1fab5f --- /dev/null +++ b/contracts/block-header-registry/test/BlockHeaderRegistryMock.sol @@ -0,0 +1,7 @@ +pragma solidity ^0.8.0; + +import "../BlockHeaderRegistry.sol"; +import "./VotingMock.sol"; +import "./ConsensusMock.sol"; + +//contract BlockHeaderRegistryMock is BlockHeaderRegistry {} diff --git a/contracts/block-header-registry/test/ConsensusMock.sol b/contracts/block-header-registry/test/ConsensusMock.sol new file mode 100644 index 0000000..953b1d5 --- /dev/null +++ b/contracts/block-header-registry/test/ConsensusMock.sol @@ -0,0 +1,12 @@ +pragma solidity ^0.8.0; + +import "../fuse/IConsensus.sol"; + +contract ConsensusMock is IConsensus { + mapping(address => bool) public override isValidator; + + constructor(address[] memory signers) { + for (uint8 i = 0; i < signers.length; i++) + isValidator[signers[i]] = true; + } +} diff --git a/contracts/block-header-registry/test/VotingMock.sol b/contracts/block-header-registry/test/VotingMock.sol new file mode 100644 index 0000000..98c4760 --- /dev/null +++ b/contracts/block-header-registry/test/VotingMock.sol @@ -0,0 +1,15 @@ +pragma solidity ^0.8.0; + +interface IBlockHeaderRegistry { + function addBlockchain(uint256, string memory) external; +} + +contract VotingMock { + function addBlockchain( + address registry, + uint256 blockchainid, + string memory rpc + ) external { + IBlockHeaderRegistry(registry).addBlockchain(blockchainid, rpc); + } +} From 35d6c1b69b7417fae656e991b0923ea5c5333af3 Mon Sep 17 00:00:00 2001 From: Andrew-Pohl <55916961+Andrew-Pohl@users.noreply.github.com> Date: Tue, 5 Apr 2022 13:43:39 +0200 Subject: [PATCH 02/13] Update spark to OE 3.3.5 (#125) --- Dockerfile.spark | 6 +++--- Version_testNet | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile.spark b/Dockerfile.spark index e82dacc..7836e10 100644 --- a/Dockerfile.spark +++ b/Dockerfile.spark @@ -1,5 +1,5 @@ -FROM alpine:3.13.2 AS builder - +FROM alpine:edge AS builder + ENV HOME=/home/parity ENV PARITY_HOME_DIR=$HOME/.local/share/io.parity.ethereum ENV PARITY_CONFIG_FILE_CHAIN=$PARITY_HOME_DIR/spec.json @@ -20,7 +20,7 @@ RUN apk add --no-cache \ jq \ bash -COPY --from=openethereum/openethereum:v3.2.6 /home/openethereum/openethereum $PARITY_BIN +COPY --from=openethereum/openethereum:v3.3.5 /home/openethereum/openethereum $PARITY_BIN ### Network RPC WebSocket EXPOSE 30300 8545 8546 diff --git a/Version_testNet b/Version_testNet index 234c3a2..56d4d34 100644 --- a/Version_testNet +++ b/Version_testNet @@ -1,3 +1,3 @@ DOCKER_IMAGE_FUSE_APP_VERSION="1.0.0" -DOCKER_IMAGE_FUSE_PARITY_VERSION="2.0.2" +DOCKER_IMAGE_FUSE_PARITY_VERSION="3.0.0" DOCKER_IMAGE_NET_STATS_VERSION="1.0.0" \ No newline at end of file From caa3287471a10232d764ecf58d00d8a154a34c15 Mon Sep 17 00:00:00 2001 From: Andrew-Pohl <55916961+Andrew-Pohl@users.noreply.github.com> Date: Tue, 5 Apr 2022 15:11:36 +0200 Subject: [PATCH 03/13] Pass bootnodes into the container (#124) --- scripts/quickstart.sh | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/scripts/quickstart.sh b/scripts/quickstart.sh index 66ed85f..1e8b9cf 100755 --- a/scripts/quickstart.sh +++ b/scripts/quickstart.sh @@ -43,6 +43,8 @@ DEFAULT_GAS_ORACLE="https:\/\/ethgasstation.info\/json\/ethgasAPI.json" PARITY_SNAPSHOT="https://node-snapshot.s3.eu-central-1.amazonaws.com/db.tar.gz" OE_SNAPSHOT="https://node-snapshot-oe.s3.eu-central-1.amazonaws.com/db.tar.gz" +BOOTNODE_FILE_FUSE="https://raw.githubusercontent.com/fuseio/fuse-network/master/config/bootnodes.txt" +BOOTNODE_FILE_SPARK="https://raw.githubusercontent.com/fuseio/fuse-network/master/config/spark/bootnodes.txt" SNAPSHOT_NODE="$OE_SNAPSHOT" @@ -291,12 +293,6 @@ function parseArguments { displayErrorAndExit "NODE_KEY is still set to default update it in the env file" fi fi - - if [[ $ROLE == bootnode ]] ; then - if ! [[ "$BOOTNODES" ]] ; then - echo "Warning! trying to run a bootnode without BOOTNODES argument!" - fi - fi if ! [ -z "$TESTNET" ] ; then if [[ $TESTNET == true ]] ; then @@ -581,6 +577,25 @@ function run { # Create and start a new container. echo -e "\nStarting as ${ROLE}..." + + # Pull bootnodes + if [[ $TESTNET != true ]] ; then + wget -O bootnodeFile $BOOTNODE_FILE_FUSE + else + wget -O bootnodeFile $BOOTNODE_FILE_SPARK + fi + + BOOTNODES="" + + while IFS= read -r line + do + BOOTNODES+="${line}," + done < "bootnodeFile" + + #remove the trailing comma + BOOTNODES=${BOOTNODES::-1} + + echo "Bootnodes = $BOOTNODES" case $ROLE in "bootnode") @@ -645,7 +660,7 @@ function run { --restart=always \ $DOCKER_IMAGE_PARITY \ --role node \ - --parity-args --no-warp --node-key $NODE_KEY --jsonrpc-threads $NUM_RPC_THREADS --jsonrpc-server-threads $NUM_HTTP_THREADS + --parity-args --no-warp --node-key $NODE_KEY --jsonrpc-threads $NUM_RPC_THREADS --jsonrpc-server-threads $NUM_HTTP_THREADS --bootnodes=$BOOTNODES ;; "validator") @@ -715,7 +730,7 @@ function run { $DOCKER_IMAGE_PARITY \ --role validator \ --address $address \ - --parity-args --no-warp + --parity-args --no-warp --bootnodes=$BOOTNODES ## Start validator-app container with all necessary arguments. $PERMISSION_PREFIX docker run \ @@ -750,7 +765,7 @@ function run { --restart=always \ $DOCKER_IMAGE_PARITY \ --role explorer \ - --parity-args --node-key $NODE_KEY + --parity-args --node-key $NODE_KEY --bootnodes=$BOOTNODES ;; esac From 9d32df888ce2c53773cec7338e604ececd42d365 Mon Sep 17 00:00:00 2001 From: Andrew-Pohl <55916961+Andrew-Pohl@users.noreply.github.com> Date: Mon, 11 Apr 2022 09:54:14 +0100 Subject: [PATCH 04/13] Update bootnodes (#126) --- config/bootnodes.txt | 6 +++--- config/spark/bootnodes.txt | 4 +++- scripts/quickstart.sh | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/config/bootnodes.txt b/config/bootnodes.txt index b7cc3b8..9a6208c 100644 --- a/config/bootnodes.txt +++ b/config/bootnodes.txt @@ -1,3 +1,3 @@ -enode://e21e2053005e40653d055fc01a07768357749d27d630702c203b1a3c00bdf219a104b1d88368173bdff1cd36b836fa89f10a70399aca72223c5d117881f4ecdd@18.184.21.11:30303 -enode://b30277f57a4b0fef9d8093b6bfb6505caa2ab67f3b9279a5468ac14b4012be5898c74266a435f8a412a472b5d1679876e95c2bc924f72ec03b2fe18378182dd1@18.184.223.179:30303 -enode://7bc2e851cad345437984d6550b1b98d7029b694f2793e2c592637a793b243760060a5a3e00d6212b75f1c534a97b41d532221071242d01116e9ff3c8dcc95672@95.217.1.4:30303 +enode://e21e2053005e40653d055fc01a07768357749d27d630702c203b1a3c00bdf219a104b1d88368173bdff1cd36b836fa89f10a70399aca72223c5d117881f4ecdd@18.192.221.115:30303 +enode://b30277f57a4b0fef9d8093b6bfb6505caa2ab67f3b9279a5468ac14b4012be5898c74266a435f8a412a472b5d1679876e95c2bc924f72ec03b2fe18378182dd1@3.123.78.115:30303 +enode://7bc2e851cad345437984d6550b1b98d7029b694f2793e2c592637a793b243760060a5a3e00d6212b75f1c534a97b41d532221071242d01116e9ff3c8dcc95672@95.217.1.4:30303 \ No newline at end of file diff --git a/config/spark/bootnodes.txt b/config/spark/bootnodes.txt index b782fa3..2c103a5 100644 --- a/config/spark/bootnodes.txt +++ b/config/spark/bootnodes.txt @@ -1 +1,3 @@ -enode://20feb2419f1abfb1cf058c8b444b5c049250e51ebbf56c625f8ead53e6b4345489003a5396ba2b548d5b02f328e51a939dbf98f2763cb67b9a3557a8381dd022@3.121.211.115:30303 \ No newline at end of file +enode://20feb2419f1abfb1cf058c8b444b5c049250e51ebbf56c625f8ead53e6b4345489003a5396ba2b548d5b02f328e51a939dbf98f2763cb67b9a3557a8381dd022@3.121.211.115:30303 +enode://ad46ec3c252bc76fb150865382b2dfbfa98ae9156a4b4659fca43b08799b8ec97d1b0a9d70aeccb26e5045853a63807cfc96e68b57cdbe1242aa12c66914f1d4@18.193.78.202:30303 +enode://293bce56d7747bd725059e665fbe9f3f57a9c0444feae31dde74bf4ae94a456ce75bcad96a13e7c01032d952eab9e794694ab088b9469b8f4f3f93222e2020af@3.67.39.73:30303 \ No newline at end of file diff --git a/scripts/quickstart.sh b/scripts/quickstart.sh index 1e8b9cf..3b8b6fd 100755 --- a/scripts/quickstart.sh +++ b/scripts/quickstart.sh @@ -587,10 +587,10 @@ function run { BOOTNODES="" - while IFS= read -r line + for line in $( Date: Sun, 24 Apr 2022 20:47:50 +0000 Subject: [PATCH 05/13] Add abi and config for block registry --- app/Dockerfile | 3 +- app/abi/blockRegistry.json | 322 +++++++++++++++++++++++++++++++++++++ 2 files changed, 324 insertions(+), 1 deletion(-) create mode 100644 app/abi/blockRegistry.json diff --git a/app/Dockerfile b/app/Dockerfile index 11c2b9b..1e641b9 100644 --- a/app/Dockerfile +++ b/app/Dockerfile @@ -5,8 +5,9 @@ ENV LOG_LEVEL=debug ENV RPC=https://rpc.fuse.io ENV CONSENSUS_ADDRESS=0x3014ca10b91cb3D0AD85fEf7A3Cb95BCAc9c0f79 ENV BLOCK_REWARD_ADDRESS=0x63D4efeD2e3dA070247bea3073BCaB896dFF6C9B +ENV BLOCK_REGISTRY_ADDRESS= COPY ./ ./ RUN npm install --only=prod -CMD ["node", "index.js"] \ No newline at end of file +CMD ["node", "index.js"] diff --git a/app/abi/blockRegistry.json b/app/abi/blockRegistry.json new file mode 100644 index 0000000..08ee74d --- /dev/null +++ b/app/abi/blockRegistry.json @@ -0,0 +1,322 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_voting", + "type": "address" + }, + { + "internalType": "address", + "name": "_consensus", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "blockchainId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "rpc", + "type": "string" + } + ], + "name": "Blockchain", + "type": "event" + }, + { + "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": "string", + "name": "", + "type": "string" + } + ], + "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" + } + ], + "name": "signedBlocks", + "outputs": [ + { + "internalType": "address", + "name": "creator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "cycleEnd", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] From ab884bf5912a6c898e44a435dac7359e5d4c3290 Mon Sep 17 00:00:00 2001 From: "Parker M. Lambert" <> Date: Mon, 25 Apr 2022 17:32:35 -0400 Subject: [PATCH 06/13] Fix logic for listening to new block headers --- app/index.js | 45 ++++++++++++++----- .../BlockHeaderRegistry.sol | 5 ++- 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/app/index.js b/app/index.js index 41b5b4b..9045044 100644 --- a/app/index.js +++ b/app/index.js @@ -54,7 +54,19 @@ function initBlockchain(chainId, rpc) { web3: new Web3(walletProvider), rpc, signer: new ethers.Wallet(pkey) + blocks: {}, } + blockchains[chainId].web3.eth.subscribe('newBlockHeaders', async (block) => { + if (chainId == 122) { + let cycleEnd = (await consensus.methods.getCurrentCycleEndBlock.call()).toNumber() + let numValidators = (await consensus.methods.currentValidatorsLength.call()).toNumber() + const validators = await Promise.all(new Array(numValidators).map(async (_, i) => await consensus.methods.currentValidatorsAtPosition.call())) + 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) + } + }) } async function getNonce() { @@ -150,18 +162,29 @@ async function emitRegistry() { logger.info('emitRegistry') const chains = await blockRegistry.getPastEvents('Blockchain', {fromBlock:0,toBlock:'latest'}) await Promise.all(chains.filter(chain => blockchains[chain[0]].rpc != chain[1] || !blockchains[chain[0]]).map(async (chain) => initBlockchain(...chain))) - const blocks = await Promise.all(Object.entries(blockchains).map(async ([chainId, blockchain]) => { - const { web3: _web3, signer } = blockchain - const latestBlock = await _web3.eth.getBlock('latest') - if (chainId == 122) { - let cycleEnd = (await consensus.methods.getCurrentCycleEndBlock.call()).toNumber() - let numValidators = (await consensus.methods.currentValidatorsLength.call()).toNumber() - const validators = await Promise.all(new Array(numValidators).map(async (_, i) => await consensus.methods.currentValidatorsAtPosition.call())) - return await signFuse(latestBlock, chainId, provider, signer, cycleEnd, validators) + 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] + }) + }) + 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) } - return await sign(latestBlock, chainId, provider, signer) - })) - await blockRegistry.addSignedBlocks.call(blocks) + } } async function runMain() { diff --git a/contracts/block-header-registry/BlockHeaderRegistry.sol b/contracts/block-header-registry/BlockHeaderRegistry.sol index 38f87a8..00b86bc 100644 --- a/contracts/block-header-registry/BlockHeaderRegistry.sol +++ b/contracts/block-header-registry/BlockHeaderRegistry.sol @@ -1,5 +1,6 @@ pragma solidity ^0.8.0; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; import "./structs.sol"; import "./parseBlock.sol"; import "./fuse/IConsensus.sol"; @@ -12,7 +13,7 @@ import "./fuse/IConsensus.sol"; **/ contract BlockHeaderRegistry { // To prevent double signatures - mapping(bytes32 => mapping(address => bool)) hasValidatorSigned; + mapping(bytes32 => mapping(address => bool)) public hasValidatorSigned; // Block hashes per block number for blockchain mapping(uint256 => mapping(uint256 => bytes32[])) public blockHashes; @@ -80,7 +81,7 @@ contract BlockHeaderRegistry { _block.signature.vs ); require(msg.sender == signer, "msg.sender == signer"); - require(!hasValidatorSigned[payload][msg.sender], "hasSigned"); + require(!hasValidatorSigned[payload][msg.sender], Strings.toHexString(uint256(payload), 32)); hasValidatorSigned[payload][signer] = true; if (_isNewBlock(payload)) { BlockHeader memory blockHeader = parseBlock(_block.rlpHeader); From cb6079ce1884d84ed7270e4f28254ec1dcff9a77 Mon Sep 17 00:00:00 2001 From: "Parker M. Lambert" <> Date: Sun, 8 May 2022 23:05:38 -0400 Subject: [PATCH 07/13] Deploy to mainnet --- app/Dockerfile | 2 +- app/abi/blockRegistry.json | 323 +------------------------------------ app/scripts/run.sh | 4 +- 3 files changed, 5 insertions(+), 324 deletions(-) diff --git a/app/Dockerfile b/app/Dockerfile index 1e641b9..e3418f4 100644 --- a/app/Dockerfile +++ b/app/Dockerfile @@ -5,7 +5,7 @@ ENV LOG_LEVEL=debug ENV RPC=https://rpc.fuse.io ENV CONSENSUS_ADDRESS=0x3014ca10b91cb3D0AD85fEf7A3Cb95BCAc9c0f79 ENV BLOCK_REWARD_ADDRESS=0x63D4efeD2e3dA070247bea3073BCaB896dFF6C9B -ENV BLOCK_REGISTRY_ADDRESS= +ENV BLOCK_REGISTRY_ADDRESS=0x971Fa1D66194aecf0A4908C73C36497B350b6Dc7 COPY ./ ./ RUN npm install --only=prod diff --git a/app/abi/blockRegistry.json b/app/abi/blockRegistry.json index 08ee74d..dae2c13 100644 --- a/app/abi/blockRegistry.json +++ b/app/abi/blockRegistry.json @@ -1,322 +1 @@ -[ - { - "inputs": [ - { - "internalType": "address", - "name": "_voting", - "type": "address" - }, - { - "internalType": "address", - "name": "_consensus", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "blockchainId", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "string", - "name": "rpc", - "type": "string" - } - ], - "name": "Blockchain", - "type": "event" - }, - { - "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": "string", - "name": "", - "type": "string" - } - ], - "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" - } - ], - "name": "signedBlocks", - "outputs": [ - { - "internalType": "address", - "name": "creator", - "type": "address" - }, - { - "internalType": "uint256", - "name": "cycleEnd", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - } -] +[{"inputs":[{"internalType":"address","name":"_consensus","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"blockchainId","type":"uint256"},{"indexed":false,"internalType":"string","name":"rpc","type":"string"}],"name":"Blockchain","type":"event"},{"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":"string","name":"","type":"string"}],"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"}],"name":"signedBlocks","outputs":[{"internalType":"address","name":"creator","type":"address"},{"internalType":"uint256","name":"cycleEnd","type":"uint256"}],"stateMutability":"view","type":"function"}] diff --git a/app/scripts/run.sh b/app/scripts/run.sh index af33c4a..562c002 100755 --- a/app/scripts/run.sh +++ b/app/scripts/run.sh @@ -4,4 +4,6 @@ POLLING_INTERVAL=5000 \ RPC=http://127.0.0.1:8545 \ CONSENSUS_ADDRESS=0x3014ca10b91cb3D0AD85fEf7A3Cb95BCAc9c0f79 \ BLOCK_REWARD_ADDRESS=0x63D4efeD2e3dA070247bea3073BCaB896dFF6C9B \ -node index.js \ No newline at end of file +BLOCK_REGISTRY_ADDRESS=0x971Fa1D66194aecf0A4908C73C36497B350b6Dc7 \ + +node index.js From edf0723b583e3f755f3c01618a83beb8fcdcda46 Mon Sep 17 00:00:00 2001 From: "Parker M. Lambert" <> Date: Sun, 8 May 2022 23:25:17 -0400 Subject: [PATCH 08/13] Fix some issues with initBlockchain --- app/index.js | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/app/index.js b/app/index.js index 9045044..09dba05 100644 --- a/app/index.js +++ b/app/index.js @@ -11,12 +11,12 @@ const { sign, signFuse } = require('./utils') const configDir = path.join(cwd, process.env.CONFIG_DIR || 'config/') let web3 -let walletProvider +let walletProvider, ethersWallet let account let consensus, blockReward, blockRegistry let blockchains = {} -function initWalletProvider(rpc) { +function initWalletProvider() { logger.info(`initWalletProvider`) let keystoreDir = path.join(configDir, 'keys/FuseNetwork') let keystore @@ -29,6 +29,7 @@ function initWalletProvider(rpc) { let wallet = EthWallet.fromV3(keystore, password) let pkey = wallet.getPrivateKeyString() walletProvider = new HDWalletProvider(pkey, rpc || process.env.RPC) + ethersWallet = new ethers.Wallet(pkey) if (!walletProvider) { throw new Error(`Could not set walletProvider for unknown reason`) } else { @@ -39,16 +40,6 @@ function initWalletProvider(rpc) { } function initBlockchain(chainId, rpc) { logger.info('initBlockchain') - let keystoreDir = path.join(configDir, 'keys/FuseNetwork') - let keystore - fs.readdirSync(keystoreDir).forEach(file => { - if (file.startsWith('UTC')) { - keystore = fs.readFileSync(path.join(keystoreDir, file)).toString() - } - }) - let password = fs.readFileSync(path.join(configDir, 'pass.pwd')).toString().trim() - let wallet = EthWallet.fromV3(keystore, password) - let pkey = wallet.getPrivateKeyString() blockchains[chainId] = { account: walletProvider.addresses[0], web3: new Web3(walletProvider), @@ -59,8 +50,8 @@ function initBlockchain(chainId, rpc) { blockchains[chainId].web3.eth.subscribe('newBlockHeaders', async (block) => { if (chainId == 122) { let cycleEnd = (await consensus.methods.getCurrentCycleEndBlock.call()).toNumber() - let numValidators = (await consensus.methods.currentValidatorsLength.call()).toNumber() - const validators = await Promise.all(new Array(numValidators).map(async (_, i) => await consensus.methods.currentValidatorsAtPosition.call())) + 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 { From d8389808c450d60e38d075ea568cb5d3b86c715e Mon Sep 17 00:00:00 2001 From: "Parker M. Lambert" <> Date: Mon, 9 May 2022 01:15:30 -0400 Subject: [PATCH 09/13] Fix issues outlined in review --- app/Dockerfile | 2 +- app/abi/blockRegistry.json | 2 +- app/index.js | 70 +++--- app/scripts/run.sh | 3 +- .../BlockHeaderRegistry.sol | 34 ++- contracts/block-header-registry/ECDSA.sol | 232 ++++++++++++++++++ contracts/block-header-registry/Strings.sol | 67 +++++ 7 files changed, 366 insertions(+), 44 deletions(-) create mode 100644 contracts/block-header-registry/ECDSA.sol create mode 100644 contracts/block-header-registry/Strings.sol diff --git a/app/Dockerfile b/app/Dockerfile index e3418f4..fd5da2c 100644 --- a/app/Dockerfile +++ b/app/Dockerfile @@ -5,7 +5,7 @@ ENV LOG_LEVEL=debug ENV RPC=https://rpc.fuse.io ENV CONSENSUS_ADDRESS=0x3014ca10b91cb3D0AD85fEf7A3Cb95BCAc9c0f79 ENV BLOCK_REWARD_ADDRESS=0x63D4efeD2e3dA070247bea3073BCaB896dFF6C9B -ENV BLOCK_REGISTRY_ADDRESS=0x971Fa1D66194aecf0A4908C73C36497B350b6Dc7 +ENV BLOCK_REGISTRY_ADDRESS=0xa7BfeDBf11a488EcA3838F03A93d8d96EAba9a02 COPY ./ ./ RUN npm install --only=prod diff --git a/app/abi/blockRegistry.json b/app/abi/blockRegistry.json index dae2c13..78da375 100644 --- a/app/abi/blockRegistry.json +++ b/app/abi/blockRegistry.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"_consensus","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"blockchainId","type":"uint256"},{"indexed":false,"internalType":"string","name":"rpc","type":"string"}],"name":"Blockchain","type":"event"},{"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":"string","name":"","type":"string"}],"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"}],"name":"signedBlocks","outputs":[{"internalType":"address","name":"creator","type":"address"},{"internalType":"uint256","name":"cycleEnd","type":"uint256"}],"stateMutability":"view","type":"function"}] +[{"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"}] diff --git a/app/index.js b/app/index.js index 09dba05..8273d14 100644 --- a/app/index.js +++ b/app/index.js @@ -11,7 +11,7 @@ const { sign, signFuse } = require('./utils') const configDir = path.join(cwd, process.env.CONFIG_DIR || 'config/') let web3 -let walletProvider, ethersWallet +let walletProvider let account let consensus, blockReward, blockRegistry let blockchains = {} @@ -28,8 +28,7 @@ function initWalletProvider() { let password = fs.readFileSync(path.join(configDir, 'pass.pwd')).toString().trim() let wallet = EthWallet.fromV3(keystore, password) let pkey = wallet.getPrivateKeyString() - walletProvider = new HDWalletProvider(pkey, rpc || process.env.RPC) - ethersWallet = new ethers.Wallet(pkey) + walletProvider = new HDWalletProvider(pkey, process.env.RPC) if (!walletProvider) { throw new Error(`Could not set walletProvider for unknown reason`) } else { @@ -40,24 +39,28 @@ function initWalletProvider() { } function initBlockchain(chainId, rpc) { logger.info('initBlockchain') - 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) => { - 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) + 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) => { + 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) { + throw `initBlockchain(${chainId}, ${rpc}) failed: ${e.toString()}` + } } async function getNonce() { @@ -150,18 +153,23 @@ function emitRewardedOnCycle() { } async function emitRegistry() { - logger.info('emitRegistry') - const chains = await blockRegistry.getPastEvents('Blockchain', {fromBlock:0,toBlock:'latest'}) - await Promise.all(chains.filter(chain => blockchains[chain[0]].rpc != chain[1] || !blockchains[chain[0]]).map(async (chain) => initBlockchain(...chain))) - 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] + try { + logger.info('emitRegistry') + const currentBlock = (await web3.eth.getBlockNumber()).toNumber() + const chains = await blockRegistry.methods.getRpcs.call() + await Promise.all(chains.filter(chain => !blockchains[chain[0]] || blockchains[chain[0]].rpc != chain[1]).map(async (chain) => initBlockchain(...chain))) + 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` + } try { const receipt = await blockRegistry.methods.addSignedBlocks(Object.values(blocks)).send({ from: account }) logger.info(`transactionHash: ${receipt.transactionHash}`) diff --git a/app/scripts/run.sh b/app/scripts/run.sh index 562c002..f710cad 100755 --- a/app/scripts/run.sh +++ b/app/scripts/run.sh @@ -4,6 +4,5 @@ POLLING_INTERVAL=5000 \ RPC=http://127.0.0.1:8545 \ CONSENSUS_ADDRESS=0x3014ca10b91cb3D0AD85fEf7A3Cb95BCAc9c0f79 \ BLOCK_REWARD_ADDRESS=0x63D4efeD2e3dA070247bea3073BCaB896dFF6C9B \ -BLOCK_REGISTRY_ADDRESS=0x971Fa1D66194aecf0A4908C73C36497B350b6Dc7 \ - +BLOCK_REGISTRY_ADDRESS=0xa7BfeDBf11a488EcA3838F03A93d8d96EAba9a02 \ node index.js diff --git a/contracts/block-header-registry/BlockHeaderRegistry.sol b/contracts/block-header-registry/BlockHeaderRegistry.sol index 00b86bc..37eed32 100644 --- a/contracts/block-header-registry/BlockHeaderRegistry.sol +++ b/contracts/block-header-registry/BlockHeaderRegistry.sol @@ -1,6 +1,5 @@ pragma solidity ^0.8.0; -import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; +import "./ECDSA.sol"; import "./structs.sol"; import "./parseBlock.sol"; import "./fuse/IConsensus.sol"; @@ -24,19 +23,29 @@ contract BlockHeaderRegistry { // Block header for blockHash mapping(bytes32 => BlockHeader) blockHeaders; - mapping(uint256 => string) public blockchains; + mapping(uint256 => uint256) public blockchains; + + struct Rpc { + uint256 chainId; + string rpc; + } + + Rpc[] public rpcs; - address immutable voting; address immutable consensus; - constructor(address _voting, address _consensus) { - voting = _voting; + constructor(address _consensus) { consensus = _consensus; } - event Blockchain(uint256 blockchainId, string rpc); + function getRpcs() public view returns (Rpc[] memory _rpcs) { + _rpcs = rpcs; + } modifier onlyVoting() { + (bool success, bytes memory result) = consensus.staticcall(abi.encodeWithSignature("getVoting()")); + require(success, "Failed call to getVoting()"); + address voting = abi.decode(result, (address)); require(msg.sender == voting, "onlyVoting"); _; } @@ -45,8 +54,15 @@ contract BlockHeaderRegistry { external onlyVoting { - blockchains[blockchainId] = rpc; - emit Blockchain(blockchainId, rpc); + uint256 index = blockchains[blockchainId] - 1; + if (index == type(uint256).max) { + require(bytes(rpc).length != 0, "Empty rpc"); + rpcs.push(Rpc(blockchainId, rpc)); + blockchains[blockchainId] = rpcs.length; + } + else { + rpcs[index].rpc = rpc; + } } modifier onlyValidator() { diff --git a/contracts/block-header-registry/ECDSA.sol b/contracts/block-header-registry/ECDSA.sol new file mode 100644 index 0000000..ca70913 --- /dev/null +++ b/contracts/block-header-registry/ECDSA.sol @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.5.0) (utils/cryptography/ECDSA.sol) + +pragma solidity ^0.8.0; + +import "./Strings.sol"; + +/** + * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations. + * + * These functions can be used to verify that a message was signed by the holder + * of the private keys of a given address. + */ +library ECDSA { + enum RecoverError { + NoError, + InvalidSignature, + InvalidSignatureLength, + InvalidSignatureS, + InvalidSignatureV + } + + function _throwError(RecoverError error) private pure { + if (error == RecoverError.NoError) { + return; // no error: do nothing + } else if (error == RecoverError.InvalidSignature) { + revert("ECDSA: invalid signature"); + } else if (error == RecoverError.InvalidSignatureLength) { + revert("ECDSA: invalid signature length"); + } else if (error == RecoverError.InvalidSignatureS) { + revert("ECDSA: invalid signature 's' value"); + } else if (error == RecoverError.InvalidSignatureV) { + revert("ECDSA: invalid signature 'v' value"); + } + } + + /** + * @dev Returns the address that signed a hashed message (`hash`) with + * `signature` or error string. This address can then be used for verification purposes. + * + * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: + * this function rejects them by requiring the `s` value to be in the lower + * half order, and the `v` value to be either 27 or 28. + * + * IMPORTANT: `hash` _must_ be the result of a hash operation for the + * verification to be secure: it is possible to craft signatures that + * recover to arbitrary addresses for non-hashed data. A safe way to ensure + * this is by receiving a hash of the original message (which may otherwise + * be too long), and then calling {toEthSignedMessageHash} on it. + * + * Documentation for signature generation: + * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js] + * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers] + * + * _Available since v4.3._ + */ + function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) { + // Check the signature length + // - case 65: r,s,v signature (standard) + // - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._ + if (signature.length == 65) { + bytes32 r; + bytes32 s; + uint8 v; + // ecrecover takes the signature parameters, and the only way to get them + // currently is to use assembly. + /// @solidity memory-safe-assembly + assembly { + r := mload(add(signature, 0x20)) + s := mload(add(signature, 0x40)) + v := byte(0, mload(add(signature, 0x60))) + } + return tryRecover(hash, v, r, s); + } else if (signature.length == 64) { + bytes32 r; + bytes32 vs; + // ecrecover takes the signature parameters, and the only way to get them + // currently is to use assembly. + /// @solidity memory-safe-assembly + assembly { + r := mload(add(signature, 0x20)) + vs := mload(add(signature, 0x40)) + } + return tryRecover(hash, r, vs); + } else { + return (address(0), RecoverError.InvalidSignatureLength); + } + } + + /** + * @dev Returns the address that signed a hashed message (`hash`) with + * `signature`. This address can then be used for verification purposes. + * + * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: + * this function rejects them by requiring the `s` value to be in the lower + * half order, and the `v` value to be either 27 or 28. + * + * IMPORTANT: `hash` _must_ be the result of a hash operation for the + * verification to be secure: it is possible to craft signatures that + * recover to arbitrary addresses for non-hashed data. A safe way to ensure + * this is by receiving a hash of the original message (which may otherwise + * be too long), and then calling {toEthSignedMessageHash} on it. + */ + function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { + (address recovered, RecoverError error) = tryRecover(hash, signature); + _throwError(error); + return recovered; + } + + /** + * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately. + * + * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures] + * + * _Available since v4.3._ + */ + function tryRecover( + bytes32 hash, + bytes32 r, + bytes32 vs + ) internal pure returns (address, RecoverError) { + bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); + uint8 v = uint8((uint256(vs) >> 255) + 27); + return tryRecover(hash, v, r, s); + } + + /** + * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately. + * + * _Available since v4.2._ + */ + function recover( + bytes32 hash, + bytes32 r, + bytes32 vs + ) internal pure returns (address) { + (address recovered, RecoverError error) = tryRecover(hash, r, vs); + _throwError(error); + return recovered; + } + + /** + * @dev Overload of {ECDSA-tryRecover} that receives the `v`, + * `r` and `s` signature fields separately. + * + * _Available since v4.3._ + */ + function tryRecover( + bytes32 hash, + uint8 v, + bytes32 r, + bytes32 s + ) internal pure returns (address, RecoverError) { + // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature + // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines + // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most + // signatures from current libraries generate a unique signature with an s-value in the lower half order. + // + // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value + // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or + // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept + // these malleable signatures as well. + if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { + return (address(0), RecoverError.InvalidSignatureS); + } + if (v != 27 && v != 28) { + return (address(0), RecoverError.InvalidSignatureV); + } + + // If the signature is valid (and not malleable), return the signer address + address signer = ecrecover(hash, v, r, s); + if (signer == address(0)) { + return (address(0), RecoverError.InvalidSignature); + } + + return (signer, RecoverError.NoError); + } + + /** + * @dev Overload of {ECDSA-recover} that receives the `v`, + * `r` and `s` signature fields separately. + */ + function recover( + bytes32 hash, + uint8 v, + bytes32 r, + bytes32 s + ) internal pure returns (address) { + (address recovered, RecoverError error) = tryRecover(hash, v, r, s); + _throwError(error); + return recovered; + } + + /** + * @dev Returns an Ethereum Signed Message, created from a `hash`. This + * produces hash corresponding to the one signed with the + * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] + * JSON-RPC method as part of EIP-191. + * + * See {recover}. + */ + function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) { + // 32 is the length in bytes of hash, + // enforced by the type signature above + return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); + } + + /** + * @dev Returns an Ethereum Signed Message, created from `s`. This + * produces hash corresponding to the one signed with the + * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] + * JSON-RPC method as part of EIP-191. + * + * See {recover}. + */ + function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) { + return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(s.length), s)); + } + + /** + * @dev Returns an Ethereum Signed Typed Data, created from a + * `domainSeparator` and a `structHash`. This produces hash corresponding + * to the one signed with the + * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] + * JSON-RPC method as part of EIP-712. + * + * See {recover}. + */ + function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) { + return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); + } +} diff --git a/contracts/block-header-registry/Strings.sol b/contracts/block-header-registry/Strings.sol new file mode 100644 index 0000000..d38bbe8 --- /dev/null +++ b/contracts/block-header-registry/Strings.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (utils/Strings.sol) + +pragma solidity ^0.8.0; + +/** + * @dev String operations. + */ +library Strings { + bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef"; + + /** + * @dev Converts a `uint256` to its ASCII `string` decimal representation. + */ + function toString(uint256 value) internal pure returns (string memory) { + // Inspired by OraclizeAPI's implementation - MIT licence + // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol + + if (value == 0) { + return "0"; + } + uint256 temp = value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + while (value != 0) { + digits -= 1; + buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); + value /= 10; + } + return string(buffer); + } + + /** + * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. + */ + function toHexString(uint256 value) internal pure returns (string memory) { + if (value == 0) { + return "0x00"; + } + uint256 temp = value; + uint256 length = 0; + while (temp != 0) { + length++; + temp >>= 8; + } + return toHexString(value, length); + } + + /** + * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. + */ + function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { + bytes memory buffer = new bytes(2 * length + 2); + buffer[0] = "0"; + buffer[1] = "x"; + for (uint256 i = 2 * length + 1; i > 1; --i) { + buffer[i] = _HEX_SYMBOLS[value & 0xf]; + value >>= 4; + } + require(value == 0, "Strings: hex length insufficient"); + return string(buffer); + } +} From 7a6b716a1453cb7f786091853e53b07bb539d321 Mon Sep 17 00:00:00 2001 From: "Parker M. Lambert" <> Date: Mon, 9 May 2022 02:57:18 -0400 Subject: [PATCH 10/13] Catch errors in block subscription --- app/index.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/app/index.js b/app/index.js index 8273d14..2ccd34d 100644 --- a/app/index.js +++ b/app/index.js @@ -48,14 +48,18 @@ function initBlockchain(chainId, rpc) { blocks: {}, } blockchains[chainId].web3.eth.subscribe('newBlockHeaders', async (block) => { - 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) + 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) { From 47040cbe9aa2e20d52ab0f09e9265e2827a4eaf5 Mon Sep 17 00:00:00 2001 From: "Parker M. Lambert" <> Date: Mon, 9 May 2022 03:11:33 -0400 Subject: [PATCH 11/13] Bring requested changes --- app/Dockerfile | 2 ++ app/index.js | 5 +++++ app/scripts/run.sh | 2 ++ 3 files changed, 9 insertions(+) diff --git a/app/Dockerfile b/app/Dockerfile index fd5da2c..59113a3 100644 --- a/app/Dockerfile +++ b/app/Dockerfile @@ -6,6 +6,8 @@ ENV RPC=https://rpc.fuse.io ENV CONSENSUS_ADDRESS=0x3014ca10b91cb3D0AD85fEf7A3Cb95BCAc9c0f79 ENV BLOCK_REWARD_ADDRESS=0x63D4efeD2e3dA070247bea3073BCaB896dFF6C9B ENV BLOCK_REGISTRY_ADDRESS=0xa7BfeDBf11a488EcA3838F03A93d8d96EAba9a02 +ENV ETH_RPC= +ENV BSC_RPC= COPY ./ ./ RUN npm install --only=prod diff --git a/app/index.js b/app/index.js index 2ccd34d..8f28c21 100644 --- a/app/index.js +++ b/app/index.js @@ -10,6 +10,8 @@ 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 @@ -90,6 +92,9 @@ function initBlockRewardContract() { 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")) + initBlockchain(122, process.env.FUSE_RPC || 'https://rpc.fuse.io/') } function emitInitiateChange() { diff --git a/app/scripts/run.sh b/app/scripts/run.sh index f710cad..8263b39 100755 --- a/app/scripts/run.sh +++ b/app/scripts/run.sh @@ -5,4 +5,6 @@ RPC=http://127.0.0.1:8545 \ CONSENSUS_ADDRESS=0x3014ca10b91cb3D0AD85fEf7A3Cb95BCAc9c0f79 \ BLOCK_REWARD_ADDRESS=0x63D4efeD2e3dA070247bea3073BCaB896dFF6C9B \ BLOCK_REGISTRY_ADDRESS=0xa7BfeDBf11a488EcA3838F03A93d8d96EAba9a02 \ +ETH_RPC="" \ +BSC_RPC="" \ node index.js From 3de0b536ad402a13858e29f3e25f68b7a2781604 Mon Sep 17 00:00:00 2001 From: "Parker M. Lambert" <> Date: Mon, 9 May 2022 03:12:52 -0400 Subject: [PATCH 12/13] Remove header registry contracts --- .../BlockHeaderRegistry.sol | 166 ------------- contracts/block-header-registry/ECDSA.sol | 232 ------------------ contracts/block-header-registry/Strings.sol | 67 ----- .../block-header-registry/fuse/IConsensus.sol | 5 - .../block-header-registry/parseBlock.sol | 219 ----------------- contracts/block-header-registry/structs.sol | 42 ---- .../test/BlockHeaderRegistryMock.sol | 7 - .../test/ConsensusMock.sol | 12 - .../block-header-registry/test/VotingMock.sol | 15 -- 9 files changed, 765 deletions(-) delete mode 100644 contracts/block-header-registry/BlockHeaderRegistry.sol delete mode 100644 contracts/block-header-registry/ECDSA.sol delete mode 100644 contracts/block-header-registry/Strings.sol delete mode 100644 contracts/block-header-registry/fuse/IConsensus.sol delete mode 100644 contracts/block-header-registry/parseBlock.sol delete mode 100644 contracts/block-header-registry/structs.sol delete mode 100644 contracts/block-header-registry/test/BlockHeaderRegistryMock.sol delete mode 100644 contracts/block-header-registry/test/ConsensusMock.sol delete mode 100644 contracts/block-header-registry/test/VotingMock.sol diff --git a/contracts/block-header-registry/BlockHeaderRegistry.sol b/contracts/block-header-registry/BlockHeaderRegistry.sol deleted file mode 100644 index 37eed32..0000000 --- a/contracts/block-header-registry/BlockHeaderRegistry.sol +++ /dev/null @@ -1,166 +0,0 @@ -pragma solidity ^0.8.0; -import "./ECDSA.sol"; -import "./structs.sol"; -import "./parseBlock.sol"; -import "./fuse/IConsensus.sol"; - -/** - - The purpose of this contract is to store on Fuse block headers - from different blockchains signed by the Fuse validators. - -**/ -contract BlockHeaderRegistry { - // To prevent double signatures - mapping(bytes32 => mapping(address => bool)) public hasValidatorSigned; - - // Block hashes per block number for blockchain - mapping(uint256 => mapping(uint256 => bytes32[])) public blockHashes; - - // Validator signatures per blockHash - mapping(bytes32 => SignedBlock) public signedBlocks; - - // Block header for blockHash - mapping(bytes32 => BlockHeader) blockHeaders; - - mapping(uint256 => uint256) public blockchains; - - struct Rpc { - uint256 chainId; - string rpc; - } - - Rpc[] public rpcs; - - address immutable consensus; - - constructor(address _consensus) { - consensus = _consensus; - } - - function getRpcs() public view returns (Rpc[] memory _rpcs) { - _rpcs = rpcs; - } - - modifier onlyVoting() { - (bool success, bytes memory result) = consensus.staticcall(abi.encodeWithSignature("getVoting()")); - require(success, "Failed call to getVoting()"); - address voting = abi.decode(result, (address)); - require(msg.sender == voting, "onlyVoting"); - _; - } - - function addBlockchain(uint256 blockchainId, string memory rpc) - external - onlyVoting - { - uint256 index = blockchains[blockchainId] - 1; - if (index == type(uint256).max) { - require(bytes(rpc).length != 0, "Empty rpc"); - rpcs.push(Rpc(blockchainId, rpc)); - blockchains[blockchainId] = rpcs.length; - } - else { - rpcs[index].rpc = rpc; - } - } - - modifier onlyValidator() { - require(_isValidator(msg.sender), "onlyValidator"); - _; - } - - /** - @notice Add a signed block from any blockchain. - @notice Costs slightly more if the block has never been registered before. - @notice Processes fuse blocks slightly differently. - @param blocks List of block headers and signatures to add. - */ - function addSignedBlocks(Block[] calldata blocks) external onlyValidator { - for (uint256 i = 0; i < blocks.length; i++) { - Block calldata _block = blocks[i]; - bytes32 rlpHeaderHash = keccak256(_block.rlpHeader); - require(rlpHeaderHash == _block.blockHash, "rlpHeaderHash"); - bool isFuse = _isFuse(_block.blockchainId); - bytes32 payload = isFuse - ? keccak256( - abi.encodePacked( - rlpHeaderHash, - _block.validators, - _block.cycleEnd - ) - ) - : rlpHeaderHash; - address signer = ECDSA.recover( - ECDSA.toEthSignedMessageHash(payload), - _block.signature.r, - _block.signature.vs - ); - require(msg.sender == signer, "msg.sender == signer"); - require(!hasValidatorSigned[payload][msg.sender], Strings.toHexString(uint256(payload), 32)); - hasValidatorSigned[payload][signer] = true; - if (_isNewBlock(payload)) { - BlockHeader memory blockHeader = parseBlock(_block.rlpHeader); - blockHeaders[payload] = blockHeader; - blockHashes[_block.blockchainId][blockHeader.number].push( - payload - ); - if (isFuse) { - signedBlocks[payload].validators = _block.validators; - signedBlocks[payload].cycleEnd = _block.cycleEnd; - } - signedBlocks[payload].creator = msg.sender; - } - signedBlocks[payload].signatures.push( - abi.encodePacked(_block.signature.r, _block.signature.vs) - ); - } - } - - function getSignedBlock(uint256 blockchainId, uint256 number) - public - view - returns ( - bytes32 blockHash, - BlockHeader memory blockHeader, - SignedBlock memory signedBlock - ) - { - bytes32[] memory _blockHashes = blockHashes[blockchainId][number]; - require(_blockHashes.length != 0, "_blockHashes.length"); - blockHash = _blockHashes[0]; - uint256 _signatures = signedBlocks[blockHash].signatures.length; - for (uint256 i = 1; i < _blockHashes.length; i++) { - uint256 _sigs = signedBlocks[_blockHashes[i]].signatures.length; - if (_sigs > _signatures) { - _signatures = _sigs; - blockHash = _blockHashes[i]; - } - } - SignedBlock storage _block = signedBlocks[blockHash]; - signedBlock.signatures = _block.signatures; - signedBlock.creator = _block.creator; - if (_isFuse(blockchainId)) { - signedBlock.validators = _block.validators; - signedBlock.cycleEnd = _block.cycleEnd; - } - blockHeader = blockHeaders[blockHash]; - } - - function _isValidator(address person) internal virtual returns (bool) { - return IConsensus(consensus).isValidator(person); - } - - function _isNewBlock(bytes32 key) private view returns (bool) { - return signedBlocks[key].signatures.length == 0; - } - - function _isFuse(uint256 blockchainId) - internal - view - virtual - returns (bool) - { - return blockchainId == 0x7a; - } -} diff --git a/contracts/block-header-registry/ECDSA.sol b/contracts/block-header-registry/ECDSA.sol deleted file mode 100644 index ca70913..0000000 --- a/contracts/block-header-registry/ECDSA.sol +++ /dev/null @@ -1,232 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.5.0) (utils/cryptography/ECDSA.sol) - -pragma solidity ^0.8.0; - -import "./Strings.sol"; - -/** - * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations. - * - * These functions can be used to verify that a message was signed by the holder - * of the private keys of a given address. - */ -library ECDSA { - enum RecoverError { - NoError, - InvalidSignature, - InvalidSignatureLength, - InvalidSignatureS, - InvalidSignatureV - } - - function _throwError(RecoverError error) private pure { - if (error == RecoverError.NoError) { - return; // no error: do nothing - } else if (error == RecoverError.InvalidSignature) { - revert("ECDSA: invalid signature"); - } else if (error == RecoverError.InvalidSignatureLength) { - revert("ECDSA: invalid signature length"); - } else if (error == RecoverError.InvalidSignatureS) { - revert("ECDSA: invalid signature 's' value"); - } else if (error == RecoverError.InvalidSignatureV) { - revert("ECDSA: invalid signature 'v' value"); - } - } - - /** - * @dev Returns the address that signed a hashed message (`hash`) with - * `signature` or error string. This address can then be used for verification purposes. - * - * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: - * this function rejects them by requiring the `s` value to be in the lower - * half order, and the `v` value to be either 27 or 28. - * - * IMPORTANT: `hash` _must_ be the result of a hash operation for the - * verification to be secure: it is possible to craft signatures that - * recover to arbitrary addresses for non-hashed data. A safe way to ensure - * this is by receiving a hash of the original message (which may otherwise - * be too long), and then calling {toEthSignedMessageHash} on it. - * - * Documentation for signature generation: - * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js] - * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers] - * - * _Available since v4.3._ - */ - function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) { - // Check the signature length - // - case 65: r,s,v signature (standard) - // - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._ - if (signature.length == 65) { - bytes32 r; - bytes32 s; - uint8 v; - // ecrecover takes the signature parameters, and the only way to get them - // currently is to use assembly. - /// @solidity memory-safe-assembly - assembly { - r := mload(add(signature, 0x20)) - s := mload(add(signature, 0x40)) - v := byte(0, mload(add(signature, 0x60))) - } - return tryRecover(hash, v, r, s); - } else if (signature.length == 64) { - bytes32 r; - bytes32 vs; - // ecrecover takes the signature parameters, and the only way to get them - // currently is to use assembly. - /// @solidity memory-safe-assembly - assembly { - r := mload(add(signature, 0x20)) - vs := mload(add(signature, 0x40)) - } - return tryRecover(hash, r, vs); - } else { - return (address(0), RecoverError.InvalidSignatureLength); - } - } - - /** - * @dev Returns the address that signed a hashed message (`hash`) with - * `signature`. This address can then be used for verification purposes. - * - * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: - * this function rejects them by requiring the `s` value to be in the lower - * half order, and the `v` value to be either 27 or 28. - * - * IMPORTANT: `hash` _must_ be the result of a hash operation for the - * verification to be secure: it is possible to craft signatures that - * recover to arbitrary addresses for non-hashed data. A safe way to ensure - * this is by receiving a hash of the original message (which may otherwise - * be too long), and then calling {toEthSignedMessageHash} on it. - */ - function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { - (address recovered, RecoverError error) = tryRecover(hash, signature); - _throwError(error); - return recovered; - } - - /** - * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately. - * - * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures] - * - * _Available since v4.3._ - */ - function tryRecover( - bytes32 hash, - bytes32 r, - bytes32 vs - ) internal pure returns (address, RecoverError) { - bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); - uint8 v = uint8((uint256(vs) >> 255) + 27); - return tryRecover(hash, v, r, s); - } - - /** - * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately. - * - * _Available since v4.2._ - */ - function recover( - bytes32 hash, - bytes32 r, - bytes32 vs - ) internal pure returns (address) { - (address recovered, RecoverError error) = tryRecover(hash, r, vs); - _throwError(error); - return recovered; - } - - /** - * @dev Overload of {ECDSA-tryRecover} that receives the `v`, - * `r` and `s` signature fields separately. - * - * _Available since v4.3._ - */ - function tryRecover( - bytes32 hash, - uint8 v, - bytes32 r, - bytes32 s - ) internal pure returns (address, RecoverError) { - // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature - // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines - // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most - // signatures from current libraries generate a unique signature with an s-value in the lower half order. - // - // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value - // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or - // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept - // these malleable signatures as well. - if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { - return (address(0), RecoverError.InvalidSignatureS); - } - if (v != 27 && v != 28) { - return (address(0), RecoverError.InvalidSignatureV); - } - - // If the signature is valid (and not malleable), return the signer address - address signer = ecrecover(hash, v, r, s); - if (signer == address(0)) { - return (address(0), RecoverError.InvalidSignature); - } - - return (signer, RecoverError.NoError); - } - - /** - * @dev Overload of {ECDSA-recover} that receives the `v`, - * `r` and `s` signature fields separately. - */ - function recover( - bytes32 hash, - uint8 v, - bytes32 r, - bytes32 s - ) internal pure returns (address) { - (address recovered, RecoverError error) = tryRecover(hash, v, r, s); - _throwError(error); - return recovered; - } - - /** - * @dev Returns an Ethereum Signed Message, created from a `hash`. This - * produces hash corresponding to the one signed with the - * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] - * JSON-RPC method as part of EIP-191. - * - * See {recover}. - */ - function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) { - // 32 is the length in bytes of hash, - // enforced by the type signature above - return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); - } - - /** - * @dev Returns an Ethereum Signed Message, created from `s`. This - * produces hash corresponding to the one signed with the - * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] - * JSON-RPC method as part of EIP-191. - * - * See {recover}. - */ - function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) { - return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(s.length), s)); - } - - /** - * @dev Returns an Ethereum Signed Typed Data, created from a - * `domainSeparator` and a `structHash`. This produces hash corresponding - * to the one signed with the - * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] - * JSON-RPC method as part of EIP-712. - * - * See {recover}. - */ - function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) { - return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); - } -} diff --git a/contracts/block-header-registry/Strings.sol b/contracts/block-header-registry/Strings.sol deleted file mode 100644 index d38bbe8..0000000 --- a/contracts/block-header-registry/Strings.sol +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (utils/Strings.sol) - -pragma solidity ^0.8.0; - -/** - * @dev String operations. - */ -library Strings { - bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef"; - - /** - * @dev Converts a `uint256` to its ASCII `string` decimal representation. - */ - function toString(uint256 value) internal pure returns (string memory) { - // Inspired by OraclizeAPI's implementation - MIT licence - // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol - - if (value == 0) { - return "0"; - } - uint256 temp = value; - uint256 digits; - while (temp != 0) { - digits++; - temp /= 10; - } - bytes memory buffer = new bytes(digits); - while (value != 0) { - digits -= 1; - buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); - value /= 10; - } - return string(buffer); - } - - /** - * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. - */ - function toHexString(uint256 value) internal pure returns (string memory) { - if (value == 0) { - return "0x00"; - } - uint256 temp = value; - uint256 length = 0; - while (temp != 0) { - length++; - temp >>= 8; - } - return toHexString(value, length); - } - - /** - * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. - */ - function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { - bytes memory buffer = new bytes(2 * length + 2); - buffer[0] = "0"; - buffer[1] = "x"; - for (uint256 i = 2 * length + 1; i > 1; --i) { - buffer[i] = _HEX_SYMBOLS[value & 0xf]; - value >>= 4; - } - require(value == 0, "Strings: hex length insufficient"); - return string(buffer); - } -} diff --git a/contracts/block-header-registry/fuse/IConsensus.sol b/contracts/block-header-registry/fuse/IConsensus.sol deleted file mode 100644 index ae1145e..0000000 --- a/contracts/block-header-registry/fuse/IConsensus.sol +++ /dev/null @@ -1,5 +0,0 @@ -pragma solidity ^0.8.0; - -interface IConsensus { - function isValidator(address) external returns (bool); -} diff --git a/contracts/block-header-registry/parseBlock.sol b/contracts/block-header-registry/parseBlock.sol deleted file mode 100644 index b428254..0000000 --- a/contracts/block-header-registry/parseBlock.sol +++ /dev/null @@ -1,219 +0,0 @@ -pragma solidity ^0.8.0; - -import {BlockHeader} from './structs.sol'; - -function parseBlock(bytes calldata rlpHeader) pure returns (BlockHeader memory header) { - assembly { - // input should be a pointer to start of a calldata slice - function decode_length(input, length) -> offset, strLen, isList { - if iszero(length) { revert(0, 1) } - - let prefix := byte(0, calldataload(input)) - - function getcd(start, len) -> val { - mstore(0, 0) - let dst := sub(32, len) - calldatacopy(dst, start, len) - val := mload(0) - mstore(0, 0) - } - - if lt(prefix, 0x80) { - offset := 0 - strLen := 1 - isList := 0 - leave - } - - if lt(prefix, 0xb8) { - if iszero(gt(length, sub(prefix, 0x80))) { revert(0, 0xff) } - strLen := sub(prefix, 0x80) - offset := 1 - isList := 0 - leave - } - - if lt(prefix, 0xc0) { - if iszero(and( - gt(length, sub(prefix, 0xb7)), - gt(length, add(sub(prefix, 0xb7), getcd(add(input, 1), sub(prefix, 0xb7)))) - )) { revert(0, 0xff) } - - let lenOfStrLen := sub(prefix, 0xb7) - strLen := getcd(add(input, 1), lenOfStrLen) - offset := add(1, lenOfStrLen) - isList := 0 - leave - } - - if lt(prefix, 0xf8) { - if iszero(gt(length, sub(prefix, 0xc0))) { revert(0, 0xff) } - // listLen - strLen := sub(prefix, 0xc0) - offset := 1 - isList := 1 - leave - } - - if lt(prefix, 0x0100) { - if iszero(and( - gt(length, sub(prefix, 0xf7)), - gt(length, add(sub(prefix, 0xf7), getcd(add(input, 1), sub(prefix, 0xf7)))) - )) { revert(0, 0xff) } - - let lenOfListLen := sub(prefix, 0xf7) - // listLen - strLen := getcd(add(input, 1), lenOfListLen) - offset := add(1, lenOfListLen) - isList := 1 - leave - } - - revert(0, 2) - } - - // Initialize rlp variables with the block's list - let iptr := rlpHeader.offset - let ilen := rlpHeader.length - let offset - let len - let isList - offset,len,isList := decode_length(iptr, ilen) - - // There's only 1 list in the Ethereum block RLP encoding (the block itself) - // If the first param isn't a list, revert - switch isList - case 0 { revert(0, 3) } - - // The returned offset + length refer to the list's payload - // We pass those values to begin extracting block properties - iptr := add(iptr, offset) - ilen := len - - // bytes32 parentHash; - offset,len,isList := decode_length(iptr, ilen) - if isList { revert(0, 4) } - calldatacopy(add(header, sub(0x20, len)), add(iptr, offset), len) - iptr := add(iptr, add(len, offset)) - ilen := sub(ilen, len) - - // bytes32 uncleHash; - offset,len,isList := decode_length(iptr, ilen) - if isList { revert(0, 4) } - calldatacopy(add(add(header, 0x20), sub(0x20, len)), add(iptr, offset), len) - iptr := add(iptr, add(len, offset)) - ilen := sub(ilen, len) - - // address coinbase; - offset,len,isList := decode_length(iptr, ilen) - if isList { revert(0, 4) } - calldatacopy(add(add(header, 0x40), sub(0x20, len)), add(iptr, offset), len) - iptr := add(iptr, add(len, offset)) - ilen := sub(ilen, len) - - // bytes32 root; - offset,len,isList := decode_length(iptr, ilen) - if isList { revert(0, 4) } - calldatacopy(add(add(header, 0x60), sub(0x20, len)), add(iptr, offset), len) - iptr := add(iptr, add(len, offset)) - ilen := sub(ilen, len) - - // bytes32 txHash; - offset,len,isList := decode_length(iptr, ilen) - if isList { revert(0, 4) } - calldatacopy(add(add(header, 0x80), sub(0x20, len)), add(iptr, offset), len) - iptr := add(iptr, add(len, offset)) - ilen := sub(ilen, len) - - // bytes32 receiptHash; - offset,len,isList := decode_length(iptr, ilen) - if isList { revert(0, 4) } - calldatacopy(add(add(header, 0xa0), sub(0x20, len)), add(iptr, offset), len) - iptr := add(iptr, add(len, offset)) - ilen := sub(ilen, len) - - // bytes32[8] bloom; - offset,len,isList := decode_length(iptr, ilen) - if isList { revert(0, 4) } - calldatacopy(mload(add(header, 0xc0)), add(iptr, offset), len) - iptr := add(iptr, add(len, offset)) - ilen := sub(ilen, len) - - // uint256 difficulty; - offset,len,isList := decode_length(iptr, ilen) - if isList { revert(0, 4) } - calldatacopy(add(add(header, 0xe0), sub(0x20, len)), add(iptr, offset), len) - iptr := add(iptr, add(len, offset)) - ilen := sub(ilen, len) - -// function write(iptr, len, dst_ptr, base_len) { -// calldatacopy(add(dst_ptr, sub(base_len, len)), iptr, len) -// } - - // uint256 number; - offset,len,isList := decode_length(iptr, ilen) - if isList { revert(0, 4) } - calldatacopy(add(add(header, 0x100), sub(0x20, len)), add(iptr, offset), len) - iptr := add(iptr, add(len, offset)) - ilen := sub(ilen, len) - - // uint256 gasLimit; - offset,len,isList := decode_length(iptr, ilen) - if isList { revert(0, 4) } - calldatacopy(add(add(header, 0x120), sub(0x20, len)), add(iptr, offset), len) - iptr := add(iptr, add(len, offset)) - ilen := sub(ilen, len) - - // uint256 gasUsed; - offset,len,isList := decode_length(iptr, ilen) - if isList { revert(0, 4) } - calldatacopy(add(add(header, 0x140), sub(0x20, len)), add(iptr, offset), len) - iptr := add(iptr, add(len, offset)) - ilen := sub(ilen, len) - - // uint256 time; - offset,len,isList := decode_length(iptr, ilen) - if isList { revert(0, 4) } - calldatacopy(add(add(header, 0x160), sub(0x20, len)), add(iptr, offset), len) - iptr := add(iptr, add(len, offset)) - ilen := sub(ilen, len) - - // bytes extra; - offset,len,isList := decode_length(iptr, ilen) - if isList { revert(0, 4) } - let free := mload(0x40) - mstore(add(header, 0x1e0), free) - mstore(free, len) - mstore(0x40, add(free, add(0x20, len))) - calldatacopy(add(free, 0x20), add(iptr, offset), len) - iptr := add(iptr, add(len, offset)) - ilen := sub(ilen, len) - - // bytes32 mixDigest; - offset,len,isList := decode_length(iptr, ilen) - if isList { revert(0, 4) } - calldatacopy(add(add(header, 0x180), sub(0x20, len)), add(iptr, offset), len) - iptr := add(iptr, add(len, offset)) - ilen := sub(ilen, len) - - // uint64 nonce; - offset,len,isList := decode_length(iptr, ilen) - if isList { revert(0, 4) } - calldatacopy(add(add(header, 0x1a0), sub(0x20, len)), add(iptr, offset), len) - iptr := add(iptr, add(len, offset)) - ilen := sub(ilen, len) - - // uint256 baseFee; - // This might not exist on some chains and legacy blocks - switch gt(iptr, add(rlpHeader.length, rlpHeader.offset)) - case 0 { - offset,len,isList := decode_length(iptr, ilen) - if isList { revert(0, 4) } - calldatacopy(add(add(header, 0x1c0), sub(0x20, len)), add(iptr, offset), len) - iptr := add(iptr, add(len, offset)) - ilen := sub(ilen, len) - } - } -} - - diff --git a/contracts/block-header-registry/structs.sol b/contracts/block-header-registry/structs.sol deleted file mode 100644 index 7c6122e..0000000 --- a/contracts/block-header-registry/structs.sol +++ /dev/null @@ -1,42 +0,0 @@ -pragma solidity ^0.8.0; - -struct Signature { - bytes32 r; - bytes32 vs; -} - -struct SignedBlock { - address creator; - bytes[] signatures; - // Just for fuse - uint256 cycleEnd; - address[] validators; -} - -struct BlockHeader { - bytes32 parentHash; - bytes32 uncleHash; - address coinbase; - bytes32 root; - bytes32 txHash; - bytes32 receiptHash; - bytes32[8] bloom; - uint256 difficulty; - uint256 number; - uint256 gasLimit; - uint256 gasUsed; - uint256 time; - bytes32 mixDigest; - uint256 nonce; - uint256 baseFee; - bytes extra; -} - -struct Block { - bytes rlpHeader; - Signature signature; - uint256 blockchainId; - bytes32 blockHash; - uint256 cycleEnd; - address[] validators; -} diff --git a/contracts/block-header-registry/test/BlockHeaderRegistryMock.sol b/contracts/block-header-registry/test/BlockHeaderRegistryMock.sol deleted file mode 100644 index e1fab5f..0000000 --- a/contracts/block-header-registry/test/BlockHeaderRegistryMock.sol +++ /dev/null @@ -1,7 +0,0 @@ -pragma solidity ^0.8.0; - -import "../BlockHeaderRegistry.sol"; -import "./VotingMock.sol"; -import "./ConsensusMock.sol"; - -//contract BlockHeaderRegistryMock is BlockHeaderRegistry {} diff --git a/contracts/block-header-registry/test/ConsensusMock.sol b/contracts/block-header-registry/test/ConsensusMock.sol deleted file mode 100644 index 953b1d5..0000000 --- a/contracts/block-header-registry/test/ConsensusMock.sol +++ /dev/null @@ -1,12 +0,0 @@ -pragma solidity ^0.8.0; - -import "../fuse/IConsensus.sol"; - -contract ConsensusMock is IConsensus { - mapping(address => bool) public override isValidator; - - constructor(address[] memory signers) { - for (uint8 i = 0; i < signers.length; i++) - isValidator[signers[i]] = true; - } -} diff --git a/contracts/block-header-registry/test/VotingMock.sol b/contracts/block-header-registry/test/VotingMock.sol deleted file mode 100644 index 98c4760..0000000 --- a/contracts/block-header-registry/test/VotingMock.sol +++ /dev/null @@ -1,15 +0,0 @@ -pragma solidity ^0.8.0; - -interface IBlockHeaderRegistry { - function addBlockchain(uint256, string memory) external; -} - -contract VotingMock { - function addBlockchain( - address registry, - uint256 blockchainid, - string memory rpc - ) external { - IBlockHeaderRegistry(registry).addBlockchain(blockchainid, rpc); - } -} From 7f81d053151638cba7ae9c2d5919e733ce761b9b Mon Sep 17 00:00:00 2001 From: "Parker M. Lambert" <> Date: Thu, 12 May 2022 08:57:45 -0400 Subject: [PATCH 13/13] Export BlockRegistry functinos to separate file --- app/blockHeaderRegistry.js | 138 +++++++++++++++++++++++++++++++++++++ app/index.js | 88 +++++------------------ 2 files changed, 154 insertions(+), 72 deletions(-) create mode 100644 app/blockHeaderRegistry.js diff --git a/app/blockHeaderRegistry.js b/app/blockHeaderRegistry.js new file mode 100644 index 0000000..106410c --- /dev/null +++ b/app/blockHeaderRegistry.js @@ -0,0 +1,138 @@ +const path = require("path"); +const logger = require("pino")({ + level: process.env.LOG_LEVEL || "info", + prettyPrint: { translateTime: true }, +}); +const Web3 = require("web3"); +const ethers = require("ethers"); +const { sign, signFuse } = require("./utils"); + +const blockchains = {}; + +function initBlockchain({ + consensus, + blockRegistry, + signer, + walletProvider, + 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(); + blockchains[chainId].blocks[block.hash] = + await signFuse( + block, + chainId, + blockchain.rpc, + 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 emitRegistry({ + consensus, + blockRegistry, + walletProvider, + signer, + web3, +}) { + try { + logger.info("emitRegistry"); + const currentBlock = (await web3.eth.getBlockNumber()).toNumber(); + const numRpcs = ( + await blockRegistry.methods.getRpcsLength().call() + ).toNumber(); + const chains = await Promise.all( + new Array(numRpcs).map( + async (_, i) => await blockRegistry.methods.rpcs(i) + ) + ); + await Promise.all( + chains + .filter( + (chain) => + !blockchains[chain[0]] || + blockchains[chain[0]].rpc != chain[1] + ) + .map(async (chain) => + initBlockchain({ + consensus, + blockRegistry, + signer, + walletProvider, + chainId: chain[0], + rpc: chain[1], + }) + ) + ); + 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`; + } + 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) + ); + } + } +} + +module.exports = { initBlockchain, emitRegistry, blockchains } diff --git a/app/index.js b/app/index.js index 8f28c21..2ec9932 100644 --- a/app/index.js +++ b/app/index.js @@ -5,18 +5,15 @@ 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 { emitRegistry } = require('./block-header-registry') 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 walletProvider, signer let account let consensus, blockReward, blockRegistry -let blockchains = {} function initWalletProvider() { logger.info(`initWalletProvider`) @@ -37,35 +34,7 @@ function initWalletProvider() { account = walletProvider.addresses[0] logger.info(`account: ${account}`) 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()}` + signer = new ethers.Wallet(pkey) } } @@ -89,12 +58,15 @@ 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")) - initBlockchain(122, process.env.FUSE_RPC || 'https://rpc.fuse.io/') + if (!ETH_RPC) throw "Missing ETH_RPC in environment" + if (!BSC_RPC) throw "Missing BSC_RPC in environment" + initBlockchain(1, ETH_RPC) + initBlockchain(56, BSC_RPC) + initBlockchain(122, FUSE_RPC || 'https://rpc.fuse.io/') } function emitInitiateChange() { @@ -161,40 +133,6 @@ function emitRewardedOnCycle() { }) } -async function emitRegistry() { - try { - logger.info('emitRegistry') - const currentBlock = (await web3.eth.getBlockNumber()).toNumber() - const chains = await blockRegistry.methods.getRpcs.call() - await Promise.all(chains.filter(chain => !blockchains[chain[0]] || blockchains[chain[0]].rpc != chain[1]).map(async (chain) => initBlockchain(...chain))) - 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` - } - 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`) @@ -217,7 +155,13 @@ async function runMain() { } await emitInitiateChange() await emitRewardedOnCycle() - await emitRegistry() + await emitRegistry({ + web3, + consensus, + blockRegistry, + signer, + walletProvider + }) } catch (e) { logger.error(e) process.exit(1)