Skip to content
Open
5 changes: 4 additions & 1 deletion app/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ ENV LOG_LEVEL=debug
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

CMD ["node", "index.js"]
CMD ["node", "index.js"]
1 change: 1 addition & 0 deletions app/abi/blockRegistry.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"inputs":[{"internalType":"address","name":"_consensus","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"blockchainId","type":"uint256"},{"internalType":"string","name":"rpc","type":"string"}],"name":"addBlockchain","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes","name":"rlpHeader","type":"bytes"},{"components":[{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"vs","type":"bytes32"}],"internalType":"struct Signature","name":"signature","type":"tuple"},{"internalType":"uint256","name":"blockchainId","type":"uint256"},{"internalType":"bytes32","name":"blockHash","type":"bytes32"},{"internalType":"uint256","name":"cycleEnd","type":"uint256"},{"internalType":"address[]","name":"validators","type":"address[]"}],"internalType":"struct Block[]","name":"blocks","type":"tuple[]"}],"name":"addSignedBlocks","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"blockHashes","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"blockchains","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getRpcs","outputs":[{"components":[{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"string","name":"rpc","type":"string"}],"internalType":"struct BlockHeaderRegistry.Rpc[]","name":"_rpcs","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"blockchainId","type":"uint256"},{"internalType":"uint256","name":"number","type":"uint256"}],"name":"getSignedBlock","outputs":[{"internalType":"bytes32","name":"blockHash","type":"bytes32"},{"components":[{"internalType":"bytes32","name":"parentHash","type":"bytes32"},{"internalType":"bytes32","name":"uncleHash","type":"bytes32"},{"internalType":"address","name":"coinbase","type":"address"},{"internalType":"bytes32","name":"root","type":"bytes32"},{"internalType":"bytes32","name":"txHash","type":"bytes32"},{"internalType":"bytes32","name":"receiptHash","type":"bytes32"},{"internalType":"bytes32[8]","name":"bloom","type":"bytes32[8]"},{"internalType":"uint256","name":"difficulty","type":"uint256"},{"internalType":"uint256","name":"number","type":"uint256"},{"internalType":"uint256","name":"gasLimit","type":"uint256"},{"internalType":"uint256","name":"gasUsed","type":"uint256"},{"internalType":"uint256","name":"time","type":"uint256"},{"internalType":"bytes32","name":"mixDigest","type":"bytes32"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"baseFee","type":"uint256"},{"internalType":"bytes","name":"extra","type":"bytes"}],"internalType":"struct BlockHeader","name":"blockHeader","type":"tuple"},{"components":[{"internalType":"address","name":"creator","type":"address"},{"internalType":"bytes[]","name":"signatures","type":"bytes[]"},{"internalType":"uint256","name":"cycleEnd","type":"uint256"},{"internalType":"address[]","name":"validators","type":"address[]"}],"internalType":"struct SignedBlock","name":"signedBlock","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"}],"name":"hasValidatorSigned","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"rpcs","outputs":[{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"string","name":"rpc","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"signedBlocks","outputs":[{"internalType":"address","name":"creator","type":"address"},{"internalType":"uint256","name":"cycleEnd","type":"uint256"}],"stateMutability":"view","type":"function"}]
138 changes: 138 additions & 0 deletions app/blockHeaderRegistry.js
Original file line number Diff line number Diff line change
@@ -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 }
29 changes: 26 additions & 3 deletions app/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ const fs = require('fs')
const HDWalletProvider = require('truffle-hdwallet-provider')
const EthWallet = require('ethereumjs-wallet')
const Web3 = require('web3')

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
let consensus, blockReward, blockRegistry

function initWalletProvider() {
logger.info(`initWalletProvider`)
Expand All @@ -32,6 +34,7 @@ function initWalletProvider() {
account = walletProvider.addresses[0]
logger.info(`account: ${account}`)
web3 = new Web3(walletProvider)
signer = new ethers.Wallet(pkey)
}
}

Expand All @@ -56,6 +59,16 @@ function initBlockRewardContract() {
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)
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() {
return new Promise(async (resolve, reject) => {
try {
Expand Down Expand Up @@ -132,13 +145,23 @@ async function runMain() {
if (!blockReward) {
initBlockRewardContract()
}
if (!blockRegistry) {
initBlockRegistryContract()
}
const isValidator = await consensus.methods.isValidator(web3.utils.toChecksumAddress(account)).call()
if (!isValidator) {
logger.warn(`${account} is not a validator, skipping`)
return
}
await emitInitiateChange()
await emitRewardedOnCycle()
await emitRegistry({
web3,
consensus,
blockRegistry,
signer,
walletProvider
})
} catch (e) {
logger.error(e)
process.exit(1)
Expand Down
5 changes: 4 additions & 1 deletion app/scripts/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@ POLLING_INTERVAL=5000 \
RPC=http://127.0.0.1:8545 \
CONSENSUS_ADDRESS=0x3014ca10b91cb3D0AD85fEf7A3Cb95BCAc9c0f79 \
BLOCK_REWARD_ADDRESS=0x63D4efeD2e3dA070247bea3073BCaB896dFF6C9B \
node index.js
BLOCK_REGISTRY_ADDRESS=0xa7BfeDBf11a488EcA3838F03A93d8d96EAba9a02 \
ETH_RPC="" \
BSC_RPC="" \
node index.js
29 changes: 29 additions & 0 deletions app/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const ethers = require('ethers')

export async function sign(header, chainId, signer) {
const rlpHeader = hashHeader(header)
const payload = ethers.utils.keccak256(rlpHeader);
const { _vs: vs, r } = ethers.utils.splitSignature(
await signer.signMessage(ethers.utils.arrayify(payload))
);
return [rlpHeader,[vs,r],chainId,payload,0,[]]
}
export function hashHeader(web3Header) {
const rlpHeader = ethers.utils.RLP.encode(
Object.values(header).map((v) => (v === 0 ? "0x" : v))
);
return rlpHeader
}

export async function signFuse(header, chainId, signer, cycleEnd, validators) {
const rlpHeader = hashHeader(header)
const packed = ethers.utils.solidityPack(
["bytes32", "address[]", "uint256"],
[blockHash, validators, cycleEnd]
);
const payload = ethers.utils.keccak256(packed);
const { _vs: vs, r } = ethers.utils.splitSignature(
await signer.signMessage(ethers.utils.arrayify(payload))
);
return [rlpHeader,[vs,r],chainId,payload,cycleEnd,validators]
}