@node-dlc/wire
Version:
Lightning Network Wire Protocol
380 lines (308 loc) • 11.6 kB
text/typescript
// tslint:disable: no-unused-expression
import { ShortChannelId } from '@node-dlc/common';
import { expect, use } from 'chai';
import { GossipRelay } from '../../lib/gossip/GossipRelay';
import { GossipRelayState } from '../../lib/gossip/GossipRelay';
import { ChannelAnnouncementMessage } from '../../lib/messages/ChannelAnnouncementMessage';
import { ChannelUpdateMessage } from '../../lib/messages/ChannelUpdateMessage';
import { NodeAnnouncementMessage } from '../../lib/messages/NodeAnnouncementMessage';
import { createFakeLogger, createFakePeer, wait } from '../_test-utils';
describe('GossipRelay', () => {
let sut: GossipRelay;
function createChanAnn(
block: number,
tx: number,
output: number,
): ChannelAnnouncementMessage {
const msg = new ChannelAnnouncementMessage();
msg.shortChannelId = new ShortChannelId(block, tx, output);
return msg;
}
function createChanUpd(
block: number,
tx: number,
output: number,
timestamp: number,
): ChannelUpdateMessage {
const msg = new ChannelUpdateMessage();
msg.shortChannelId = new ShortChannelId(block, tx, output);
msg.timestamp = timestamp;
return msg;
}
function createNodeAnn(
nodeIdHex: string,
timestamp: number,
): NodeAnnouncementMessage {
const msg = new NodeAnnouncementMessage();
msg.nodeId = Buffer.from(nodeIdHex, 'hex');
msg.timestamp = timestamp;
return msg;
}
beforeEach(() => {
sut = new GossipRelay(createFakeLogger(), 100, 3);
});
describe('state: Inactive', () => {
describe('on .start()', () => {
beforeEach(() => {
sut.start();
});
afterEach(() => {
sut.stop();
});
it('starts periodic flushing', async () => {
const peer1 = createFakePeer();
sut.addPeer(peer1);
const msg1 = createChanAnn(0, 0, 0);
sut.enqueue(msg1);
await wait(200);
expect(peer1.sendMessage.called).to.be.true;
expect(peer1.sendMessage.args[0][0]).to.equal(msg1);
});
it('transitions state to Active', () => {
sut.start();
expect(sut.state).to.equal(GossipRelayState.Active);
});
});
describe('on .stop()', () => {
it('no-op', () => {
sut.stop();
});
});
describe('on enqueue', () => {
it('ignores message', () => {
sut.enqueue(createChanAnn(0, 0, 0));
expect((sut as any)._queue.length).to.equal(0);
});
});
});
describe('state: Active', () => {
beforeEach(() => {
sut.start();
});
afterEach(() => {
sut.stop();
});
describe('on .start()', () => {
it('no-op', () => {
sut.start();
});
});
describe('on .stop()', () => {
it('stops flushing', async () => {
// create a peer
const peer1 = createFakePeer();
sut.addPeer(peer1);
// enqueue a message that normally would go to a peer
const msg1 = createChanAnn(0, 0, 0);
sut.enqueue(msg1);
// stop activity before the timer would tick
await wait(50);
sut.stop();
// wait until after the timer would normally have ticked
await wait(200);
// ensure no message was sent
expect(peer1.sendMessage.called).to.be.false;
});
it('changes state to Inactive', () => {
expect(sut.state).to.equal(GossipRelayState.Active);
sut.stop();
expect(sut.state).to.equal(GossipRelayState.Inactive);
});
});
describe('on enqueue', () => {
describe('channel_announcement', () => {
it('adds new message', () => {
sut.enqueue(createChanAnn(0, 0, 0));
expect((sut as any)._queue.length).to.equal(1);
});
it('ignores duplicate', () => {
sut.enqueue(createChanAnn(0, 0, 0));
sut.enqueue(createChanAnn(0, 1, 0));
expect((sut as any)._queue.length).to.equal(2);
sut.enqueue(createChanAnn(0, 0, 0));
expect((sut as any)._queue.length).to.equal(
2,
'should ignore duplicate',
);
expect((sut as any)._queue[0].shortChannelId.txIdx).to.equal(0, "duplicate should maintain original position"); // prettier-ignore
});
});
describe('channel_update', () => {
it('adds new message', () => {
sut.enqueue(createChanUpd(0, 0, 0, 0));
expect((sut as any)._queue.length).to.equal(1);
});
it('ignores duplicate', () => {
sut.enqueue(createChanUpd(0, 0, 0, 0));
sut.enqueue(createChanUpd(0, 1, 0, 0));
expect((sut as any)._queue.length).to.equal(2);
sut.enqueue(createChanUpd(0, 0, 0, 0));
expect((sut as any)._queue.length).to.equal(
2,
'should ignore update',
);
expect((sut as any)._queue[0].shortChannelId.txIdx).to.equal(0, "duplicate should maintain original position"); // prettier-ignore
});
it('replaces outdated', () => {
sut.enqueue(createChanUpd(0, 0, 0, 0));
sut.enqueue(createChanUpd(1, 0, 0, 0));
expect((sut as any)._queue.length).to.equal(2);
sut.enqueue(createChanUpd(0, 0, 0, 1));
expect((sut as any)._queue[0].shortChannelId.block).to.equal(1);
expect((sut as any)._queue[1].shortChannelId.block).to.equal(0);
});
});
describe('node_announcement', () => {
it('adds new message', () => {
sut.enqueue(createNodeAnn('00'.repeat(32), 0));
expect((sut as any)._queue.length).to.equal(1);
});
it('ignores duplicate', () => {
sut.enqueue(createNodeAnn('00'.repeat(32), 0));
sut.enqueue(createNodeAnn('01'.repeat(32), 0));
expect((sut as any)._queue.length).to.equal(2);
sut.enqueue(createNodeAnn('00'.repeat(32), 0));
expect((sut as any)._queue[0].nodeId.toString('hex')).to.equal(
'00'.repeat(32),
);
expect((sut as any)._queue[1].nodeId.toString('hex')).to.equal(
'01'.repeat(32),
);
});
it('replaces outdated', () => {
sut.enqueue(createNodeAnn('00'.repeat(32), 0));
sut.enqueue(createNodeAnn('01'.repeat(32), 0));
expect((sut as any)._queue.length).to.equal(2);
sut.enqueue(createNodeAnn('00'.repeat(32), 1));
expect((sut as any)._queue[0].nodeId.toString('hex')).to.equal(
'01'.repeat(32),
);
expect((sut as any)._queue[1].nodeId.toString('hex')).to.equal(
'00'.repeat(32),
);
expect((sut as any)._queue[1].timestamp).to.equal(1);
});
});
});
describe('on timer', () => {
it('flushes to single message peer', () => {
// create a fake peer and add
const peer1 = createFakePeer();
sut.addPeer(peer1);
// push a new message
const msg1 = createChanAnn(0, 0, 0);
sut.enqueue(msg1);
// manually trigger the timer for testing purposes
(sut as any)._onTimer();
expect(peer1.sendMessage.called).to.be.true;
expect(peer1.sendMessage.args[0][0]).to.equal(msg1);
});
it('flushes to several messages peer', () => {
// create a fake peer and add
const peer1 = createFakePeer();
sut.addPeer(peer1);
// push messages
const msg1 = createChanAnn(0, 0, 0);
const msg2 = createChanAnn(1, 0, 0);
sut.enqueue(msg1);
sut.enqueue(msg2);
// manually trigger the timer for testing purposes
(sut as any)._onTimer();
expect(peer1.sendMessage.called).to.be.true;
expect(peer1.sendMessage.args[0][0]).to.equal(msg1);
expect(peer1.sendMessage.args[1][0]).to.equal(msg2);
});
it('flushes new messages to peer', () => {
// create a fake peer and add
const peer1 = createFakePeer();
sut.addPeer(peer1);
// push messages
const msg1 = createChanAnn(0, 0, 0);
sut.enqueue(msg1);
// manually trigger the timer for testing purposes
(sut as any)._onTimer();
// reset the stub
peer1.sendMessage.reset();
// push new message
const msg2 = createChanAnn(1, 0, 0);
sut.enqueue(msg2);
// manually trigger the timer for testing purposes
(sut as any)._onTimer();
expect(peer1.sendMessage.called).to.be.true;
expect(peer1.sendMessage.args[0][0]).to.equal(msg2);
});
it('flushes new messages to peers', () => {
// create a fake peer and add
const peer1 = createFakePeer();
sut.addPeer(peer1);
// push messages
const msg1 = createChanAnn(0, 0, 0);
sut.enqueue(msg1);
// manually trigger the timer for testing purposes
(sut as any)._onTimer();
// reset the stub
peer1.sendMessage.reset();
// add a new peer
const peer2 = createFakePeer();
sut.addPeer(peer2);
// push new message
const msg2 = createChanAnn(1, 0, 0);
sut.enqueue(msg2);
// manually trigger the timer for testing purposes
(sut as any)._onTimer();
// peer1 should only see the 2nd message
expect(peer1.sendMessage.called).to.be.true;
expect(peer1.sendMessage.args[0][0]).to.equal(msg2);
// peer2 should only see the 2nd message
expect(peer2.sendMessage.called).to.be.true;
expect(peer2.sendMessage.args[0][0]).to.equal(msg2);
});
it('prunes excess messages', () => {
sut.enqueue(createChanAnn(0, 0, 0));
sut.enqueue(createChanAnn(1, 0, 0));
sut.enqueue(createChanAnn(2, 0, 0));
sut.enqueue(createChanAnn(3, 0, 0));
sut.enqueue(createChanAnn(4, 0, 0));
// manually trigger the timer for testing purposes
(sut as any)._onTimer();
expect((sut as any)._queue.length).to.equal(3);
expect((sut as any)._queue[0].shortChannelId.block).to.equal(2);
});
it('it adjusts peer positions based on index count', () => {
// create a fake peer and add
const peer1 = createFakePeer();
sut.addPeer(peer1);
// enqueue some items - peer is at index 0
sut.enqueue(createChanAnn(0, 0, 0)); // index 0
sut.enqueue(createChanAnn(1, 0, 0)); // index 1
sut.enqueue(createChanAnn(2, 0, 0)); // index 2
// manually trigger the timer for testing purposes
(sut as any)._onTimer();
// enqueue more items - peer is at index 3
sut.enqueue(createChanAnn(3, 0, 0)); // index 3
sut.enqueue(createChanAnn(4, 0, 0)); // index 4
// manually trigger the timer for testing purposes which
// will cause a prune of and leave 3 items in the queue
(sut as any)._onTimer();
expect((sut as any)._peers.get(peer1)).to.equal(3);
});
});
});
describe('add peer', () => {
it('adds peer at current depth', () => {
// create a fake peer and add
const peer1 = createFakePeer();
sut.addPeer(peer1);
expect((sut as any)._peers.size).to.equal(1);
});
});
describe('remove peer', () => {
it('removes peer from list', () => {
// create a fake peer and add
const peer1 = createFakePeer();
sut.addPeer(peer1);
sut.removePeer(peer1);
expect((sut as any)._peers.size).to.equal(0);
});
});
});