import { BitField } from "@node-lightning/core" ; import { ILogger } from "@node-lightning/logger" ; import * as noise from "@node-lightning/noise" ; import { expect } from "chai" ; import sinon from "sinon" ; import { Duplex } from "stream" ; import { IWireMessage } from "../lib" ; import { InitFeatureFlags } from "../lib/flags/InitFeatureFlags" ; import { Peer } from "../lib/Peer" ; import { PeerState } from "../lib/PeerState" ; import { PingPongState } from "../lib/PingPongState" ; import { createFakeLogger } from "./_test-utils" ; class FakeSocket extends Duplex { [ x ] : any ; public outgoing : Buffer [ ] = [ ] ; constructor ( ) { super ( ) ; this . end = sinon . spy ( this . end . bind ( this ) ) ; this . rpk = Buffer . alloc ( 33 ) ; } public _write ( data , encoding , cb ) { this . outgoing . push ( data ) ; cb ( ) ; } public _read ( ) { } public simReceive ( data ) { this . push ( data ) ; } } class FakeMessage implements IWireMessage { public type = 3 ; [ x ] : any ; constructor ( msg ) { this . msg = msg ; } public serialize ( ) { return Buffer . from ( this . msg ) ; } } describe ( "Peer" , ( ) => { let chainHashes ; let sut ; let ls ; let rpk ; let logger ; let sandbox ; let socket ; let localFeatures ; beforeEach ( ( ) => { chainHashes = [ Buffer . alloc ( 32 , 0xff ) ] ; localFeatures = new BitField < InitFeatureFlags > ( ) ; localFeatures . set ( InitFeatureFlags . optionDataLossProtectOptional ) ; ls = Buffer . alloc ( 32 , 0 ) ; rpk = Buffer . alloc ( 32 , 1 ) ; logger = createFakeLogger ( ) ; sut = new Peer ( ls , localFeatures , chainHashes , logger , 1 ) ; socket = new FakeSocket ( ) ; sut . pingPongState = sinon . createStubInstance ( PingPongState ) as any ; sandbox = sinon . createSandbox ( ) ; } ) ; afterEach ( ( ) => { sandbox . restore ( ) ; } ) ; describe ( ".connect()" , ( ) => { beforeEach ( ( ) => { sandbox . stub ( noise , "connect" ) . returns ( socket as any ) ; sandbox . stub ( sut , "_onSocketReady" as any ) ; sandbox . stub ( sut , "_onSocketClose" as any ) ; sandbox . stub ( sut , "_onSocketError" as any ) ; sandbox . stub ( sut , "_onSocketReadable" as any ) ; sut . connect ( rpk , "127.0.0.1" , 9735 ) ; } ) ; it ( "should be initiator" , ( ) => { expect ( sut . isInitiator ) . to . equal ( true ) ; } ) ; it ( "should bind to ready" , ( ) => { socket . emit ( "ready" ) ; expect ( ( sut as any ) . _onSocketReady . called ) . to . be . true ; } ) ; it ( "should bind close" , ( ) => { socket . emit ( "close" ) ; expect ( ( sut as any ) . _onSocketClose . called ) . to . be . true ; } ) ; it ( "should bind error" , ( ) => { socket . emit ( "error" ) ; expect ( ( sut as any ) . _onSocketError . called ) . to . be . true ; } ) ; it ( "should bind readable" , ( ) => { socket . emit ( "readable" ) ; expect ( ( sut as any ) . _onSocketReadable . called ) . to . be . true ; } ) ; } ) ; describe ( ".attach()" , ( ) => { beforeEach ( ( ) => { sandbox . stub ( sut , "_onSocketReady" as any ) ; sandbox . stub ( sut , "_onSocketClose" as any ) ; sandbox . stub ( sut , "_onSocketError" as any ) ; sandbox . stub ( sut , "_onSocketReadable" as any ) ; sut . attach ( socket as any ) ; } ) ; it ( "should not be initiator" , ( ) => { expect ( sut . isInitiator ) . to . equal ( false ) ; } ) ; it ( "should bind to ready" , ( ) => { socket . emit ( "ready" ) ; expect ( ( sut as any ) . _onSocketReady . called ) . to . be . true ; } ) ; it ( "should bind close" , ( ) => { socket . emit ( "close" ) ; expect ( ( sut as any ) . _onSocketClose . called ) . to . be . true ; } ) ; it ( "should bind error" , ( ) => { socket . emit ( "error" ) ; expect ( ( sut as any ) . _onSocketError . called ) . to . be . true ; } ) ; it ( "should bind readable" , ( ) => { socket . emit ( "readable" ) ; expect ( ( sut as any ) . _onSocketReadable . called ) . to . be . true ; } ) ; } ) ; describe ( "when connected" , ( ) => { beforeEach ( ( ) => { sut . attach ( socket as any ) ; } ) ; describe ( ".sendMessage()" , ( ) => { it ( "should throw when not ready" , ( ) => { expect ( ( ) => sut . sendMessage ( new FakeMessage ( "hello" ) as any ) ) . to . throw ( ) ; } ) ; it ( "should send the serialized message" , ( ) => { const input = new FakeMessage ( "test" ) ; sut . state = Peer . states . Ready ; sut . sendMessage ( input ) ; expect ( ( socket as any ) . outgoing [ 0 ] ) . to . deep . equal ( Buffer . from ( "test" ) ) ; } ) ; it ( "should emit a sending message" , done => { const input = new FakeMessage ( "test" ) ; sut . state = Peer . states . Ready ; sut . on ( "sending" , ( ) => done ( ) ) ; sut . sendMessage ( input ) ; } ) ; } ) ; describe ( ".disconnect()" , ( ) => { it ( "should stop the socket" , ( ) => { sut . disconnect ( ) ; expect ( ( sut . socket as any ) . end . called ) . to . be . true ; } ) ; it ( "should change the peer state to disconnecting" , ( ) => { sut . disconnect ( ) ; expect ( sut . state ) . to . equal ( PeerState . Disconnecting ) ; } ) ; } ) ; describe ( ".reconnect()" , ( ) => { it ( "should stop the socket" , ( ) => { sut . reconnect ( ) ; expect ( ( sut . socket as any ) . end . called ) . to . be . true ; } ) ; it ( "should retain the peer state" , ( ) => { const beforeState = sut . state ; sut . reconnect ( ) ; expect ( sut . state ) . to . equal ( beforeState ) ; } ) ; } ) ; describe ( "._onSocketReady()" , ( ) => { it ( "should transition state to awaiting_peer_init" , ( ) => { ( sut as any ) . _onSocketReady ( ) ; expect ( sut . state ) . to . equal ( Peer . states . AwaitingPeerInit ) ; } ) ; it ( "should send the init message to the peer" , ( ) => { ( sut as any ) . _onSocketReady ( ) ; expect ( ( socket as any ) . outgoing [ 0 ] ) . to . deep . equal ( Buffer . from ( "001000000001020120ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" , "hex" ) ) ; } ) ; } ) ; describe ( "_onSocketClose" , ( ) => { it ( "should stop the ping pong state" , ( ) => { sut . state = PeerState . Disconnecting ; ( sut as any ) . _onSocketClose ( ) ; expect ( ( sut as any ) . pingPongState . onDisconnecting . called ) . to . be . true ; } ) ; describe ( "when disconnecting" , ( ) => { it ( "should emit the close event" , done => { sut . state = PeerState . Disconnecting ; sut . on ( "close" , ( ) => done ( ) ) ; ( sut as any ) . _onSocketClose ( ) ; } ) ; } ) ; describe ( "when initiator" , ( ) => { it ( "should trigger reconnect" , done => { sut . state = PeerState . Ready ; sut . reconnectTimeoutMs = 0 ; sut . connect = sandbox . stub ( sut , "connect" ) ; sut . isInitiator = true ; ( sut as any ) . _onSocketClose ( ) ; setTimeout ( ( ) => { expect ( ( sut . connect as any ) . called ) . to . be . true ; done ( ) ; } , 50 ) ; } ) ; } ) ; describe ( "when not initiator" , ( ) => { it ( "should not trigger reconnect" , done => { sut . state = PeerState . Ready ; sut . reconnectTimeoutMs = 0 ; sut . connect = sandbox . stub ( sut , "connect" ) ; sut . isInitiator = false ; ( sut as any ) . _onSocketClose ( ) ; setTimeout ( ( ) => { expect ( ( sut . connect as any ) . called ) . to . be . false ; done ( ) ; } , 50 ) ; } ) ; } ) ; } ) ; describe ( "_onSocketError" , ( ) => { it ( "should emit error event" , done => { sut . on ( "error" , ( ) => done ( ) ) ; ( sut as any ) . _onSocketError ( ) ; } ) ; } ) ; describe ( "_sendInitMessage" , ( ) => { it ( "should send the initialization message" , ( ) => { ( sut as any ) . _sendInitMessage ( ) ; expect ( ( socket as any ) . outgoing [ 0 ] ) . to . deep . equal ( Buffer . from ( "001000000001020120ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" , "hex" ) ) ; } ) ; } ) ; describe ( "_processPeerInitMessage" , ( ) => { let input ; beforeEach ( ( ) => { input = Buffer . from ( "001000000000" , "hex" ) ; } ) ; it ( "it should fail if not init message" , ( ) => { input = Buffer . from ( "001100000000" , "hex" ) ; expect ( ( ) => ( sut as any ) . _processPeerInitMessage ( input ) ) . to . throw ( ) ; } ) ; it ( "should start ping state" , ( ) => { ( sut as any ) . _processPeerInitMessage ( input ) ; expect ( ( sut as any ) . pingPongState . start . called ) . to . be . true ; } ) ; it ( "should change the state to ready" , ( ) => { ( sut as any ) . _processPeerInitMessage ( input ) ; expect ( sut . state ) . to . equal ( Peer . states . Ready ) ; } ) ; it ( "should capture the init features" , ( ) => { input = Buffer . from ( "00100000000109" , "hex" ) ; ( sut as any ) . _processPeerInitMessage ( input ) ; expect ( sut . remoteFeatures ) . to . be . instanceof ( BitField ) ; expect ( sut . remoteFeatures . isSet ( InitFeatureFlags . optionDataLossProtectRequired ) ) ; expect ( sut . remoteFeatures . isSet ( InitFeatureFlags . initialRoutingSyncOptional ) ) ; } ) ; it ( "should emit ready" , done => { sut . on ( "ready" , ( ) => done ( ) ) ; ( sut as any ) . _processPeerInitMessage ( input ) ; } ) ; it ( "should be ok with no remote chainhash" , ( ) => { input = Buffer . from ( "00100000000109" , "hex" ) ; ( sut as any ) . _processPeerInitMessage ( input ) ; } ) ; it ( "should be ok with single chain_hash" , ( ) => { input = Buffer . from ( "00100000000109" + "0120" + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" , "hex" ) ; ( sut as any ) . _processPeerInitMessage ( input ) ; expect ( sut . remoteChains . length ) . to . equal ( 1 ) ; expect ( sut . remoteChains [ 0 ] . toString ( "hex" ) ) . to . equal ( "ff" . repeat ( 32 ) ) ; } ) ; it ( "should be ok with multiple chain_hashes" , ( ) => { input = Buffer . from ( "00100000000109" + "0140" + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" , "hex" ) ; ( sut as any ) . _processPeerInitMessage ( input ) ; expect ( sut . remoteChains . length ) . to . equal ( 2 ) ; expect ( sut . remoteChains [ 0 ] . toString ( "hex" ) ) . to . equal ( "ff" . repeat ( 32 ) ) ; expect ( sut . remoteChains [ 1 ] . toString ( "hex" ) ) . to . equal ( "ee" . repeat ( 32 ) ) ; } ) ; it ( "should disconnect with no chainhash match" , ( ) => { input = Buffer . from ( "00100000000109" + "0120" + "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" , "hex" ) ; ( sut as any ) . _processPeerInitMessage ( input ) ; expect ( sut . state ) . to . equal ( PeerState . Disconnecting ) ; } ) ; it ( "should stay connected when local odd no remote feature" , ( ) => { ( sut as any ) . localFeatures = new BitField < InitFeatureFlags > ( ) ; sut . localFeatures . set ( InitFeatureFlags . optionDataLossProtectOptional ) ; input = Buffer . from ( "00100000000100" , "hex" ) ; ( sut as any ) . _processPeerInitMessage ( input ) ; expect ( sut . state ) . to . equal ( PeerState . Ready ) ; } ) ; it ( "should stay connected when local even/remote even feature" , ( ) => { sut . localFeatures . set ( InitFeatureFlags . optionDataLossProtectRequired ) ; input = Buffer . from ( "00100000000102" , "hex" ) ; ( sut as any ) . _processPeerInitMessage ( input ) ; expect ( sut . state ) . to . equal ( PeerState . Ready ) ; } ) ; it ( "should stay connected with local even/remote even feature" , ( ) => { sut . localFeatures . set ( InitFeatureFlags . optionDataLossProtectRequired ) ; input = Buffer . from ( "00100000000101" , "hex" ) ; ( sut as any ) . _processPeerInitMessage ( input ) ; expect ( sut . state ) . to . equal ( PeerState . Ready ) ; } ) ; it ( "should disconnect with local even missing remote feature" , ( ) => { sut . localFeatures . set ( InitFeatureFlags . optionDataLossProtectRequired ) ; input = Buffer . from ( "00100000000100" , "hex" ) ; ( sut as any ) . _processPeerInitMessage ( input ) ; expect ( sut . state ) . to . equal ( PeerState . Disconnecting ) ; } ) ; } ) ; describe ( "_processMessage" , ( ) => { let input ; let msg ; beforeEach ( ( ) => { input = Buffer . from ( "001000000000" , "hex" ) ; } ) ; describe ( "when valid message" , ( ) => { it ( "should log with ping service" , ( ) => { msg = ( sut as any ) . _processMessage ( input ) ; expect ( ( sut as any ) . pingPongState . onMessage . called ) . to . be . true ; } ) ; it ( "should emit the message" , ( ) => { expect ( msg . type ) . to . equal ( 16 ) ; } ) ; } ) ; } ) ; describe ( "_onSocketReadable" , ( ) => { it ( "should read peer init message when awaiting_peer_init state" , done => { sut . state = Peer . states . AwaitingPeerInit ; sut . on ( "ready" , ( ) => done ( ) ) ; socket . simReceive ( Buffer . from ( "001000000000" , "hex" ) ) ; } ) ; it ( "should process message when in ready state" , done => { sut . state = Peer . states . Ready ; sut . on ( "readable" , ( ) => { done ( ) ; } ) ; socket . simReceive ( Buffer . from ( "001000000000" , "hex" ) ) ; } ) ; describe ( "on error" , ( ) => { it ( "should close the socket" , done => { sut . state = Peer . states . Ready ; sut . on ( "error" , ( ) => { expect ( ( socket as any ) . end . called ) . to . be . true ; done ( ) ; } ) ; socket . simReceive ( Buffer . from ( "0010" , "hex" ) ) ; } ) ; it ( "should emit an error event" , done => { sut . state = Peer . states . Ready ; sut . on ( "error" , ( ) => done ( ) ) ; socket . simReceive ( Buffer . from ( "0010" , "hex" ) ) ; } ) ; } ) ; } ) ; } ) ; } ) ;