UNPKG

leap-node

Version:

[![codecov](https://codecov.io/gh/leapdao/leap-node/branch/master/graph/badge.svg)](https://codecov.io/gh/leapdao/leap-node) [![Docker Repository on Quay](https://quay.io/repository/leapdao/leap-node/status "Docker Repository on Quay")](https://quay.io/re

399 lines (339 loc) 10.2 kB
const { Tx, Block, Input, Outpoint, Output } = require('leap-core'); const createDb = require('./createDb'); const ADDR_1 = '0xB8205608d54cb81f44F263bE086027D8610F3C94'; const PRIV = '0x9b63fe8147edb8d251a6a66fd18c0ed73873da9fff3f08ea202e1c0a8ead7311'; const levelBatch = { put: jest.fn(() => Promise.resolve()), write: resolve => setImmediate(resolve), }; const levelMock = { put: jest.fn(() => Promise.resolve()), get: jest.fn(() => Promise.resolve()), batch() { return levelBatch; }, }; describe('db', () => { test('getLastBlockSynced', async () => { let methodCalled = false; const level = { async get(key) { methodCalled = key === 'lastBlockSynced'; if (!methodCalled) { throw new Error('lastBlockSynced key expected'); } return 10; }, }; const db = createDb(level); expect(await db.getLastBlockSynced()).toBe(10); expect(methodCalled).toBe(true); }); test('getLastBlockSynced: notFound', async () => { let methodCalled = false; const level = { async get(key) { methodCalled = key === 'lastBlockSynced'; throw new Error('Error'); }, }; const db = createDb(level); expect(await db.getLastBlockSynced()).toBe(0); expect(methodCalled).toBe(true); }); test('getTransaction: null', async () => { let methodCalled = false; const txHash = '0x00001'; const level = { async get(key) { methodCalled = key === `tx!${txHash}`; if (!methodCalled) { throw new Error('tx!$txHash key expected'); } const err = new Error('NotFoundError'); err.type = 'NotFoundError'; throw err; }, }; const db = createDb(level); expect(await db.getTransaction(txHash)).toBe(null); expect(methodCalled).toBe(true); }); test('getTransaction: exception', async () => { let methodCalled = false; const txHash = '0x00001'; const level = { async get(key) { methodCalled = key === `tx!${txHash}`; if (!methodCalled) { throw new Error('tx!$txHash key expected'); } const err = new Error('Random error'); throw err; }, }; const db = createDb(level); let error; try { await db.getTransaction(txHash); } catch (err) { error = err; } expect(error.message).toBe('Random error'); }); test('getTransaction: document', async () => { let methodCalled = false; const txHash = '0x00001'; const level = { async get(key) { methodCalled = key === `tx!${txHash}`; if (!methodCalled) { throw new Error('tx!$txHash key expected'); } return JSON.stringify({ hash: txHash, data: '0x001', }); }, }; const db = createDb(level); expect(await db.getTransaction(txHash)).toEqual({ hash: txHash, data: '0x001', }); expect(methodCalled).toBe(true); }); test('getBlock: number', async () => { let methodCalled = false; const number = 0; const level = { async get(key) { methodCalled = key === `block!${number}`; if (!methodCalled) { throw new Error('block!$hashOrNum key expected'); } // return block hash return '0x0000000000000000000000'; }, }; const db = createDb(level); expect(await db.getBlock(number)).toEqual('0x0000000000000000000000'); expect(methodCalled).toBe(true); }); test('storeBlock', async () => { const block = new Block(0); const out1 = new Outpoint( '0x7777777777777777777777777777777777777777777777777777777777777777', 0 ); const out2 = new Outpoint( '0x6666666666666666666666666666666666666666666666666666666666666666', 1 ); const tx = Tx.transfer( [new Input(out1), new Input(out2)], [new Output(100, ADDR_1, 0)] ).signAll(PRIV); block.addTx(tx); let calls = []; const level = { batch() { return { put(...args) { calls.push(args); }, write(resolve) { setImmediate(resolve); }, }; }, }; const db = createDb(level); await db.storeBlock(block); expect(calls.length).toBe(6); expect(calls[0]).toEqual([ `tx!${tx.hash()}`, JSON.stringify({ txData: tx.toJSON(), blockHash: block.hash(), height: block.height, txPos: 0, }), ]); expect(calls[1]).toEqual([ `out!0x7777777777777777777777777777777777777777777777777777777777777777:0`, `tx!${tx.hash()}`, ]); expect(calls[2]).toEqual([ `out!0x6666666666666666666666666666666666666666666666666666666666666666:1`, `tx!${tx.hash()}`, ]); expect(calls[3]).toEqual([ `block!${block.hash()}`, JSON.stringify({ blockData: block.toJSON(), height: block.height, }), ]); expect(calls[4]).toEqual([`block!${block.height}`, block.hash()]); expect(calls[5]).toEqual(['lastBlockSynced', block.height]); // adding deposit should not add prevOut records calls = []; const deposit = Tx.deposit(1, 100, ADDR_1, 0); const block1 = new Block(1); block1.addTx(deposit); await db.storeBlock(block1); expect(calls.length).toBe(4); }); test('getTransactionByPrevOut', async () => { let methodCalled = false; const prevTxHash = '0x6666666666666666666666666666666666666666666666666666666666666666'; const outIndex = 1; const utxo = `${prevTxHash}:${outIndex}`; const tx = Tx.transfer( [new Input(new Outpoint(prevTxHash, outIndex))], [new Output(100, ADDR_1, 0)] ).signAll(PRIV); let callCount = 0; const level = { async get(key) { methodCalled = true; callCount += 1; if (callCount === 1 && key === `out!${utxo}`) { // return tx hash return `tx!${tx.hash()}`; } if (callCount === 2 && key === `tx!${tx.hash()}`) { return JSON.stringify({ txData: tx.toJSON(), blockHash: '0x3447', height: 2, txPos: 0, }); } methodCalled = false; throw new Error('no call or unexpected call:', key); }, }; const db = createDb(level); expect(await db.getTransactionByPrevOut(utxo)).toEqual({ txData: tx.toJSON(), blockHash: '0x3447', height: 2, txPos: 0, }); expect(methodCalled).toBe(true); }); describe('#storeSubmission', () => { const submission1 = { blocksRoot: '0x123', casBitmap: '0x200', slotId: 1, validatorAddress: ADDR_1, }; const submission2 = { blocksRoot: '0x456', casBitmap: '0x100', slotId: 2, validatorAddress: ADDR_1, }; test('single submission per period', async () => { levelMock.get = jest.fn().mockRejectedValue({ type: 'NotFoundError' }); const db = createDb(levelMock); await db.storeSubmission(32, submission1); await db.storeSubmission(64, submission2); expect(levelMock.batch().put).toHaveBeenNthCalledWith( 1, 'period!0x123', 'period!32' ); expect(levelMock.batch().put).toHaveBeenNthCalledWith( 2, 'period!32', JSON.stringify(submission1) ); expect(levelMock.batch().put).toHaveBeenNthCalledWith( 3, 'period!0x456', 'period!64' ); expect(levelMock.batch().put).toHaveBeenNthCalledWith( 4, 'period!64', JSON.stringify(submission2) ); }); test('skip saving if submission already exists', async () => { const sameRootSubmission = { blocksRoot: '0x123', casBitmap: '0x300', slotId: 2, validatorAddress: ADDR_1, }; levelMock.get .mockResolvedValue('period!32') .mockResolvedValue(submission1); const db = createDb(levelMock); await db.storeSubmission(32, sameRootSubmission); expect(levelMock.batch().put).not.toHaveBeenCalled(); await db.storeSubmission(32, submission2); expect(levelMock.batch().put).toHaveBeenNthCalledWith( 1, 'period!0x456', 'period!32' ); expect(levelMock.batch().put).toHaveBeenNthCalledWith( 2, 'period!32', JSON.stringify(submission2) ); }); }); describe('nodeState', () => { test('store', async () => { const state = { someState: { state: 1 } }; const db = createDb(levelMock); await db.storeNodeState(state); expect(levelMock.put).toHaveBeenCalledWith( 'nodeState', JSON.stringify(state) ); }); test('read', async () => { const db = createDb(levelMock); await db.getNodeState(); expect(levelMock.get).toHaveBeenCalledWith('nodeState'); }); }); describe('lastSeenRootChainBlock', () => { test('#setLastSeenRootChainBlock', async () => { const db = createDb(levelMock); await db.setLastSeenRootChainBlock(4); expect(levelMock.put).toHaveBeenCalledWith('lastSeenRootChainBlock', 4); }); test('#getLastSeenRootChainBlock', async () => { levelMock.get = jest.fn().mockResolvedValue(4); const db = createDb(levelMock); const value = await db.getLastSeenRootChainBlock(); expect(levelMock.get).toHaveBeenCalledWith('lastSeenRootChainBlock'); expect(value).toEqual(4); }); }); test('#setStalePeriodProposal', async () => { const db = createDb(levelMock); await db.setStalePeriodProposal({ prop: 1 }); expect(levelMock.put).toHaveBeenCalledWith( 'stalePeriodProposal', JSON.stringify({ prop: 1 }) ); }); test('#getStalePeriodProposal', async () => { levelMock.get = jest.fn().mockResolvedValue(JSON.stringify({ prop: 1 })); const db = createDb(levelMock); const value = await db.getStalePeriodProposal(); expect(levelMock.get).toHaveBeenCalledWith('stalePeriodProposal'); expect(value).toEqual({ prop: 1 }); }); });