From f165efc31b8f3c1c1df7ebaa5084ead73641b6bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marin=20Petruni=C4=87?= Date: Tue, 23 Apr 2019 14:03:28 +0200 Subject: [PATCH 1/9] implement deposit root fetching --- src/eth1/ethers.ts | 9 +++++++-- test/unit/eth1/ethers.test.ts | 29 +++++++++++++++++++++++------ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/eth1/ethers.ts b/src/eth1/ethers.ts index 31f0189cef05..8686e727bf20 100644 --- a/src/eth1/ethers.ts +++ b/src/eth1/ethers.ts @@ -1,6 +1,6 @@ import BN from "bn.js"; import {EventEmitter} from "events"; -import {ethers} from "ethers"; +import {Contract, ethers} from "ethers"; import {deserialize} from "@chainsafe/ssz"; import {bytes32, Deposit, DepositData, Eth1Data} from "../types"; @@ -105,7 +105,12 @@ export class EthersEth1Notifier extends EventEmitter implements Eth1Notifier { } public async depositRoot(): Promise { - return Buffer.alloc(32); + const depositRootHex = await this.contract.get_deposit_root(); + return Buffer.from(depositRootHex.substr(2), 'hex'); + } + + public setContract(contract: Contract) { + this.contract = contract; } private async contractExists(address: string) { diff --git a/test/unit/eth1/ethers.test.ts b/test/unit/eth1/ethers.test.ts index 3ec42230aa6b..9fc433cc301f 100644 --- a/test/unit/eth1/ethers.test.ts +++ b/test/unit/eth1/ethers.test.ts @@ -1,5 +1,5 @@ import chai, {assert, expect} from "chai"; -import { ethers, Event } from "ethers"; +import {Contract, ethers, Event} from "ethers"; import ganache from "ganache-core"; import sinon from "sinon"; @@ -18,11 +18,11 @@ describe("Eth1Notifier", () => { provider, }); - before(async function() { + before(async function(): Promise { logger.silent(true); }); - it("should fail to start because there isn't contract at given address", async function() { + it("should fail to start because there isn't contract at given address", async function(): Promise { await expect(eth1.start()).to.be.rejectedWith('There is no deposit contract at given address'); }); @@ -36,6 +36,7 @@ describe("Eth1Notifier", () => { eth1.processDepositLog(dataHex, indexHex); assert(cb.calledOnce, "deposit event did not fire"); }); + it("should process a Eth2Genesis log", async function() { const cb = sinon.spy(); eth1.on("eth2genesis", cb); @@ -51,7 +52,8 @@ describe("Eth1Notifier", () => { await eth1.processEth2GenesisLog(depositRootHex, depositCountHex, timeHex, event); assert(cb.calledOnce, "eth2genesis event did not fire"); }); - it("should process a new block", async function() { + + it("should process a new block", async function(): Promise { this.timeout(0); const cb = sinon.spy(); @@ -61,8 +63,23 @@ describe("Eth1Notifier", () => { assert(cb.calledOnce, "new block event did not fire"); }); - after(async function() { + it("should get deposit root from contract", async function(): Promise { + const spy = sinon.stub(); + const contract = { + // eslint-disable-next-line + get_deposit_root: spy + }; + eth1.setContract(contract as any); + const testDepositRoot = Buffer.alloc(32); + spy.resolves('0x' + testDepositRoot.toString('hex')); + + const depositRoot = await eth1.depositRoot(); + expect(depositRoot).to.be.deep.equal(testDepositRoot); + + }); + + after(async function(): Promise { await promisify(ganacheProvider.close)(); logger.silent(false); - }) + }); }); From 87d2a418e84a0b44929019c418b41ae43486ceca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marin=20Petruni=C4=87?= Date: Tue, 23 Apr 2019 14:22:51 +0200 Subject: [PATCH 2/9] implement tracking of latest eth1 block hash --- src/eth1/ethers.ts | 30 +++++++++++++++++++----------- src/eth1/interface.ts | 2 +- test/unit/eth1/ethers.test.ts | 8 ++++++++ 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/eth1/ethers.ts b/src/eth1/ethers.ts index 8686e727bf20..989f118aa799 100644 --- a/src/eth1/ethers.ts +++ b/src/eth1/ethers.ts @@ -30,19 +30,13 @@ export class EthersEth1Notifier extends EventEmitter implements Eth1Notifier { this.opts = opts; this.provider = opts.provider; this.depositCount = 0; + this._latestBlockHash = null; } public async start(): Promise { - const address = this.opts.depositContract.address; - const abi = this.opts.depositContract.abi; - if (!(await this.contractExists(address))) { - throw new Error(`There is no deposit contract at given address: ${address}`); - } - try { - this.contract = new ethers.Contract(address, abi, this.provider); - } catch (e) { - throw new Error('Eth1 deposit contract not found! Probably wrong eth1 rpc url'); - } + await this.initContract(); + const latestBlockNumber = await this.provider.getBlockNumber(); + await this.processBlockHeadUpdate(latestBlockNumber); this.provider.on('block', this.processBlockHeadUpdate.bind(this)); this.contract.on('Deposit', this.processDepositLog.bind(this)); this.contract.on('Eth2Genesis', this.processEth2GenesisLog.bind(this)); @@ -58,6 +52,7 @@ export class EthersEth1Notifier extends EventEmitter implements Eth1Notifier { public async processBlockHeadUpdate(blockNumber): Promise { logger.debug(`Received eth1 block ${blockNumber}`); const block = await this.provider.getBlock(blockNumber); + this._latestBlockHash = Buffer.from(block.hash.substr(2), 'hex'); this.emit('block', block); } @@ -100,7 +95,7 @@ export class EthersEth1Notifier extends EventEmitter implements Eth1Notifier { return []; } - public async latestBlockHash(): Promise { + public latestBlockHash(): bytes32 { return this._latestBlockHash; } @@ -113,6 +108,19 @@ export class EthersEth1Notifier extends EventEmitter implements Eth1Notifier { this.contract = contract; } + private async initContract(): Promise { + const address = this.opts.depositContract.address; + const abi = this.opts.depositContract.abi; + if (!(await this.contractExists(address))) { + throw new Error(`There is no deposit contract at given address: ${address}`); + } + try { + this.contract = new ethers.Contract(address, abi, this.provider); + } catch (e) { + throw new Error('Eth1 deposit contract not found! Probably wrong eth1 rpc url'); + } + } + private async contractExists(address: string) { if (!isValidAddress(address)) return false; const code = await this.provider.getCode(address); diff --git a/src/eth1/interface.ts b/src/eth1/interface.ts index 6281d4f5220c..573b97372d30 100644 --- a/src/eth1/interface.ts +++ b/src/eth1/interface.ts @@ -39,7 +39,7 @@ export interface Eth1Notifier extends EventEmitter { /** * Return the latest block hash */ - latestBlockHash(): Promise; + latestBlockHash(): bytes32; /** * Return the merkle root of the deposits diff --git a/test/unit/eth1/ethers.test.ts b/test/unit/eth1/ethers.test.ts index 9fc433cc301f..7063cc0e8e94 100644 --- a/test/unit/eth1/ethers.test.ts +++ b/test/unit/eth1/ethers.test.ts @@ -63,6 +63,14 @@ describe("Eth1Notifier", () => { assert(cb.calledOnce, "new block event did not fire"); }); + it("should get latest block hash", async function(): Promise { + this.timeout(0); + + await eth1.processBlockHeadUpdate(0); + expect(eth1.latestBlockHash()).to.not.be.null; + expect(eth1.latestBlockHash().length).to.be.equal(32); + }); + it("should get deposit root from contract", async function(): Promise { const spy = sinon.stub(); const contract = { From 1bbebe26030f7f1ff420a37141e5b207d5045dec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marin=20Petruni=C4=87?= Date: Wed, 1 May 2019 12:16:00 +0200 Subject: [PATCH 3/9] add methods for tracking genesis deposits in database --- src/db/impl/abstract.ts | 23 ++++++++++++++++++- src/db/interface.ts | 20 ++++++++++++++++- src/db/schema.ts | 2 +- test/unit/db/level.test.ts | 45 +++++++++++++++++++++++++------------- test/utils/deposit.ts | 22 +++++++++++++++++++ 5 files changed, 94 insertions(+), 18 deletions(-) create mode 100644 test/utils/deposit.ts diff --git a/src/db/impl/abstract.ts b/src/db/impl/abstract.ts index d4e010ae8ed8..6ed3c90820d1 100644 --- a/src/db/impl/abstract.ts +++ b/src/db/impl/abstract.ts @@ -6,6 +6,7 @@ import { BeaconBlock, BeaconState, bytes32, + Deposit, ProposerSlashing, Slot, Transfer, @@ -15,7 +16,7 @@ import { import {Bucket, encodeKey, Key} from "../schema"; -import {deserialize, serialize, hashTreeRoot} from "@chainsafe/ssz"; +import {deserialize, hashTreeRoot, serialize} from "@chainsafe/ssz"; export interface SearchOptions { gt: any; @@ -252,4 +253,24 @@ export default abstract class AbstractDB extends EventEmitter implements DB { await this.batchDelete(criteria); } + public async getGenesisDeposits(): Promise { + const data = await this.search({ + gt: encodeKey(Bucket.genesisDeposit, Buffer.alloc(0)), + lt: encodeKey(Bucket.genesisDeposit + 1, Buffer.alloc(0)), + }); + return data.map((data) => deserialize(data, Deposit)); + } + + public async setGenesisDeposit(deposit: Deposit): Promise { + await this.put(encodeKey(Bucket.genesisDeposit, deposit.index), serialize(deposit, Deposit)); + } + + public async deleteGenesisDeposits(deposits: Deposit[]): Promise { + const criteria: any[] = []; + deposits.forEach((deposit) => + criteria.push(encodeKey(Bucket.genesisDeposit, deposit.index)) + ); + await this.batchDelete(criteria); + } + } diff --git a/src/db/interface.ts b/src/db/interface.ts index b11561830a74..e23e3e340969 100644 --- a/src/db/interface.ts +++ b/src/db/interface.ts @@ -9,7 +9,7 @@ import { ProposerSlashing, Slot, VoluntaryExit, - Transfer, + Transfer, Deposit, } from "../types"; export interface DBOptions { @@ -24,6 +24,24 @@ export interface DB extends EventEmitter { start(): Promise; stop(): Promise; + /** + * Adds deposit to database + * @param deposit + */ + setGenesisDeposit(deposit: Deposit): Promise; + + /** + * Get all stored deposits sorted from oldest to newest. + * It will only contain deposits until Eth2Genesis event. + * After that, deposits will be kept in BeaconBlock + */ + getGenesisDeposits(): Promise; + + /** + * Deletes all deposits. + */ + deleteGenesisDeposits(deposits: Deposit[]): Promise; + /** * Get the beacon chain state * @returns {Promise} diff --git a/src/db/schema.ts b/src/db/schema.ts index 30aeb56a9f8b..0cc7d0950a62 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -7,7 +7,7 @@ export enum Bucket { mainChain, chainInfo, validator, - deposit, + genesisDeposit, exit, transfer, proposerSlashing, diff --git a/test/unit/db/level.test.ts b/test/unit/db/level.test.ts index 6d0362b85551..187fd35ba35c 100644 --- a/test/unit/db/level.test.ts +++ b/test/unit/db/level.test.ts @@ -1,4 +1,4 @@ -import { assert } from "chai"; +import {assert} from "chai"; import leveldown from "leveldown"; import promisify from "promisify-es6"; import {hashTreeRoot, serialize,} from "@chainsafe/ssz"; @@ -13,6 +13,7 @@ import {generateEmptyAttestation} from "../../utils/attestation"; import {generateEmptyVoluntaryExit} from "../../utils/voluntaryExits"; import level from "level"; import logger from "../../../src/logger/winston"; +import {generateDeposit} from "../../utils/deposit"; describe("LevelDB", () => { const dbLocation = "./.__testdb"; @@ -78,19 +79,33 @@ describe("LevelDB", () => { assert.equal(noAttestations.length, 0); }); + it("should correctly set, get, delete genesis deposits", async () => { + const testDeposits = Array.from({length: 64}, (_, i) => { + return generateDeposit(i); + }); + for (const a of testDeposits) { + await db.setGenesisDeposit(a); + } + const actualDeposits = await db.getGenesisDeposits(); + assert.equal(actualDeposits.length, actualDeposits.length); + await db.deleteGenesisDeposits(testDeposits); + const noDeposits = await db.getGenesisDeposits(); + assert.equal(noDeposits.length, 0); + }); + it("should correctly set, get, delete voluntary exits", async () => { - const testVoluntaryExits = Array.from({length: 10}, (_, i) => { - const a = generateEmptyVoluntaryExit(); - a.epoch = i; - return a; - }); - for (const a of testVoluntaryExits) { - await db.setVoluntaryExit(a); - } - const actualVoluntaryExits = await db.getVoluntaryExits(); - assert.equal(actualVoluntaryExits.length, testVoluntaryExits.length); - await db.deleteVoluntaryExits(actualVoluntaryExits); - const noVoluntaryExits = await db.getVoluntaryExits(); - assert.equal(noVoluntaryExits.length, 0); - }) + const testVoluntaryExits = Array.from({length: 10}, (_, i) => { + const a = generateEmptyVoluntaryExit(); + a.epoch = i; + return a; + }); + for (const a of testVoluntaryExits) { + await db.setVoluntaryExit(a); + } + const actualVoluntaryExits = await db.getVoluntaryExits(); + assert.equal(actualVoluntaryExits.length, testVoluntaryExits.length); + await db.deleteVoluntaryExits(actualVoluntaryExits); + const noVoluntaryExits = await db.getVoluntaryExits(); + assert.equal(noVoluntaryExits.length, 0); + }); }); diff --git a/test/utils/deposit.ts b/test/utils/deposit.ts new file mode 100644 index 000000000000..f056077e48c2 --- /dev/null +++ b/test/utils/deposit.ts @@ -0,0 +1,22 @@ +import {Deposit} from "../../src/types"; +import {randBetween} from "./misc"; +import {MAX_EFFECTIVE_BALANCE, MIN_DEPOSIT_AMOUNT} from "../../src/constants"; +import BN from "bn.js"; + +/** + * Generates a fake attestation data for test purposes. + * @returns {Deposit} + * @param index + */ +export function generateDeposit(index: number): Deposit { + return { + index, + proof: [], + data: { + amount: new BN(randBetween(MIN_DEPOSIT_AMOUNT, MAX_EFFECTIVE_BALANCE).toString()), + pubkey: Buffer.alloc(48), + withdrawalCredentials: Buffer.alloc(32), + signature: Buffer.alloc(48) + } + }; +} From eb77888cdb3e2ad7abb1de58cd5f21365fd5a2d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marin=20Petruni=C4=87?= Date: Wed, 1 May 2019 12:20:05 +0200 Subject: [PATCH 4/9] adjust eth1notifier interface --- src/eth1/interface.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/eth1/interface.ts b/src/eth1/interface.ts index 76f71b6af4f2..ee2c699446ef 100644 --- a/src/eth1/interface.ts +++ b/src/eth1/interface.ts @@ -4,6 +4,7 @@ import {bytes32, DepositData, Deposit, Eth1Data} from "../types"; export interface Eth1Options { depositContract: { + deployedAt: number; address: string; abi: any[]; }; From 3cb83b72ce0d3332fd0a1dbb8c940cb04a909b82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marin=20Petruni=C4=87?= Date: Wed, 1 May 2019 12:20:25 +0200 Subject: [PATCH 5/9] generater 32 wei amount --- test/utils/deposit.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/utils/deposit.ts b/test/utils/deposit.ts index f056077e48c2..941469c5fd56 100644 --- a/test/utils/deposit.ts +++ b/test/utils/deposit.ts @@ -1,6 +1,5 @@ import {Deposit} from "../../src/types"; -import {randBetween} from "./misc"; -import {MAX_EFFECTIVE_BALANCE, MIN_DEPOSIT_AMOUNT} from "../../src/constants"; +import {EMPTY_SIGNATURE} from "../../src/constants"; import BN from "bn.js"; /** @@ -13,10 +12,10 @@ export function generateDeposit(index: number): Deposit { index, proof: [], data: { - amount: new BN(randBetween(MIN_DEPOSIT_AMOUNT, MAX_EFFECTIVE_BALANCE).toString()), + amount: new BN(32).mul(new BN(10).muln(9)), pubkey: Buffer.alloc(48), withdrawalCredentials: Buffer.alloc(32), - signature: Buffer.alloc(48) + signature: EMPTY_SIGNATURE } }; } From 9a24bb10f5e390f0cf3be5157349b33074bae5e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marin=20Petruni=C4=87?= Date: Wed, 1 May 2019 13:17:00 +0200 Subject: [PATCH 6/9] eth1 notifier code reorg and tests --- .codecov.yml | 3 +- src/eth1/defaults.ts | 2 + src/eth1/dev/defaults.ts | 1 + src/eth1/fake.ts | 50 --------------- src/eth1/{ => impl}/ethers.ts | 75 +++++++++++++++------- src/eth1/{ => impl}/mock.ts | 25 ++++++-- src/eth1/index.ts | 4 +- test/e2e/eth1/deploy.test.ts | 2 + test/unit/eth1/ethers.test.ts | 115 ++++++++++++++++++++++++++-------- 9 files changed, 169 insertions(+), 108 deletions(-) delete mode 100644 src/eth1/fake.ts rename src/eth1/{ => impl}/ethers.ts (69%) rename src/eth1/{ => impl}/mock.ts (67%) diff --git a/.codecov.yml b/.codecov.yml index 30519f80c60e..ab28dc530e02 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -24,6 +24,7 @@ comment: layout: "header, diff" behavior: default require_changes: no - + ignore: - "test" + - "**/mock*" diff --git a/src/eth1/defaults.ts b/src/eth1/defaults.ts index 3f21f08ee837..8c160ed4a2cf 100644 --- a/src/eth1/defaults.ts +++ b/src/eth1/defaults.ts @@ -4,6 +4,8 @@ import {DEPOSIT_CONTRACT_ADDRESS} from "../constants"; export default { provider: ethers.getDefaultProvider(), depositContract: { + //block at which contract is deployed + deployedAt: 0, address: DEPOSIT_CONTRACT_ADDRESS, // taken from https://github.com/ethereum/deposit_contract/blob/master/deposit_contract/contracts/validator_registration.json abi: [{"name": "Deposit", "inputs": [{"type": "bytes", "name": "data", "indexed": false}, {"type": "bytes", "name": "merkle_tree_index", "indexed": false}], "anonymous": false, "type": "event"}, {"name": "Eth2Genesis", "inputs": [{"type": "bytes32", "name": "deposit_root", "indexed": false}, {"type": "bytes", "name": "deposit_count", "indexed": false}, {"type": "bytes", "name": "time", "indexed": false}], "anonymous": false, "type": "event"}, {"name": "__init__", "outputs": [], "inputs": [], "constant": false, "payable": false, "type": "constructor"}, {"name": "to_little_endian_64", "outputs": [{"type": "bytes", "name": "out"}], "inputs": [{"type": "uint256", "name": "value"}], "constant": true, "payable": false, "type": "function", "gas": 7089}, {"name": "get_deposit_root", "outputs": [{"type": "bytes32", "name": "out"}], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 26965}, {"name": "get_deposit_count", "outputs": [{"type": "bytes", "name": "out"}], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 11038}, {"name": "deposit", "outputs": [], "inputs": [{"type": "bytes", "name": "deposit_input"}], "constant": false, "payable": true, "type": "function", "gas": 390140}, {"name": "chainStarted", "outputs": [{"type": "bool", "name": "out"}], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 603}], diff --git a/src/eth1/dev/defaults.ts b/src/eth1/dev/defaults.ts index fa1a9e66df30..edbbc64fa6e2 100644 --- a/src/eth1/dev/defaults.ts +++ b/src/eth1/dev/defaults.ts @@ -6,6 +6,7 @@ export const networkOpts = { }; export const depositContract = { + deployedAt: 0, //https://github.com/chainsafe/deposit_contract/blob/master/deposit_contract/contracts/validator_registration.json abi: [{"name": "Deposit", "inputs": [{"type": "bytes", "name": "data", "indexed": false}, {"type": "bytes", "name": "merkle_tree_index", "indexed": false}], "anonymous": false, "type": "event"}, {"name": "Eth2Genesis", "inputs": [{"type": "bytes32", "name": "deposit_root", "indexed": false}, {"type": "bytes", "name": "deposit_count", "indexed": false}, {"type": "bytes", "name": "time", "indexed": false}], "anonymous": false, "type": "event"}, {"name": "__init__", "outputs": [], "inputs": [], "constant": false, "payable": false, "type": "constructor"}, {"name": "to_little_endian_64", "outputs": [{"type": "bytes", "name": "out"}], "inputs": [{"type": "uint256", "name": "value"}], "constant": true, "payable": false, "type": "function", "gas": 7089}, {"name": "get_deposit_root", "outputs": [{"type": "bytes32", "name": "out"}], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 26965}, {"name": "get_deposit_count", "outputs": [{"type": "bytes", "name": "out"}], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 11038}, {"name": "deposit", "outputs": [], "inputs": [{"type": "bytes", "name": "deposit_input"}], "constant": false, "payable": true, "type": "function", "gas": 390140}, {"name": "chainStarted", "outputs": [{"type": "bool", "name": "out"}], "inputs": [], "constant": true, "payable": false, "type": "function", "gas": 603}], bytecode: "0x600035601c52740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a052341561009e57600080fd5b6101406000601f818352015b600061014051602081106100bd57600080fd5b600060c052602060c020015460208261016001015260208101905061014051602081106100e957600080fd5b600060c052602060c020015460208261016001015260208101905080610160526101609050805160208201209050606051600161014051018060405190131561013157600080fd5b809190121561013f57600080fd5b6020811061014c57600080fd5b600060c052602060c0200155606051600161014051018060405190131561017257600080fd5b809190121561018057600080fd5b6020811061018d57600080fd5b600060c052602060c020015460605160016101405101806040519013156101b357600080fd5b80919012156101c157600080fd5b602081106101ce57600080fd5b600160c052602060c02001555b81516001018083528114156100aa575b5050610f2a56600035601c52740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a0526380673289600051141561026b57602060046101403734156100b457600080fd5b67ffffffffffffffff6101405111156100cc57600080fd5b60006101605261014051610180526101a060006008818352015b6101605160086000811215610103578060000360020a820461010a565b8060020a82025b905090506101605260ff61018051166101c052610160516101c0516101605101101561013557600080fd5b6101c051610160510161016052610180517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8600081121561017e578060000360020a8204610185565b8060020a82025b90509050610180525b81516001018083528114156100e6575b5050601860086020820661026001602082840111156101bc57600080fd5b60208061028082610160600060046015f1505081815280905090509050805160200180610320828460006004600a8704601201f16101f957600080fd5b50506103205160206001820306601f8201039050610380610320516008818352015b8261038051111561022b57610247565b60006103805161034001535b815160010180835281141561021b575b5050506020610300526040610320510160206001820306601f8201039050610300f3005b63c5f2892f600051141561039957341561028457600080fd5b6000610140526002546101605261018060006020818352015b600160016101605116141561030957600061018051602081106102bf57600080fd5b600160c052602060c0200154602082610220010152602081019050610140516020826102200101526020810190508061022052610220905080516020820120905061014052610362565b6000610140516020826101a0010152602081019050610180516020811061032f57600080fd5b600060c052602060c02001546020826101a0010152602081019050806101a0526101a09050805160208201209050610140525b610160600261037057600080fd5b60028151048152505b815160010180835281141561029d575b50506101405160005260206000f3005b63621fd130600051141561046f5734156103b257600080fd5b60606101c060246380673289610140526002546101605261015c6000305af16103da57600080fd5b6101e0805160200180610260828460006004600a8704601201f16103fd57600080fd5b50506102605160206001820306601f82010390506102c0610260516008818352015b826102c051111561042f5761044b565b60006102c05161028001535b815160010180835281141561041f575b5050506020610240526040610260510160206001820306601f8201039050610240f3005b6398b1e06a6000511415610d0c576020600461014037610220600435600401610160376102006004356004013511156104a757600080fd5b633b9aca006103c0526103c0516104bd57600080fd5b6103c05134046103a052633b9aca006103a05110156104db57600080fd5b6407735940006103a05111156104f057600080fd5b6002546103e05242610400526000606061070060246380673289610680526103a0516106a05261069c6000305af161052757600080fd5b61072060088060208461084001018260208501600060046012f150508051820191505060606107e06024638067328961076052610400516107805261077c6000305af161057357600080fd5b61080060088060208461084001018260208501600060046012f15050805182019150506101606102008060208461084001018260208501600060046045f150508051820191505080610840526108409050805160200180610420828460006004600a8704601201f16105e457600080fd5b50506000610aa0526002610ac052610ae060006020818352015b6000610ac05161060d57600080fd5b610ac0516103e05160016103e05101101561062757600080fd5b60016103e051010614151561063b576106a7565b610aa060605160018251018060405190131561065657600080fd5b809190121561066457600080fd5b815250610ac080511515610679576000610693565b600281516002835102041461068d57600080fd5b60028151025b8152505b81516001018083528114156105fe575b5050610420805160208201209050610b0052610b2060006020818352015b610aa051610b20511215610730576000610b2051602081106106e657600080fd5b600160c052602060c0200154602082610b40010152602081019050610b0051602082610b4001015260208101905080610b4052610b409050805160208201209050610b0052610735565b610746565b5b81516001018083528114156106c5575b5050610b0051610aa0516020811061075d57600080fd5b600160c052602060c0200155600280546001825401101561077d57600080fd5b60018154018155506020610c40600463c5f2892f610be052610bfc6000305af16107a657600080fd5b610c4051610bc0526060610ce060246380673289610c60526103e051610c8052610c7c6000305af16107d757600080fd5b610d00805160200180610d40828460006004600a8704601201f16107fa57600080fd5b50506040610dc052610dc051610e0052610420805160200180610dc051610e0001828460006004600a8704601201f161083257600080fd5b5050610dc051610e00015160206001820306601f8201039050610dc051610e0001610da08151610220818352015b83610da0511015156108715761088e565b6000610da0516020850101535b8151600101808352811415610860575b505050506020610dc051610e00015160206001820306601f8201039050610dc0510101610dc052610dc051610e2052610d40805160200180610dc051610e0001828460006004600a8704601201f16108e557600080fd5b5050610dc051610e00015160206001820306601f8201039050610dc051610e0001610da081516020818352015b83610da05110151561092357610940565b6000610da0516020850101535b8151600101808352811415610912575b505050506020610dc051610e00015160206001820306601f8201039050610dc0510101610dc0527f42dc88172194fbb332e0cb2fd0d4411b0b44a152a0d05a406b6790641bdefec0610dc051610e00a16407735940006103a0511415610d0a5760038054600182540110156109b457600080fd5b600181540181555060096003541415610d095742610e605242610e8052620151806109de57600080fd5b62015180610e805106610e605110156109f657600080fd5b42610e805262015180610a0857600080fd5b62015180610e805106610e6051036202a30042610e605242610e805262015180610a3157600080fd5b62015180610e805106610e60511015610a4957600080fd5b42610e805262015180610a5b57600080fd5b62015180610e805106610e605103011015610a7557600080fd5b6202a30042610e605242610e805262015180610a9057600080fd5b62015180610e805106610e60511015610aa857600080fd5b42610e805262015180610aba57600080fd5b62015180610e805106610e60510301610e40526060610f2060246380673289610ea052600254610ec052610ebc6000305af1610af557600080fd5b610f40805160200180610f80828460006004600a8704601201f1610b1857600080fd5b5050606061106060246380673289610fe052610e405161100052610ffc6000305af1610b4357600080fd5b6110808051602001806110c0828460006004600a8704601201f1610b6657600080fd5b5050610bc05161118052606061114052611140516111a052610f808051602001806111405161118001828460006004600a8704601201f1610ba657600080fd5b505061114051611180015160206001820306601f8201039050611140516111800161112081516020818352015b8361112051101515610be457610c01565b6000611120516020850101535b8151600101808352811415610bd3575b50505050602061114051611180015160206001820306601f820103905061114051010161114052611140516111c0526110c08051602001806111405161118001828460006004600a8704601201f1610c5857600080fd5b505061114051611180015160206001820306601f8201039050611140516111800161112081516020818352015b8361112051101515610c9657610cb3565b6000611120516020850101535b8151600101808352811415610c85575b50505050602061114051611180015160206001820306601f8201039050611140510101611140527f08b71ef3f1b58f7a23ffb82e27f12f0888c8403f1ceb0ea7ea26b274e2189d4c61114051611180a160016004555b5b005b63845980e86000511415610d32573415610d2557600080fd5b60045460005260206000f3005b60006000fd5b6101f2610f2a036101f26000396101f2610f2a036000f3" diff --git a/src/eth1/fake.ts b/src/eth1/fake.ts deleted file mode 100644 index 402247aece7d..000000000000 --- a/src/eth1/fake.ts +++ /dev/null @@ -1,50 +0,0 @@ -import BN from "bn.js"; -import {Deposit, DepositData, Eth1Data, number64} from "../types"; -import {EMPTY_SIGNATURE} from "../constants"; -import {intDiv} from "../util/math"; - - -interface DummyChainStart { - deposits: Deposit[]; - genesisTime: number64; - eth1Data: Eth1Data; -} - -function generateEthData(): Eth1Data { - return { - blockHash: Buffer.alloc(32), - depositRoot: Buffer.alloc(32), - depositCount: 0, - }; -} - -function generateFakeDeposits(): Deposit[] { - const deposits: Deposit[] = []; - - for (let i = 0; i < 10; i++) { - const data: DepositData = { - pubkey: Buffer.alloc(48), - withdrawalCredentials: Buffer.alloc(32), - amount: new BN(32).mul(new BN(10).muln(9)), // 32000000000 - signature: EMPTY_SIGNATURE, - }; - - const deposit: Deposit = { - proof: [Buffer.alloc(32)], - data, - index: i, - }; - deposits.push(deposit); - } - return deposits; -} - -export function getInitialDeposits(): DummyChainStart { - return { - deposits: generateFakeDeposits(), - eth1Data: generateEthData(), - genesisTime: intDiv(Date.now(), 1000), - }; -} - - diff --git a/src/eth1/ethers.ts b/src/eth1/impl/ethers.ts similarity index 69% rename from src/eth1/ethers.ts rename to src/eth1/impl/ethers.ts index 074a07279147..fc2996377d1d 100644 --- a/src/eth1/ethers.ts +++ b/src/eth1/impl/ethers.ts @@ -3,44 +3,60 @@ import {EventEmitter} from "events"; import {Contract, ethers} from "ethers"; import {deserialize} from "@chainsafe/ssz"; -import {bytes32, Deposit, DepositData, Eth1Data} from "../types"; +import {bytes32, Deposit, DepositData, Eth1Data, number64} from "../../types"; -import {Eth1Notifier, Eth1Options} from "./interface"; -import logger from "../logger"; -import {isValidAddress} from "../util/address"; +import {Eth1Notifier, Eth1Options} from "../interface"; +import logger from "../../logger"; +import {isValidAddress} from "../../util/address"; +import {DB} from "../../db"; export interface EthersEth1Options extends Eth1Options { provider: ethers.providers.BaseProvider; + db: DB; + contract?: Contract; } /** * Watch the Eth1.0 chain using Ethers */ export class EthersEth1Notifier extends EventEmitter implements Eth1Notifier { + private provider: ethers.providers.BaseProvider; + private contract: ethers.Contract; + private db: DB; + private _latestBlockHash: bytes32; + + private genesisBlockHash: number64; + private depositCount: number; - private chainStarted: boolean; + private opts: EthersEth1Options; + public constructor(opts: EthersEth1Options) { super(); this.opts = opts; this.provider = opts.provider; + this.contract = this.opts.contract; + this.db = opts.db; this.depositCount = 0; this._latestBlockHash = null; + this.genesisBlockHash = null; } public async start(): Promise { - await this.initContract(); - const latestBlockNumber = await this.provider.getBlockNumber(); - await this.processBlockHeadUpdate(latestBlockNumber); + if(!this.contract) { + await this.initContract(); + } this.provider.on('block', this.processBlockHeadUpdate.bind(this)); this.contract.on('Deposit', this.processDepositLog.bind(this)); this.contract.on('Eth2Genesis', this.processEth2GenesisLog.bind(this)); - logger.info(`Started listening on eth1 events on chain ${(await this.provider.getNetwork()).chainId}`); + logger.info( + `Started listening on eth1 events on chain ${(await this.provider.getNetwork()).chainId}` + ); } public async stop(): Promise { @@ -60,40 +76,55 @@ export class EthersEth1Notifier extends EventEmitter implements Eth1Notifier { const dataBuf = Buffer.from(dataHex.substr(2), 'hex'); const index = Buffer.from(indexHex.substr(2), 'hex').readUIntLE(0, 6); - logger.info(`Received validator deposit event index=${index}. Current deposit count=${this.depositCount}`); + logger.info( + `Received validator deposit event index=${index}. Current deposit count=${this.depositCount}` + ); if (index !== this.depositCount) { logger.warn(`Validator deposit with index=${index} received out of order.`); // deposit processed out of order return; } this.depositCount++; - const data: DepositData = deserialize(dataBuf, DepositData); - - // TODO: Add deposit to merkle trie/db - this.emit('deposit', data, index); + const deposit: Deposit = { + index: index, + //TODO: calculate proof + proof: [], + data + }; + //after genesis stop storing in genesisDeposit bucket + if(!this.genesisBlockHash) { + await this.db.setGenesisDeposit(deposit); + } + this.emit('deposit', deposit); } - public async processEth2GenesisLog(depositRootHex: string, depositCountHex: string, timeHex: string, event: ethers.Event): Promise { + public async processEth2GenesisLog( + depositRootHex: string, + depositCountHex: string, + timeHex: string, + event: ethers.Event + ): Promise { const depositRoot = Buffer.from(depositRootHex.substr(2), 'hex'); const depositCount = Buffer.from(depositCountHex.substr(2), 'hex').readUIntLE(0, 6); const time = new BN(Buffer.from(timeHex.substr(2), 'hex').readUIntLE(0, 6)); const blockHash = Buffer.from(event.blockHash.substr(2), 'hex'); + this.genesisBlockHash = event.blockNumber; logger.info(`Received Eth2Genesis event. blockNumber=${event.blockNumber}, time=${time}`); - // TODO: Ensure the deposit root is the same that we've stored const genesisEth1Data: Eth1Data = { depositRoot, blockHash, depositCount, }; - - this.chainStarted = true; - this.emit('eth2genesis', time, await this.genesisDeposits(), genesisEth1Data); + const genesisDeposits = await this.genesisDeposits(); + this.emit('eth2genesis', time, genesisDeposits, genesisEth1Data); + //from now on it will be kept in BeaconBlock + await this.db.deleteGenesisDeposits(genesisDeposits); } public async genesisDeposits(): Promise { - return []; + return this.db.getGenesisDeposits(); } public latestBlockHash(): bytes32 { @@ -105,10 +136,6 @@ export class EthersEth1Notifier extends EventEmitter implements Eth1Notifier { return Buffer.from(depositRootHex.substr(2), 'hex'); } - public setContract(contract: Contract) { - this.contract = contract; - } - private async initContract(): Promise { const address = this.opts.depositContract.address; const abi = this.opts.depositContract.abi; diff --git a/src/eth1/mock.ts b/src/eth1/impl/mock.ts similarity index 67% rename from src/eth1/mock.ts rename to src/eth1/impl/mock.ts index c330e3ba351c..985f32ec4f82 100644 --- a/src/eth1/mock.ts +++ b/src/eth1/impl/mock.ts @@ -1,8 +1,8 @@ import {EventEmitter} from "events"; -import {bytes32, DepositData, Deposit, Eth1Data} from "../types"; +import {bytes32, Deposit} from "../../types"; -import {Eth1Notifier, Eth1Options} from "./interface"; +import {Eth1Notifier, Eth1Options} from "../interface"; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface MockEth1Options extends Eth1Options { @@ -15,18 +15,31 @@ export class MockEth1Notifier extends EventEmitter implements Eth1Notifier { public async start(): Promise { } + public async stop(): Promise { } - public async processBlockHeadUpdate(blockNumber): Promise {} - public async processDepositLog(dataHex: string, indexHex: string): Promise {} - public async processEth2GenesisLog(depositRootHex: string, depositCountHex: string, timeHex: string, event: object): Promise {} + public async processBlockHeadUpdate(blockNumber): Promise { + } + + public async processDepositLog(dataHex: string, indexHex: string): Promise { + } + + public async processEth2GenesisLog( + depositRootHex: string, + depositCountHex: string, + timeHex: string, event: object + ): Promise { + } + public async genesisDeposits(): Promise { return []; } - public async latestBlockHash(): Promise { + + public latestBlockHash(): bytes32 { return Buffer.alloc(32); } + public async depositRoot(): Promise { return Buffer.alloc(32); } diff --git a/src/eth1/index.ts b/src/eth1/index.ts index 0531efe1a0d0..3e20a887dd18 100644 --- a/src/eth1/index.ts +++ b/src/eth1/index.ts @@ -1,4 +1,4 @@ export {Eth1Notifier, Eth1Options} from "./interface"; -export {MockEth1Notifier, MockEth1Options} from "./mock"; -export {EthersEth1Notifier, EthersEth1Options} from "./ethers"; +export {MockEth1Notifier, MockEth1Options} from "./impl/mock"; +export {EthersEth1Notifier, EthersEth1Options} from "./impl/ethers"; export {Eth1Wallet} from "./wallet"; diff --git a/test/e2e/eth1/deploy.test.ts b/test/e2e/eth1/deploy.test.ts index 2b523c086e4b..e036b61cb8b4 100644 --- a/test/e2e/eth1/deploy.test.ts +++ b/test/e2e/eth1/deploy.test.ts @@ -15,6 +15,7 @@ describe("Eth1Notifier - using deployed contract", () => { let provider; before(async function() { + this.timeout(0); logger.silent(true); // deploy deposit contract eth1Network = new PrivateEth1Network({ @@ -71,6 +72,7 @@ describe("Eth1Notifier - using deployed contract", () => { ) ) ); + console.log(await eth1Notifier.genesisDeposits('latest')); assert(cb.called, "eth2genesis event did not fire"); }); diff --git a/test/unit/eth1/ethers.test.ts b/test/unit/eth1/ethers.test.ts index 7063cc0e8e94..227aa6494cd1 100644 --- a/test/unit/eth1/ethers.test.ts +++ b/test/unit/eth1/ethers.test.ts @@ -3,41 +3,96 @@ import {Contract, ethers, Event} from "ethers"; import ganache from "ganache-core"; import sinon from "sinon"; -import { EthersEth1Notifier } from "../../../src/eth1"; +import {EthersEth1Notifier} from "../../../src/eth1"; import defaults from "../../../src/eth1/defaults"; import promisify from "promisify-es6"; import logger from "../../../src/logger/winston"; import chaiAsPromised from "chai-as-promised"; +import {generateDeposit} from "../../utils/deposit"; chai.use(chaiAsPromised); describe("Eth1Notifier", () => { const ganacheProvider = ganache.provider(); const provider = new ethers.providers.Web3Provider(ganacheProvider); - const eth1 = new EthersEth1Notifier({ - depositContract: defaults.depositContract, - provider, - }); + let db: any = {}; + let eth1; + let sandbox; - before(async function(): Promise { + before(async function (): Promise { logger.silent(true); + sandbox = sinon.createSandbox(); + db.setGenesisDeposit = sandbox.stub(); + db.getGenesisDeposits = sandbox.stub(); + db.deleteGenesisDeposits = sandbox.stub(); + eth1 = new EthersEth1Notifier({ + depositContract: defaults.depositContract, + provider, + db + }); }); - it("should fail to start because there isn't contract at given address", async function(): Promise { - await expect(eth1.start()).to.be.rejectedWith('There is no deposit contract at given address'); + after(async () => { + sandbox.restore(); + await promisify(ganacheProvider.close)(); + logger.silent(false); }); - it("should process a Deposit log", async function() { + it( + "should fail to start because there isn't contract at given address", + async function (): Promise { + await expect(eth1.start()) + .to.be.rejectedWith('There is no deposit contract at given address'); + } + ); + + it( + "should start notifier", + async function (): Promise { + const contract = sinon.createStubInstance(Contract); + const notifier = new EthersEth1Notifier({ + depositContract: defaults.depositContract, + provider, + db, + // @ts-ignore + contract + }); + contract.on.returns(null); + await notifier.start(); + expect(contract.on.withArgs('Deposit', sinon.match.any).calledOnce).to.be.true; + expect(contract.on.withArgs('Eth2Genesis', sinon.match.any).calledOnce).to.be.true; + } + ); + + it( + "should stop notifier", + async function (): Promise { + const contract = sinon.createStubInstance(Contract); + const notifier = new EthersEth1Notifier({ + depositContract: defaults.depositContract, + provider, + db, + // @ts-ignore + contract + }); + contract.removeAllListeners.returns(null); + await notifier.stop(); + expect(contract.removeAllListeners.withArgs('Deposit').calledOnce).to.be.true; + expect(contract.removeAllListeners.withArgs('Eth2Genesis').calledOnce).to.be.true; + } + ); + + it("should process a Deposit log", async function () { const cb = sinon.spy(); eth1.on('deposit', cb); const dataHex = "0x" + Buffer.alloc(528).toString("hex"); const indexHex = "0x" + Buffer.alloc(528).toString("hex"); - - eth1.processDepositLog(dataHex, indexHex); + db.setGenesisDeposit.resolves(null); + await eth1.processDepositLog(dataHex, indexHex); assert(cb.calledOnce, "deposit event did not fire"); }); - it("should process a Eth2Genesis log", async function() { + it("should process a Eth2Genesis log", async function () { const cb = sinon.spy(); eth1.on("eth2genesis", cb); @@ -47,13 +102,21 @@ describe("Eth1Notifier", () => { const unixTimeNow = Math.floor(Date.now() / 1000); timeBuf.writeUInt32LE(unixTimeNow, 0); const timeHex = "0x" + timeBuf.toString("hex"); - const event = { blockHash: "0x0000000000000000" } as Event; - + const event = {blockHash: "0x0000000000000000"} as Event; + const genesisDeposits = [ + generateDeposit(0), + generateDeposit(1), + ]; + db.getGenesisDeposits.resolves(genesisDeposits); + db.deleteGenesisDeposits.withArgs(genesisDeposits).resolves(null); await eth1.processEth2GenesisLog(depositRootHex, depositCountHex, timeHex, event); - assert(cb.calledOnce, "eth2genesis event did not fire"); + assert( + cb.withArgs(sinon.match.any, genesisDeposits, sinon.match.any).calledOnce, + "eth2genesis event did not fire" + ); }); - it("should process a new block", async function(): Promise { + it("should process a new block", async function (): Promise { this.timeout(0); const cb = sinon.spy(); @@ -63,7 +126,7 @@ describe("Eth1Notifier", () => { assert(cb.calledOnce, "new block event did not fire"); }); - it("should get latest block hash", async function(): Promise { + it("should get latest block hash", async function (): Promise { this.timeout(0); await eth1.processBlockHeadUpdate(0); @@ -71,23 +134,25 @@ describe("Eth1Notifier", () => { expect(eth1.latestBlockHash().length).to.be.equal(32); }); - it("should get deposit root from contract", async function(): Promise { + it("should get deposit root from contract", async function (): Promise { const spy = sinon.stub(); - const contract = { + // @ts-ignore + const contract: Contract = { // eslint-disable-next-line get_deposit_root: spy }; - eth1.setContract(contract as any); + const notifier = new EthersEth1Notifier({ + depositContract: defaults.depositContract, + provider, + db, + contract + }); const testDepositRoot = Buffer.alloc(32); spy.resolves('0x' + testDepositRoot.toString('hex')); - const depositRoot = await eth1.depositRoot(); + const depositRoot = await notifier.depositRoot(); expect(depositRoot).to.be.deep.equal(testDepositRoot); }); - after(async function(): Promise { - await promisify(ganacheProvider.close)(); - logger.silent(false); - }); }); From 40d22e3e3004d8c2238eaa2317a024da5f87fef9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marin=20Petruni=C4=87?= Date: Wed, 1 May 2019 13:42:00 +0200 Subject: [PATCH 7/9] test fixes --- src/node/index.ts | 5 +- test/e2e/eth1/deploy.test.ts | 14 +++- test/e2e/eth1/dev/network.test.ts | 5 +- test/unit/eth1/deploy.ts | 77 -------------------- test/unit/util/math.test.ts | 113 ++++++++++++++++++++++++------ 5 files changed, 112 insertions(+), 102 deletions(-) delete mode 100644 test/unit/eth1/deploy.ts diff --git a/src/node/index.ts b/src/node/index.ts index de14196028e9..13cff774f449 100644 --- a/src/node/index.ts +++ b/src/node/index.ts @@ -51,7 +51,10 @@ class BeaconNode { this.db = new LevelDB(this.conf.db); this.network = new P2PNetwork(this.conf.network); - this.eth1 = new EthersEth1Notifier(this.conf.eth1); + this.eth1 = new EthersEth1Notifier({ + ...this.conf.eth1, + db: this.db + }); this.sync = new Sync(this.conf.sync, { network: this.network, }); diff --git a/test/e2e/eth1/deploy.test.ts b/test/e2e/eth1/deploy.test.ts index e036b61cb8b4..6dcc98330ebb 100644 --- a/test/e2e/eth1/deploy.test.ts +++ b/test/e2e/eth1/deploy.test.ts @@ -1,4 +1,4 @@ -import { assert } from "chai"; +import {assert} from "chai"; import {ethers} from "ethers"; import sinon from "sinon"; @@ -6,6 +6,8 @@ import {Eth1Wallet, EthersEth1Notifier} from "../../../src/eth1"; import {depositContract} from "../../../src/eth1/dev/defaults"; import {PrivateEth1Network} from "../../../src/eth1/dev"; import logger from "../../../src/logger/winston"; +import {LevelDB} from "../../../src/db"; +import level from "level"; describe("Eth1Notifier - using deployed contract", () => { @@ -13,6 +15,13 @@ describe("Eth1Notifier - using deployed contract", () => { let eth1Network; let depositContractAddress; let provider; + const dbLocation = "./.__testdb"; + const testDb = level( + dbLocation, { + keyEncoding: 'binary', + valueEncoding: 'binary', + }); + const db = new LevelDB({db: testDb}); before(async function() { this.timeout(0); @@ -32,7 +41,8 @@ describe("Eth1Notifier - using deployed contract", () => { ...depositContract, address: depositContractAddress, }, - provider + provider, + db }); await eth1Notifier.start(); }); diff --git a/test/e2e/eth1/dev/network.test.ts b/test/e2e/eth1/dev/network.test.ts index 6a16d03d753c..498f751d1aab 100644 --- a/test/e2e/eth1/dev/network.test.ts +++ b/test/e2e/eth1/dev/network.test.ts @@ -4,7 +4,7 @@ import {Wallet} from "ethers"; import * as ethers from "ethers/ethers"; import {expect} from "chai"; -describe('Eth1 dev network', () => { +describe('Eth1 dev network', function () { before(() => { logger.silent(true); @@ -32,7 +32,8 @@ describe('Eth1 dev network', () => { await network.stop(); }); - it('should deploy deposit contract', async () => { + it('should deploy deposit contract', async function() { + this.timeout(3000); const network = new PrivateEth1Network({ host: '127.0.0.1', port: 34567, diff --git a/test/unit/eth1/deploy.ts b/test/unit/eth1/deploy.ts deleted file mode 100644 index a66be5941361..000000000000 --- a/test/unit/eth1/deploy.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { assert } from "chai"; -import {ethers} from "ethers"; -import sinon from "sinon"; - -import {Eth1Wallet, EthersEth1Notifier} from "../../../src/eth1"; -import {depositContract} from "../../../src/eth1/dev/defaults"; -import {PrivateEth1Network} from "../../../src/eth1/dev"; -import logger from "../../../src/logger/winston"; - -describe("Eth1Notifier - using deployed contract", () => { - - let eth1Notifier; - let eth1Network; - let depositContractAddress; - let provider; - - before(async function() { - logger.silent(true); - // deploy deposit contract - eth1Network = new PrivateEth1Network({ - host: '127.0.0.1', - port: 34569 - }); - await eth1Network.start(); - depositContractAddress = await eth1Network.deployDepositContract(); - provider = new ethers.providers.JsonRpcProvider('http://127.0.0.1:34569'); - provider.pollingInterval = 1; - provider.polling = true; - eth1Notifier = new EthersEth1Notifier({ - depositContract: { - ...depositContract, - address: depositContractAddress, - }, - provider - }); - await eth1Notifier.start(); - }); - - after(async() => { - await eth1Notifier.stop(); - await eth1Network.stop(); - logger.silent(false); - }); - - it("should process a Deposit log", async function() { - this.timeout(6000); - const wallet = new Eth1Wallet(eth1Network.accounts()[0], provider); - - const cb = sinon.spy(); - eth1Notifier.on('deposit', cb); - - - await wallet.createValidatorDeposit(depositContractAddress, ethers.utils.parseEther('32.0')); - - assert(cb.calledOnce, "deposit event did not fire"); - }); - - it("should process a Eth2Genesis log", async function() { - this.timeout(30000); - - const cb = sinon.spy(); - eth1Notifier.on('eth2genesis', cb); - await Promise.all( - eth1Network - .accounts() - .map((account) => - (new Eth1Wallet(account, provider)) - .createValidatorDeposit( - depositContractAddress, - ethers.utils.parseEther('32.0') - ) - ) - ); - assert(cb.called, "eth2genesis event did not fire"); - }); - -}); diff --git a/test/unit/util/math.test.ts b/test/unit/util/math.test.ts index 309db4bdba93..e9f7746699e3 100644 --- a/test/unit/util/math.test.ts +++ b/test/unit/util/math.test.ts @@ -2,32 +2,105 @@ import { assert } from "chai"; import { intSqrt, + bnMin, bnMax, intDiv, bnSqrt } from "../../../src/util/math"; +import BN from "bn.js"; -describe("intSqrt", () => { - it("0 should return 0", () => { - const result = intSqrt(0); - assert.equal(result, 0, "Should have returned 0!"); - }); - it("1 should return 1", () => { - const result = intSqrt(1); - assert.equal(result, 1, "Should have returned 1!"); +describe('util/maths', function() { + + describe("bnMin", () => { + it("if a is lt should return a", () => { + const a = new BN('1'); + const b = new BN('2'); + const result = bnMin(a, b); + assert.equal(result, a, "Should have returned a!"); + }); + it("if b is lt should return b", () => { + const a = new BN('3'); + const b = new BN('2'); + const result = bnMin(a, b); + assert.equal(result, b, "Should have returned b!"); + }); }); - it("3 should return 1", () => { - const result = intSqrt(3); - assert.equal(result, 1, "Should have returned 1!"); + + describe("bnMax", () => { + it("if a is gt should return a", () => { + const a = new BN('2'); + const b = new BN('1'); + const result = bnMax(a, b); + assert.equal(result, a, "Should have returned a!"); + }); + it("if b is gt should return b", () => { + const a = new BN('2'); + const b = new BN('3'); + const result = bnMax(a, b); + assert.equal(result, b, "Should have returned b!"); + }); }); - it("4 should return 2", () => { - const result = intSqrt(4); - assert.equal(result, 2, "Should have returned 2!"); + + describe("intDiv", () => { + it("should divide whole number", () => { + const result = intDiv(6, 3); + assert.equal(result, 2, "Should have returned 2!"); + }); + it("should round less division", () => { + const result = intDiv(9, 8); + assert.equal(result, 1, "Should have returned 1!"); + }); }); - it("16 should return 4", () => { - const result = intSqrt(16); - assert.equal(result, 4, "Should have returned 4!"); + + describe("intSqrt", () => { + it("0 should return 0", () => { + const result = intSqrt(0); + assert.equal(result, 0, "Should have returned 0!"); + }); + it("1 should return 1", () => { + const result = intSqrt(1); + assert.equal(result, 1, "Should have returned 1!"); + }); + it("3 should return 1", () => { + const result = intSqrt(3); + assert.equal(result, 1, "Should have returned 1!"); + }); + it("4 should return 2", () => { + const result = intSqrt(4); + assert.equal(result, 2, "Should have returned 2!"); + }); + it("16 should return 4", () => { + const result = intSqrt(16); + assert.equal(result, 4, "Should have returned 4!"); + }); + it("31 should return 5", () => { + const result = intSqrt(31); + assert.equal(result, 5, "Should have returned 5!"); + }); }); - it("31 should return 5", () => { - const result = intSqrt(31); - assert.equal(result, 5, "Should have returned 5!"); + + describe("bnSqrt", () => { + it("0 should return 0", () => { + const result = bnSqrt(new BN('0')); + assert.equal(result.toString(), new BN('0').toString(), "Should have returned 0!"); + }); + it("1 should return 1", () => { + const result = bnSqrt(new BN('1')); + assert.equal(result.toString(), new BN('1').toString(), "Should have returned 1!"); + }); + it("3 should return 1", () => { + const result = bnSqrt(new BN('3')); + assert.equal(result.toString(), new BN('1').toString(), "Should have returned 1!"); + }); + it("4 should return 2", () => { + const result = bnSqrt(new BN('4')); + assert.equal(result.toString(), new BN('2').toString(), "Should have returned 2!"); + }); + it("16 should return 4", () => { + const result = bnSqrt(new BN('16')); + assert.equal(result.toString(), new BN('4').toString(), "Should have returned 4!"); + }); + it("31 should return 5", () => { + const result = bnSqrt(new BN('31')); + assert.equal(result.toString(), new BN('5').toString(), "Should have returned 5!"); + }); }); }); From f7f6c8fb46e72e70a83e0c33f83d414e7ed3ed4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marin=20Petruni=C4=87?= Date: Thu, 2 May 2019 11:01:34 +0200 Subject: [PATCH 8/9] switch e2e tests to in memory database --- test/e2e/eth1/deploy.test.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/test/e2e/eth1/deploy.test.ts b/test/e2e/eth1/deploy.test.ts index 6dcc98330ebb..e949567c4337 100644 --- a/test/e2e/eth1/deploy.test.ts +++ b/test/e2e/eth1/deploy.test.ts @@ -6,7 +6,7 @@ import {Eth1Wallet, EthersEth1Notifier} from "../../../src/eth1"; import {depositContract} from "../../../src/eth1/dev/defaults"; import {PrivateEth1Network} from "../../../src/eth1/dev"; import logger from "../../../src/logger/winston"; -import {LevelDB} from "../../../src/db"; +import {LevelDB, PouchDb} from "../../../src/db"; import level from "level"; describe("Eth1Notifier - using deployed contract", () => { @@ -15,13 +15,7 @@ describe("Eth1Notifier - using deployed contract", () => { let eth1Network; let depositContractAddress; let provider; - const dbLocation = "./.__testdb"; - const testDb = level( - dbLocation, { - keyEncoding: 'binary', - valueEncoding: 'binary', - }); - const db = new LevelDB({db: testDb}); + const db = new PouchDb({name: 'testDb'}); before(async function() { this.timeout(0); @@ -82,7 +76,7 @@ describe("Eth1Notifier - using deployed contract", () => { ) ) ); - console.log(await eth1Notifier.genesisDeposits('latest')); + await eth1Notifier.genesisDeposits('latest'); assert(cb.called, "eth2genesis event did not fire"); }); From eefabc3b29c1c636407468d86b5e567cf3bbab71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marin=20Petruni=C4=87?= Date: Thu, 2 May 2019 11:11:15 +0200 Subject: [PATCH 9/9] address Pr comments --- src/eth1/impl/ethers.ts | 5 ++--- src/node/index.ts | 10 ++++++---- test/e2e/eth1/deploy.test.ts | 6 ++---- test/unit/eth1/ethers.test.ts | 14 +++++--------- 4 files changed, 15 insertions(+), 20 deletions(-) diff --git a/src/eth1/impl/ethers.ts b/src/eth1/impl/ethers.ts index fc2996377d1d..f91c303cef51 100644 --- a/src/eth1/impl/ethers.ts +++ b/src/eth1/impl/ethers.ts @@ -12,7 +12,6 @@ import {DB} from "../../db"; export interface EthersEth1Options extends Eth1Options { provider: ethers.providers.BaseProvider; - db: DB; contract?: Contract; } @@ -36,12 +35,12 @@ export class EthersEth1Notifier extends EventEmitter implements Eth1Notifier { private opts: EthersEth1Options; - public constructor(opts: EthersEth1Options) { + public constructor(opts: EthersEth1Options, {db}) { super(); this.opts = opts; this.provider = opts.provider; this.contract = this.opts.contract; - this.db = opts.db; + this.db = db; this.depositCount = 0; this._latestBlockHash = null; this.genesisBlockHash = null; diff --git a/src/node/index.ts b/src/node/index.ts index 13cff774f449..ecfdd9ad4b9b 100644 --- a/src/node/index.ts +++ b/src/node/index.ts @@ -51,10 +51,12 @@ class BeaconNode { this.db = new LevelDB(this.conf.db); this.network = new P2PNetwork(this.conf.network); - this.eth1 = new EthersEth1Notifier({ - ...this.conf.eth1, - db: this.db - }); + this.eth1 = new EthersEth1Notifier( + this.conf.eth1, + { + db: this.db + } + ); this.sync = new Sync(this.conf.sync, { network: this.network, }); diff --git a/test/e2e/eth1/deploy.test.ts b/test/e2e/eth1/deploy.test.ts index e949567c4337..7ccd13a685ce 100644 --- a/test/e2e/eth1/deploy.test.ts +++ b/test/e2e/eth1/deploy.test.ts @@ -6,8 +6,7 @@ import {Eth1Wallet, EthersEth1Notifier} from "../../../src/eth1"; import {depositContract} from "../../../src/eth1/dev/defaults"; import {PrivateEth1Network} from "../../../src/eth1/dev"; import logger from "../../../src/logger/winston"; -import {LevelDB, PouchDb} from "../../../src/db"; -import level from "level"; +import {PouchDb} from "../../../src/db"; describe("Eth1Notifier - using deployed contract", () => { @@ -36,8 +35,7 @@ describe("Eth1Notifier - using deployed contract", () => { address: depositContractAddress, }, provider, - db - }); + }, {db}); await eth1Notifier.start(); }); diff --git a/test/unit/eth1/ethers.test.ts b/test/unit/eth1/ethers.test.ts index 227aa6494cd1..5b0ef885d8df 100644 --- a/test/unit/eth1/ethers.test.ts +++ b/test/unit/eth1/ethers.test.ts @@ -26,9 +26,8 @@ describe("Eth1Notifier", () => { db.deleteGenesisDeposits = sandbox.stub(); eth1 = new EthersEth1Notifier({ depositContract: defaults.depositContract, - provider, - db - }); + provider + }, {db}); }); after(async () => { @@ -52,10 +51,9 @@ describe("Eth1Notifier", () => { const notifier = new EthersEth1Notifier({ depositContract: defaults.depositContract, provider, - db, // @ts-ignore contract - }); + }, {db}); contract.on.returns(null); await notifier.start(); expect(contract.on.withArgs('Deposit', sinon.match.any).calledOnce).to.be.true; @@ -70,10 +68,9 @@ describe("Eth1Notifier", () => { const notifier = new EthersEth1Notifier({ depositContract: defaults.depositContract, provider, - db, // @ts-ignore contract - }); + }, {db}); contract.removeAllListeners.returns(null); await notifier.stop(); expect(contract.removeAllListeners.withArgs('Deposit').calledOnce).to.be.true; @@ -144,9 +141,8 @@ describe("Eth1Notifier", () => { const notifier = new EthersEth1Notifier({ depositContract: defaults.depositContract, provider, - db, contract - }); + }, {db}); const testDepositRoot = Buffer.alloc(32); spy.resolves('0x' + testDepositRoot.toString('hex'));