Commit 757ccee1 authored by Georgios Konstantopoulos's avatar Georgios Konstantopoulos Committed by GitHub

feat: import the message relayer service (#36)

* feat: import the message relayer service

https://github.com/ethereum-optimism/optimism-ts-services/blob/1c6f8ad07afadc37ace76c9c63635d9b5132f6bd/src/services/message-relayer.service.ts

* chore: clean up unused types

* fix: do not try to run relayer unit tests

* ops(message-relayer): add docker image and compose settings

* test: add L1 <> L2 communication

* ci: parallelize building steps

* ci: try to resolve hardhat race condition
parent 6f0ae0a7
......@@ -15,18 +15,6 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Build the base layer dependencies
working-directory: ./ops
run: docker-compose build --parallel -- builder l2geth l1_chain
- name: Build the other services
working-directory: ./ops
run: docker-compose build --parallel -- deployer dtl batch_submitter
- name: Bring the stack up and wait for the sequencer to be ready
working-directory: ./ops
run: docker-compose up -d && ./scripts/wait-for-sequencer.sh
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
......@@ -39,14 +27,17 @@ jobs:
restore-keys: |
${{ runner.os }}-yarn-
- name: Install integration test dependencies
working-directory: ./integration-tests
# if: steps.yarn-cache.outputs.cache-hit != 'true'
run: yarn install
- name: Build the services
working-directory: ./ops
run: ./scripts/build-ci.sh
- name: Build deps for the integration tests
run: yarn build
- name: Bring the stack up and wait for the sequencer to be ready
working-directory: ./ops
run: docker-compose up -d && ./scripts/wait-for-sequencer.sh
- name: Run the integration tests
working-directory: ./integration-tests
run: yarn test:integration
run: |
yarn build:contracts
yarn build:contracts:ovm
yarn test:integration
pragma solidity >=0.7.0;
contract ICrossDomainMessenger {
address public xDomainMessageSender;
}
contract SimpleStorage {
address public msgSender;
address public xDomainSender;
bytes32 public value;
uint256 public totalCount;
function setValue(bytes32 newValue) public {
msgSender = msg.sender;
xDomainSender = ICrossDomainMessenger(msg.sender)
.xDomainMessageSender();
value = newValue;
totalCount++;
}
function dumbSetValue(bytes32 newValue) public {
value = newValue;
}
}
{
"name": "@eth-optimism/sequencer-interactions",
"name": "@eth-optimism/integration-tests",
"version": "0.0.1",
"description": "[Optimism] Integration Tests: Sequencer Interactions",
"description": "[Optimism] Integration Tests",
"author": "Optimism PBC",
"license": "MIT",
"scripts": {
"lint": "yarn lint:fix && yarn lint:check",
"lint:check": "tslint --format stylish --project .",
"lint:fix": "prettier --config ./prettier-config.json --write 'test/**/*.ts'",
"test:integration": "hardhat --network optimism test"
"build:integration": "./scripts/build.sh",
"build:contracts": "hardhat compile",
"build:contracts:ovm": "hardhat compile --network optimism",
"test:integration": "hardhat --network optimism test",
"clean": "rimraf cache artifacts artifacts-ovm cache-ovm"
},
"devDependencies": {
"@eth-optimism/contracts": "^0.1.11",
......@@ -21,6 +25,7 @@
"ethers": "^5.0.32",
"hardhat": "^2.1.2",
"lodash": "^4.17.21",
"mocha": "^8.3.1"
"mocha": "^8.3.1",
"rimraf": "^3.0.2"
}
}
yarn build:contracts &
yarn build:contracts:ovm &
wait
import { expect } from 'chai'
/* Imports: External */
import { Contract, ContractFactory, Wallet, providers } from 'ethers'
import { Watcher } from '@eth-optimism/core-utils'
import { getContractFactory } from '@eth-optimism/contracts'
/* Imports: Internal */
import l1SimpleStorageJson from '../artifacts/contracts/SimpleStorage.sol/SimpleStorage.json'
import l2SimpleStorageJson from '../artifacts-ovm/contracts/SimpleStorage.sol/SimpleStorage.json'
describe('Basic L1<>L2 Communication', async () => {
let l1Wallet: Wallet
let l2Wallet: Wallet
let l1Provider: providers.JsonRpcProvider
let l2Provider: providers.JsonRpcProvider
let AddressManager: Contract
let Factory__L1SimpleStorage: ContractFactory
let Factory__L2SimpleStorage: ContractFactory
let L1CrossDomainMessenger: Contract
let L2CrossDomainMessenger: Contract
let watcher: Watcher
let L1SimpleStorage: Contract
let L2SimpleStorage: Contract
before(async () => {
const httpPort = 8545
const l1HttpPort = 9545
l1Provider = new providers.JsonRpcProvider(`http://localhost:${l1HttpPort}`)
l2Provider = new providers.JsonRpcProvider(`http://localhost:${httpPort}`)
l1Wallet = new Wallet(
'0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',
l1Provider
)
l2Wallet = Wallet.createRandom().connect(l2Provider)
const addressManagerAddress = '0x5FbDB2315678afecb367f032d93F642f64180aa3'
AddressManager = getContractFactory('Lib_AddressManager')
.connect(l1Wallet)
.attach(addressManagerAddress)
Factory__L1SimpleStorage = new ContractFactory(
l1SimpleStorageJson.abi,
l1SimpleStorageJson.bytecode,
l1Wallet
)
Factory__L2SimpleStorage = new ContractFactory(
l2SimpleStorageJson.abi,
l2SimpleStorageJson.bytecode,
l2Wallet
)
const l1MessengerAddress = await AddressManager.getAddress(
'Proxy__OVM_L1CrossDomainMessenger'
)
const l2MessengerAddress = await AddressManager.getAddress(
'OVM_L2CrossDomainMessenger'
)
L1CrossDomainMessenger = getContractFactory('iOVM_L1CrossDomainMessenger')
.connect(l1Wallet)
.attach(l1MessengerAddress)
L2CrossDomainMessenger = getContractFactory('iOVM_L2CrossDomainMessenger')
.connect(l2Wallet)
.attach(l2MessengerAddress)
watcher = new Watcher({
l1: {
provider: l1Provider,
messengerAddress: L1CrossDomainMessenger.address,
},
l2: {
provider: l2Provider,
messengerAddress: L2CrossDomainMessenger.address,
},
})
})
beforeEach(async () => {
L1SimpleStorage = await Factory__L1SimpleStorage.deploy()
await L1SimpleStorage.deployTransaction.wait()
L2SimpleStorage = await Factory__L2SimpleStorage.deploy()
await L2SimpleStorage.deployTransaction.wait()
})
it('should withdraw from L2 -> L1', async () => {
const value = `0x${'77'.repeat(32)}`
// Send L2 -> L1 message.
const transaction = await L2CrossDomainMessenger.sendMessage(
L1SimpleStorage.address,
L1SimpleStorage.interface.encodeFunctionData('setValue', [value]),
5000000,
{ gasLimit: 7000000 }
)
// Wait for the L2 transaction to be mined.
await transaction.wait()
// Wait for the transaction to be relayed on L1.
const messageHashes = await watcher.getMessageHashesFromL2Tx(
transaction.hash
)
await watcher.getL1TransactionReceipt(messageHashes[0])
expect(await L1SimpleStorage.msgSender()).to.equal(
L1CrossDomainMessenger.address
)
expect(await L1SimpleStorage.xDomainSender()).to.equal(l2Wallet.address)
expect(await L1SimpleStorage.value()).to.equal(value)
expect((await L1SimpleStorage.totalCount()).toNumber()).to.equal(1)
})
it('should deposit from L1 -> L2', async () => {
const value = `0x${'42'.repeat(32)}`
// Send L1 -> L2 message.
const transaction = await L1CrossDomainMessenger.sendMessage(
L2SimpleStorage.address,
L2SimpleStorage.interface.encodeFunctionData('setValue', [value]),
5000000,
{ gasLimit: 7000000 }
)
// Wait for the L1 transaction to be mined.
await transaction.wait()
// Wait for the transaction to be included on L2.
const messageHashes = await watcher.getMessageHashesFromL1Tx(
transaction.hash
)
await watcher.getL2TransactionReceipt(messageHashes[0])
expect(await L2SimpleStorage.msgSender()).to.equal(
L2CrossDomainMessenger.address
)
expect(await L2SimpleStorage.xDomainSender()).to.equal(l1Wallet.address)
expect(await L2SimpleStorage.value()).to.equal(value)
expect((await L2SimpleStorage.totalCount()).toNumber()).to.equal(1)
})
})
......@@ -92,3 +92,17 @@ services:
ports:
- ${L2GETH_HTTP_PORT:-8545}:8545
- ${L2GETH_WS_PORT:-8546}:8546
relayer:
image: ethereumoptimism:relayer
build:
context: ..
dockerfile: ./ops/docker/Dockerfile.relayer
entrypoint: ./relayer.sh
environment:
L1_NODE_WEB3_URL: http://l1_chain:8545
L2_NODE_WEB3_URL: http://l2geth:8545
URL: http://deployer:8081/addresses.json
# a funded hardhat account
L1_WALLET_KEY: "0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97"
RETRIES: 60
......@@ -30,6 +30,7 @@ COPY packages/smock/package.json ./packages/smock/package.json
COPY packages/contracts/package.json ./packages/contracts/package.json
COPY packages/data-transport-layer/package.json ./packages/data-transport-layer/package.json
COPY packages/batch-submitter/package.json ./packages/batch-submitter/package.json
COPY packages/message-relayer/package.json ./packages/message-relayer/package.json
RUN yarn install --frozen-lockfile
......
FROM ethereumoptimism:builder AS builder
FROM node:14-alpine
RUN apk add --no-cache curl bash jq
WORKDIR /opt/optimism
# copy top level files
COPY --from=builder /optimism/*.json ./
COPY --from=builder /optimism/yarn.lock .
COPY --from=builder /optimism/node_modules ./node_modules
# copy deps (would have been nice if docker followed the symlinks required)
COPY --from=builder /optimism/packages/core-utils/package.json ./packages/core-utils/package.json
COPY --from=builder /optimism/packages/core-utils/dist ./packages/core-utils/dist
COPY --from=builder /optimism/packages/contracts/package.json ./packages/contracts/package.json
COPY --from=builder /optimism/packages/contracts/dist ./packages/contracts/dist
COPY --from=builder /optimism/packages/contracts/artifacts ./packages/contracts/artifacts
COPY --from=builder /optimism/packages/contracts/artifacts-ovm ./packages/contracts/artifacts-ovm
# copy the service
WORKDIR /opt/optimism/packages/message-relayer
COPY --from=builder /optimism/packages/message-relayer/dist ./dist
COPY --from=builder /optimism/packages/message-relayer/package.json .
COPY --from=builder /optimism/packages/message-relayer/exec ./exec
COPY --from=builder /optimism/packages/message-relayer/node_modules ./node_modules
# copy this over in case you want to run alongside other services
COPY ./ops/scripts/relayer.sh .
ENTRYPOINT ["node", "exec/run-message-relayer.js"]
......@@ -5,6 +5,7 @@ DEBUG=info*,error*,warn*,debug*
MAX_L1_TX_SIZE=90000
MIN_L1_TX_SIZE=0
MAX_TX_BATCH_COUNT=50
MAX_STATE_BATCH_COUNT=50
POLL_INTERVAL=15000
NUM_CONFIRMATIONS=0
......
# build in 2 steps
function build_images() {
docker-compose build --parallel -- builder l2geth l1_chain
docker-compose build --parallel -- deployer dtl batch_submitter relayer
}
function build_dependencies() {
yarn
yarn build
}
build_images &
build_dependencies &
wait
#!/bin/bash
RETRIES=${RETRIES:-20}
# get the addrs from the URL provided
ADDRESSES=$(curl --retry-connrefused --retry $RETRIES --retry-delay 3 $URL)
# set the env
export ADDRESS_MANAGER_ADDRESS=$(echo $ADDRESSES | jq -r '.AddressManager')
# waits for l2geth to be up
curl --retry-connrefused --retry $RETRIES --retry-delay 1 $L2_NODE_WEB3_URL
# go
node ./exec/run-message-relayer.js
import * as path from 'path'
import * as glob from 'glob'
import { ethers, ContractFactory, Signer } from 'ethers'
import {
ethers,
ContractFactory,
Signer,
providers,
Contract,
constants,
} from 'ethers'
import { Interface } from 'ethers/lib/utils'
export const getContractDefinition = (name: string, ovm?: boolean): any => {
......@@ -33,3 +40,29 @@ export const getContractFactory = (
const contractInterface = getContractInterface(name, ovm)
return new ContractFactory(contractInterface, definition.bytecode, signer)
}
export const loadContract = (
name: string,
address: string,
provider: providers.JsonRpcProvider
): Contract => {
return new Contract(address, getContractInterface(name) as any, provider)
}
export const loadContractFromManager = async (args: {
name: string
proxy?: string
Lib_AddressManager: Contract
provider: providers.JsonRpcProvider
}): Promise<Contract> => {
const { name, proxy, Lib_AddressManager, provider } = args
const address = await Lib_AddressManager.getAddress(proxy ? proxy : name)
if (address === constants.AddressZero) {
throw new Error(
`Lib_AddressManager does not have a record for a contract named: ${name}`
)
}
return loadContract(name, address, provider)
}
#!/usr/bin/env node
const main = require("../dist/exec/run").default
;(async () => {
await main()
})().catch((err) => {
console.log(err)
process.exit(1)
})
{
"name": "@eth-optimism/message-relayer",
"version": "0.0.1",
"description": "[Optimism] Cross Domain Message Relayer service",
"main": "dist/index",
"types": "dist/index",
"files": [
"dist/index"
],
"scripts": {
"start": "node ./exec/run-message-relayer.js",
"build": "tsc -p ./tsconfig.build.json",
"clean": "rimraf dist/ ./tsconfig.build.tsbuildinfo",
"lint": "yarn lint:fix && yarn lint:check",
"lint:check": "tslint --format stylish --project .",
"lint:fix": "prettier --config prettier-config.json --write \"{src,exec,test}/**/*.ts\""
},
"keywords": [
"optimism",
"ethereum",
"relayer"
],
"homepage": "https://github.com/ethereum-optimism/optimism-monorepo/tree/master/packages/batch-submitter#readme",
"license": "MIT",
"author": "Optimism",
"repository": {
"type": "git",
"url": "https://github.com/ethereum-optimism/optimism.git"
},
"dependencies": {
"@eth-optimism/core-utils": "^0.1.10",
"@eth-optimism/contracts": "^0.1.11",
"dotenv": "^8.2.0",
"ethers": "^5.1.0",
"google-spreadsheet": "^3.1.15",
"merkletreejs": "^0.2.18",
"rlp": "^2.2.6"
}
}
../../prettier-config.json
\ No newline at end of file
import { Wallet, providers } from 'ethers'
import { MessageRelayerService } from '../service'
import SpreadSheet from '../spreadsheet'
import { config } from 'dotenv'
config()
const env = process.env
const L2_NODE_WEB3_URL = env.L2_NODE_WEB3_URL
const L1_NODE_WEB3_URL = env.L1_NODE_WEB3_URL
const ADDRESS_MANAGER_ADDRESS = env.ADDRESS_MANAGER_ADDRESS
const L1_WALLET_KEY = env.L1_WALLET_KEY
const MNEMONIC = env.MNEMONIC
const HD_PATH = env.HD_PATH
const RELAY_GAS_LIMIT = env.RELAY_GAS_LIMIT || '4000000'
const POLLING_INTERVAL = env.POLLING_INTERVAL || '5000'
const GET_LOGS_INTERVAL = env.GET_LOGS_INTERVAL || '2000'
const L2_BLOCK_OFFSET = env.L2_BLOCK_OFFSET || '1'
const L1_START_OFFSET = env.L1_BLOCK_OFFSET || '1'
const FROM_L2_TRANSACTION_INDEX = env.FROM_L2_TRANSACTION_INDEX || '0'
// Spreadsheet configuration
const SPREADSHEET_MODE = env.SPREADSHEET_MODE || ''
const SHEET_ID = env.SHEET_ID || ''
const CLIENT_EMAIL = env.CLIENT_EMAIL || ''
const CLIENT_PRIVATE_KEY = env.CLIENT_PRIVATE_KEY || ''
const main = async () => {
if (!ADDRESS_MANAGER_ADDRESS) {
throw new Error('Must pass ADDRESS_MANAGER_ADDRESS')
}
if (!L1_NODE_WEB3_URL) {
throw new Error('Must pass L1_NODE_WEB3_URL')
}
if (!L2_NODE_WEB3_URL) {
throw new Error('Must pass L2_NODE_WEB3_URL')
}
const l2Provider = new providers.JsonRpcProvider(L2_NODE_WEB3_URL)
const l1Provider = new providers.JsonRpcProvider(L1_NODE_WEB3_URL)
let wallet: Wallet
if (L1_WALLET_KEY) {
wallet = new Wallet(L1_WALLET_KEY, l1Provider)
} else if (MNEMONIC) {
wallet = Wallet.fromMnemonic(MNEMONIC, HD_PATH)
wallet = wallet.connect(l1Provider)
} else {
throw new Error('Must pass one of L1_WALLET_KEY or MNEMONIC')
}
let spreadsheet = null
if (SPREADSHEET_MODE) {
if (!SHEET_ID) {
throw new Error('Must pass SHEET_ID')
}
if (!CLIENT_EMAIL) {
throw new Error('Must pass CLIENT_EMAIL')
}
if (!CLIENT_PRIVATE_KEY) {
throw new Error('Must pass CLIENT_PRIVATE_KEY')
}
const privateKey = CLIENT_PRIVATE_KEY.replace(/\\n/g, '\n')
spreadsheet = new SpreadSheet(SHEET_ID)
await spreadsheet.init(CLIENT_EMAIL, privateKey)
}
const service = new MessageRelayerService({
l1RpcProvider: l1Provider,
l2RpcProvider: l2Provider,
addressManagerAddress: ADDRESS_MANAGER_ADDRESS,
l1Wallet: wallet,
relayGasLimit: parseInt(RELAY_GAS_LIMIT, 10),
fromL2TransactionIndex: parseInt(FROM_L2_TRANSACTION_INDEX, 10),
pollingInterval: parseInt(POLLING_INTERVAL, 10),
l2BlockOffset: parseInt(L2_BLOCK_OFFSET, 10),
l1StartOffset: parseInt(L1_START_OFFSET, 10),
getLogsInterval: parseInt(GET_LOGS_INTERVAL, 10),
spreadsheetMode: !!SPREADSHEET_MODE,
spreadsheet,
})
await service.start()
}
export default main
/* Imports: External */
import { Contract, ethers, Wallet, BigNumber, providers } from 'ethers'
import * as rlp from 'rlp'
import { MerkleTree } from 'merkletreejs'
/* Imports: Internal */
import { BaseService, fromHexString, sleep } from '@eth-optimism/core-utils'
import SpreadSheet from './spreadsheet'
import { loadContract, loadContractFromManager } from '@eth-optimism/contracts'
import { StateRootBatchHeader, SentMessage, SentMessageProof } from './types'
interface MessageRelayerOptions {
// Providers for interacting with L1 and L2.
l1RpcProvider: providers.JsonRpcProvider
l2RpcProvider: providers.JsonRpcProvider
// Address of the AddressManager contract, used to resolve the various addresses we'll need
// within this service.
addressManagerAddress: string
// Wallet instance, used to sign and send the L1 relay transactions.
l1Wallet: Wallet
// Max gas to relay messages with.
relayGasLimit: number
// Height of the L2 transaction to start searching for L2->L1 messages.
fromL2TransactionIndex?: number
// Interval in seconds to wait between loops.
pollingInterval?: number
// Number of blocks that L2 is "ahead" of transaction indices. Can happen if blocks are created
// on L2 after the genesis but before the first state commitment is published.
l2BlockOffset?: number
// L1 block to start querying events from. Recommended to set to the StateCommitmentChain deploy height
l1StartOffset?: number
// Number of blocks within each getLogs query - max is 2000
getLogsInterval?: number
// Append txs to a spreadsheet instead of submitting transactions
spreadsheetMode?: boolean
spreadsheet?: SpreadSheet
}
const optionSettings = {
relayGasLimit: { default: 4_000_000 },
fromL2TransactionIndex: { default: 0 },
pollingInterval: { default: 5000 },
l2BlockOffset: { default: 1 },
l1StartOffset: { default: 0 },
getLogsInterval: { default: 2000 },
spreadsheetMode: { default: false },
}
export class MessageRelayerService extends BaseService<MessageRelayerOptions> {
protected name = 'Message Relayer'
constructor(options: MessageRelayerOptions) {
super('Message Relayer', options, optionSettings)
}
protected spreadsheetMode: boolean
protected spreadsheet: SpreadSheet
private state: {
lastFinalizedTxHeight: number
nextUnfinalizedTxHeight: number
lastQueriedL1Block: number
eventCache: ethers.Event[]
Lib_AddressManager: Contract
OVM_StateCommitmentChain: Contract
OVM_L1CrossDomainMessenger: Contract
OVM_L2CrossDomainMessenger: Contract
OVM_L2ToL1MessagePasser: Contract
}
protected async _init(): Promise<void> {
this.logger.info('Initializing message relayer', { options: this.options })
// Need to improve this, sorry.
this.state = {} as any
const address = await this.options.l1Wallet.getAddress()
this.logger.info('Using L1 EOA', { address })
this.state.Lib_AddressManager = loadContract(
'Lib_AddressManager',
this.options.addressManagerAddress,
this.options.l1RpcProvider
)
this.logger.info('Connecting to OVM_StateCommitmentChain...')
this.state.OVM_StateCommitmentChain = await loadContractFromManager({
name: 'OVM_StateCommitmentChain',
Lib_AddressManager: this.state.Lib_AddressManager,
provider: this.options.l1RpcProvider,
})
this.logger.info('Connected to OVM_StateCommitmentChain', {
address: this.state.OVM_StateCommitmentChain.address,
})
this.logger.info('Connecting to OVM_L1CrossDomainMessenger...')
this.state.OVM_L1CrossDomainMessenger = await loadContractFromManager({
name: 'OVM_L1CrossDomainMessenger',
proxy: 'Proxy__OVM_L1CrossDomainMessenger',
Lib_AddressManager: this.state.Lib_AddressManager,
provider: this.options.l1RpcProvider,
})
this.logger.info('Connected to OVM_L1CrossDomainMessenger', {
address: this.state.OVM_L1CrossDomainMessenger.address,
})
this.logger.info('Connecting to OVM_L2CrossDomainMessenger...')
this.state.OVM_L2CrossDomainMessenger = await loadContractFromManager({
name: 'OVM_L2CrossDomainMessenger',
Lib_AddressManager: this.state.Lib_AddressManager,
provider: this.options.l2RpcProvider,
})
this.logger.info('Connected to OVM_L2CrossDomainMessenger', {
address: this.state.OVM_L2CrossDomainMessenger.address,
})
this.logger.info('Connecting to OVM_L2ToL1MessagePasser...')
this.state.OVM_L2ToL1MessagePasser = loadContract(
'OVM_L2ToL1MessagePasser',
'0x4200000000000000000000000000000000000000',
this.options.l2RpcProvider
)
this.logger.info('Connected to OVM_L2ToL1MessagePasser', {
address: this.state.OVM_L2ToL1MessagePasser.address,
})
this.logger.info('Connected to all contracts.')
if (this.options.spreadsheetMode) {
this.logger.info('Running in spreadsheet mode')
}
this.state.lastQueriedL1Block = this.options.l1StartOffset
this.state.eventCache = []
this.state.lastFinalizedTxHeight = this.options.fromL2TransactionIndex || 0
this.state.nextUnfinalizedTxHeight =
this.options.fromL2TransactionIndex || 0
}
protected async _start(): Promise<void> {
while (this.running) {
await sleep(this.options.pollingInterval)
try {
this.logger.info('Checking for newly finalized transactions...')
if (
!(await this._isTransactionFinalized(
this.state.nextUnfinalizedTxHeight
))
) {
this.logger.info('Did not find any newly finalized transactions', {
retryAgainInS: Math.floor(this.options.pollingInterval / 1000),
})
continue
}
this.state.lastFinalizedTxHeight = this.state.nextUnfinalizedTxHeight
while (
await this._isTransactionFinalized(this.state.nextUnfinalizedTxHeight)
) {
const size = (
await this._getStateBatchHeader(this.state.nextUnfinalizedTxHeight)
).batch.batchSize.toNumber()
this.logger.info(
'Found a batch of finalized transaction(s), checking for more...',
{ batchSize: size }
)
this.state.nextUnfinalizedTxHeight += size
}
this.logger.info('Found finalized transactions', {
totalNumber:
this.state.nextUnfinalizedTxHeight -
this.state.lastFinalizedTxHeight,
})
const messages = await this._getSentMessages(
this.state.lastFinalizedTxHeight,
this.state.nextUnfinalizedTxHeight
)
if (messages.length === 0) {
this.logger.info('Did not find any L2->L1 messages', {
retryAgainInS: Math.floor(this.options.pollingInterval / 1000),
})
}
for (const message of messages) {
this.logger.info('Found a message sent during transaction', {
index: message.parentTransactionIndex,
})
if (await this._wasMessageRelayed(message)) {
this.logger.info('Message has already been relayed, skipping.')
continue
}
this.logger.info(
'Message not yet relayed. Attempting to generate a proof...'
)
const proof = await this._getMessageProof(message)
this.logger.info(
'Successfully generated a proof. Attempting to relay to Layer 1...'
)
await this._relayMessageToL1(message, proof)
}
this.logger.info(
'Finished searching through newly finalized transactions',
{
retryAgainInS: Math.floor(this.options.pollingInterval / 1000),
}
)
} catch (err) {
this.logger.error('Caught an unhandled error', { err })
}
}
}
private async _getStateBatchHeader(
height: number
): Promise<
| {
batch: StateRootBatchHeader
stateRoots: string[]
}
| undefined
> {
const filter = this.state.OVM_StateCommitmentChain.filters.StateBatchAppended()
let startingBlock = this.state.lastQueriedL1Block
while (
startingBlock < (await this.options.l1RpcProvider.getBlockNumber())
) {
this.state.lastQueriedL1Block = startingBlock
this.logger.info('Querying events', {
startingBlock,
endBlock: startingBlock + this.options.getLogsInterval,
})
const events: ethers.Event[] = await this.state.OVM_StateCommitmentChain.queryFilter(
filter,
startingBlock,
startingBlock + this.options.getLogsInterval
)
this.state.eventCache = this.state.eventCache.concat(events)
startingBlock += this.options.getLogsInterval
}
// tslint:disable-next-line
const event = this.state.eventCache.find((event) => {
return (
event.args._prevTotalElements.toNumber() <= height &&
event.args._prevTotalElements.toNumber() +
event.args._batchSize.toNumber() >
height
)
})
if (event) {
const transaction = await this.options.l1RpcProvider.getTransaction(
event.transactionHash
)
const [
stateRoots,
] = this.state.OVM_StateCommitmentChain.interface.decodeFunctionData(
'appendStateBatch',
transaction.data
)
return {
batch: {
batchIndex: event.args._batchIndex,
batchRoot: event.args._batchRoot,
batchSize: event.args._batchSize,
prevTotalElements: event.args._prevTotalElements,
extraData: event.args._extraData,
},
stateRoots,
}
}
return
}
private async _isTransactionFinalized(height: number): Promise<boolean> {
this.logger.info('Checking if tx is finalized', { height })
const header = await this._getStateBatchHeader(height)
if (header === undefined) {
this.logger.info('No state batch header found.')
return false
} else {
this.logger.info('Got state batch header', { header })
}
return !(await this.state.OVM_StateCommitmentChain.insideFraudProofWindow(
header.batch
))
}
private async _getSentMessages(
startHeight: number,
endHeight: number
): Promise<SentMessage[]> {
const filter = this.state.OVM_L2CrossDomainMessenger.filters.SentMessage()
const events = await this.state.OVM_L2CrossDomainMessenger.queryFilter(
filter,
startHeight + this.options.l2BlockOffset,
endHeight + this.options.l2BlockOffset - 1
)
return events.map((event) => {
const message = event.args.message
const decoded = this.state.OVM_L2CrossDomainMessenger.interface.decodeFunctionData(
'relayMessage',
message
)
return {
target: decoded._target,
sender: decoded._sender,
message: decoded._message,
messageNonce: decoded._messageNonce,
encodedMessage: message,
encodedMessageHash: ethers.utils.keccak256(message),
parentTransactionIndex: event.blockNumber - this.options.l2BlockOffset,
parentTransactionHash: event.transactionHash,
}
})
}
private async _wasMessageRelayed(message: SentMessage): Promise<boolean> {
return this.state.OVM_L1CrossDomainMessenger.successfulMessages(
message.encodedMessageHash
)
}
private async _getMessageProof(
message: SentMessage
): Promise<SentMessageProof> {
const messageSlot = ethers.utils.keccak256(
ethers.utils.keccak256(
message.encodedMessage +
this.state.OVM_L2CrossDomainMessenger.address.slice(2)
) + '00'.repeat(32)
)
// TODO: Complain if the proof doesn't exist.
const proof = await this.options.l2RpcProvider.send('eth_getProof', [
this.state.OVM_L2ToL1MessagePasser.address,
[messageSlot],
'0x' +
BigNumber.from(
message.parentTransactionIndex + this.options.l2BlockOffset
)
.toHexString()
.slice(2)
.replace(/^0+/, ''),
])
// TODO: Complain if the batch doesn't exist.
const header = await this._getStateBatchHeader(
message.parentTransactionIndex
)
const elements = []
for (
let i = 0;
i < Math.pow(2, Math.ceil(Math.log2(header.stateRoots.length)));
i++
) {
if (i < header.stateRoots.length) {
elements.push(header.stateRoots[i])
} else {
elements.push(ethers.utils.keccak256('0x' + '00'.repeat(32)))
}
}
const hash = (el: Buffer | string): Buffer => {
return Buffer.from(ethers.utils.keccak256(el).slice(2), 'hex')
}
const leaves = elements.map((element) => {
return fromHexString(element)
})
const tree = new MerkleTree(leaves, hash)
const index =
message.parentTransactionIndex - header.batch.prevTotalElements.toNumber()
const treeProof = tree.getProof(leaves[index], index).map((element) => {
return element.data
})
return {
stateRoot: header.stateRoots[index],
stateRootBatchHeader: header.batch,
stateRootProof: {
index,
siblings: treeProof,
},
stateTrieWitness: rlp.encode(proof.accountProof),
storageTrieWitness: rlp.encode(proof.storageProof[0].proof),
}
}
private async _relayMessageToL1(
message: SentMessage,
proof: SentMessageProof
): Promise<void> {
if (this.options.spreadsheetMode) {
try {
await this.options.spreadsheet.addRow({
target: message.target,
sender: message.sender,
message: message.message,
messageNonce: message.messageNonce.toString(),
encodedMessage: message.encodedMessage,
encodedMessageHash: message.encodedMessageHash,
parentTransactionIndex: message.parentTransactionIndex,
parentTransactionHash: message.parentTransactionIndex,
stateRoot: proof.stateRoot,
batchIndex: proof.stateRootBatchHeader.batchIndex.toString(),
batchRoot: proof.stateRootBatchHeader.batchRoot,
batchSize: proof.stateRootBatchHeader.batchSize.toString(),
prevTotalElements: proof.stateRootBatchHeader.prevTotalElements.toString(),
extraData: proof.stateRootBatchHeader.extraData,
index: proof.stateRootProof.index,
siblings: proof.stateRootProof.siblings.join(','),
stateTrieWitness: proof.stateTrieWitness.toString('hex'),
storageTrieWitness: proof.storageTrieWitness.toString('hex'),
})
this.logger.info('Submitted relay message to spreadsheet')
} catch (e) {
this.logger.error('Cannot submit message to spreadsheet')
this.logger.error(e.message)
}
} else {
try {
this.logger.info(
'Dry-run, checking to make sure proof would succeed...'
)
await this.state.OVM_L1CrossDomainMessenger.connect(
this.options.l1Wallet
).callStatic.relayMessage(
message.target,
message.sender,
message.message,
message.messageNonce,
proof,
{
gasLimit: this.options.relayGasLimit,
}
)
this.logger.info(
'Proof should succeed. Submitting for real this time...'
)
} catch (err) {
this.logger.error('Proof would fail, skipping', { err })
return
}
const result = await this.state.OVM_L1CrossDomainMessenger.connect(
this.options.l1Wallet
).relayMessage(
message.target,
message.sender,
message.message,
message.messageNonce,
proof,
{
gasLimit: this.options.relayGasLimit,
}
)
try {
const receipt = await result.wait()
this.logger.info('Relay message transaction sent', {
transactionHash: receipt.transactionHash,
})
} catch (err) {
this.logger.error('Real relay attempt failed, skipping.', { err })
return
}
this.logger.info('Message successfully relayed to Layer 1!')
}
}
}
import { GoogleSpreadsheet } from 'google-spreadsheet'
export default class SpreadSheet {
public doc
public sheet
constructor(id) {
this.doc = new GoogleSpreadsheet(id)
this.sheet = null
}
async init(email, privateKey) {
await this.doc.useServiceAccountAuth({
client_email: email,
private_key: privateKey,
})
await this.doc.loadInfo()
this.sheet = this.doc.sheetsByIndex[0]
}
async addRow(row) {
return this.sheet.addRow(row)
}
}
import { BigNumber } from 'ethers'
export interface StateRootBatchHeader {
batchIndex: BigNumber
batchRoot: string
batchSize: BigNumber
prevTotalElements: BigNumber
extraData: string
}
export interface SentMessage {
target: string
sender: string
message: string
messageNonce: number
encodedMessage: string
encodedMessageHash: string
parentTransactionIndex: number
parentTransactionHash: string
}
export interface SentMessageProof {
stateRoot: string
stateRootBatchHeader: StateRootBatchHeader
stateRootProof: StateRootProof
stateTrieWitness: string | Buffer
storageTrieWitness: string | Buffer
}
export interface StateRootProof {
index: number
siblings: string[]
}
{
"extends": "../../tsconfig.build.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist"
},
"include": [
"src/**/*"
]
}
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"resolveJsonModule": true
}
}
{
"extends": "../../tslint.base.json"
}
......@@ -2344,7 +2344,7 @@ arrify@^1.0.0, arrify@^1.0.1:
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=
arrify@^2.0.1:
arrify@^2.0.0, arrify@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa"
integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==
......@@ -2459,6 +2459,13 @@ axios@^0.19.0:
dependencies:
follow-redirects "1.5.10"
axios@^0.21.1:
version "0.21.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
dependencies:
follow-redirects "^1.10.0"
babel-code-frame@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
......@@ -2997,7 +3004,7 @@ base-x@^3.0.2, base-x@^3.0.8:
dependencies:
safe-buffer "^5.0.1"
base64-js@^1.3.1:
base64-js@^1.3.0, base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
......@@ -3251,6 +3258,11 @@ bsert@~0.0.10:
resolved "https://registry.yarnpkg.com/bsert/-/bsert-0.0.10.tgz#231ac82873a1418c6ade301ab5cd9ae385895597"
integrity sha512-NHNwlac+WPy4t2LoNh8pXk8uaIGH3NSaIUbTTRXGpE2WEbq0te/tDykYHkFK57YKLPjv/aGHmbqvnGeVWDz57Q==
buffer-equal-constant-time@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=
buffer-from@^1.0.0, buffer-from@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
......@@ -4448,6 +4460,13 @@ ecc-jsbn@~0.1.1:
jsbn "~0.1.0"
safer-buffer "^2.1.0"
ecdsa-sig-formatter@1.0.11, ecdsa-sig-formatter@^1.0.11:
version "1.0.11"
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
dependencies:
safe-buffer "^5.0.1"
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
......@@ -5104,6 +5123,7 @@ ethereumjs-wallet@0.6.5:
uuid "^3.3.2"
"ethers-v4@npm:ethers@4", ethers@^4.0.32:
name ethers-v4
version "4.0.48"
resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.48.tgz#330c65b8133e112b0613156e57e92d9009d8fbbe"
integrity sha512-sZD5K8H28dOrcidzx9f8KYh8083n5BexIO3+SbE4jK83L85FxtpXZBCQdXb8gkg+7sBqomcLhhkU7UHL+F7I2g==
......@@ -5342,7 +5362,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2:
assign-symbols "^1.0.0"
is-extendable "^1.0.1"
extend@~3.0.2:
extend@^3.0.2, extend@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
......@@ -5429,6 +5449,11 @@ fast-safe-stringify@^2.0.7:
resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743"
integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==
fast-text-encoding@^1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz#ec02ac8e01ab8a319af182dae2681213cfe9ce53"
integrity sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==
fastq@^1.6.0:
version "1.11.0"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.11.0.tgz#bb9fb955a07130a918eb63c1f5161cc32a5d0858"
......@@ -5580,7 +5605,7 @@ follow-redirects@1.5.10:
dependencies:
debug "=3.1.0"
follow-redirects@^1.12.1:
follow-redirects@^1.10.0, follow-redirects@^1.12.1:
version "1.13.3"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267"
integrity sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==
......@@ -5799,6 +5824,25 @@ gauge@~2.7.3:
strip-ansi "^3.0.1"
wide-align "^1.1.0"
gaxios@^4.0.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-4.2.0.tgz#33bdc4fc241fc33b8915a4b8c07cfb368b932e46"
integrity sha512-Ms7fNifGv0XVU+6eIyL9LB7RVESeML9+cMvkwGS70xyD6w2Z80wl6RiqiJ9k1KFlJCUTQqFFc8tXmPQfSKUe8g==
dependencies:
abort-controller "^3.0.0"
extend "^3.0.2"
https-proxy-agent "^5.0.0"
is-stream "^2.0.0"
node-fetch "^2.3.0"
gcp-metadata@^4.2.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-4.2.1.tgz#31849fbcf9025ef34c2297c32a89a1e7e9f2cd62"
integrity sha512-tSk+REe5iq/N+K+SK1XjZJUrFPuDqGZVzCy2vocIHIGmPlTGsa8owXMJwGkrXr73NO0AzhPW4MF2DEHz7P2AVw==
dependencies:
gaxios "^4.0.0"
json-bigint "^1.0.0"
get-caller-file@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
......@@ -6039,6 +6083,37 @@ globby@^11.0.2:
merge2 "^1.3.0"
slash "^3.0.0"
google-auth-library@^6.1.3:
version "6.1.6"
resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-6.1.6.tgz#deacdcdb883d9ed6bac78bb5d79a078877fdf572"
integrity sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==
dependencies:
arrify "^2.0.0"
base64-js "^1.3.0"
ecdsa-sig-formatter "^1.0.11"
fast-text-encoding "^1.0.0"
gaxios "^4.0.0"
gcp-metadata "^4.2.0"
gtoken "^5.0.4"
jws "^4.0.0"
lru-cache "^6.0.0"
google-p12-pem@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-3.0.3.tgz#673ac3a75d3903a87f05878f3c75e06fc151669e"
integrity sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA==
dependencies:
node-forge "^0.10.0"
google-spreadsheet@^3.1.15:
version "3.1.15"
resolved "https://registry.yarnpkg.com/google-spreadsheet/-/google-spreadsheet-3.1.15.tgz#e7a86f750d8166faaa3e16929561baceb807bf5a"
integrity sha512-S5477f3Gf3Mz6AXgCw7dbaYnzu5aHou1AX4sDqrGboQWnAytkxqJGKQiXN+zzRTTcYzSTJCe0g7KqCPZO9xiOw==
dependencies:
axios "^0.21.1"
google-auth-library "^6.1.3"
lodash "^4.17.20"
got@9.6.0:
version "9.6.0"
resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85"
......@@ -6086,6 +6161,15 @@ growl@1.10.5:
resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e"
integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==
gtoken@^5.0.4:
version "5.2.1"
resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-5.2.1.tgz#4dae1fea17270f457954b4a45234bba5fc796d16"
integrity sha512-OY0BfPKe3QnMsY9MzTHTSKn+Vl2l1CcLe6BwDEQj00mbbkl5nyQ/7EUREstg4fQNZ8iYE7br4JJ7TdKeDOPWmw==
dependencies:
gaxios "^4.0.0"
google-p12-pem "^3.0.3"
jws "^4.0.0"
handlebars@^4.0.1, handlebars@^4.7.6:
version "4.7.7"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1"
......@@ -7006,6 +7090,13 @@ jsesc@~0.5.0:
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=
json-bigint@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1"
integrity sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==
dependencies:
bignumber.js "^9.0.0"
json-buffer@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
......@@ -7132,6 +7223,23 @@ just-extend@^4.0.2:
resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.2.1.tgz#ef5e589afb61e5d66b24eca749409a8939a8c744"
integrity sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==
jwa@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.0.tgz#a7e9c3f29dae94027ebcaf49975c9345593410fc"
integrity sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==
dependencies:
buffer-equal-constant-time "1.0.1"
ecdsa-sig-formatter "1.0.11"
safe-buffer "^5.0.1"
jws@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.0.tgz#2d4e8cf6a318ffaa12615e9dec7e86e6c97310f4"
integrity sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==
dependencies:
jwa "^2.0.0"
safe-buffer "^5.0.1"
keccak@3.0.1, keccak@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.1.tgz#ae30a0e94dbe43414f741375cff6d64c8bea0bff"
......@@ -7924,7 +8032,7 @@ merkle-patricia-tree@^4.0.0:
rlp "^2.2.3"
semaphore-async-await "^1.5.1"
merkletreejs@^0.2.12:
merkletreejs@^0.2.12, merkletreejs@^0.2.18:
version "0.2.18"
resolved "https://registry.yarnpkg.com/merkletreejs/-/merkletreejs-0.2.18.tgz#205cc4f79e134c9bc887bdbd440b9e78787c4823"
integrity sha512-f8bSFaUDPZhot94xkjb83XbG1URaiNLxZy6LWTw2IzbQeCA4YX/UxublGxXdLQIYXbWkDghq6EqwG5u4I7ELmA==
......@@ -8499,7 +8607,7 @@ node-fetch@2.1.2:
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5"
integrity sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=
node-fetch@^2.6.0, node-fetch@^2.6.1:
node-fetch@^2.3.0, node-fetch@^2.6.0, node-fetch@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
......@@ -8512,6 +8620,11 @@ node-fetch@~1.7.1:
encoding "^0.1.11"
is-stream "^1.0.1"
node-forge@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==
node-gyp-build@^4.2.0:
version "4.2.3"
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.2.3.tgz#ce6277f853835f718829efb47db20f3e4d9c4739"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment