import { HashValue , OutPoint } from "@node-lightning/core" ; import { ChannelAnnouncementMessage } from "../messages/ChannelAnnouncementMessage" ; import { ChannelUpdateMessage } from "../messages/ChannelUpdateMessage" ; import { ExtendedChannelAnnouncementMessage } from "../messages/ExtendedChannelAnnouncementMessage" ; import { IWireMessage } from "../messages/IWireMessage" ; import { NodeAnnouncementMessage } from "../messages/NodeAnnouncementMessage" ; import { MessageType } from "../MessageType" ; import { fundingScript } from "../ScriptUtils" ; import { WireError , WireErrorCode } from "../WireError" ; import { IGossipStore } from "./GossipStore" ; import { IGossipFilterChainClient } from "./IGossipFilterChainClient" ; export class Result < V , E > { public static err < V , E > ( error ) { return new Result < V , E > ( undefined , error ) ; } public static ok < V , E > ( value ) { return new Result < V , E > ( value ) ; } constructor ( readonly value ? , readonly error ? ) { } public get isOk ( ) : boolean { return this . value !== undefined ; } public get isErr ( ) : boolean { return this . error !== undefined ; } } export type GossipFilterResult = Result < IWireMessage [ ] , WireError > ; /**
 * GossipFilter recieves messages from peers and performs validation
 * on the messages to ensure that they are valid messages. These validation
 * follow messaging rules defined in Bolt #7 and include things like
 * signature checks, on-chain validation, and message sequencing requirements.
 * Successful message validation results in messages being written to an
 * instance of IGossipStore and returned as results
 *
 * The GossipFilter will also store pending messages, such as channel_update
 * message arriving before the channel_announcement.
 */ export class GossipFilter { constructor ( readonly gossipStore , readonly pendingStore , readonly chainClient ? ) { } /**
     * Validates a message and writes it to the appropriate store.
     * A fully processed messages (or releated messages) will be
     * returned when a message is successfully processed.
     *
     */ public async validateMessage ( msg ) { switch ( msg . type ) { case MessageType . ChannelAnnouncement : return await this . _validateChannelAnnouncement ( msg as ChannelAnnouncementMessage ) ; case MessageType . NodeAnnouncement : return await this . _validateNodeAnnouncement ( msg as NodeAnnouncementMessage ) ; case MessageType . ChannelUpdate : return await this . _validateChannelUpdate ( msg as ChannelUpdateMessage ) ; } return Result . ok ( [ ] as IWireMessage [ ] ) ; } /**
     * Validate a node announcement message by checking to see if the
     * message is newer than the prior timestamp and if the message
     * has a valid signature from the corresponding node
     */ private async _validateNodeAnnouncement ( msg ) { const existing = await this . gossipStore . findNodeAnnouncement ( msg . nodeId ) ; if ( existing && existing . timestamp >= msg . timestamp ) return Result . ok ( [ ] as IWireMessage [ ] ) ; const scids = await this . gossipStore . findChannelsForNode ( msg . nodeId ) ; if ( ! scids . length ) { await this . pendingStore . saveNodeAnnouncement ( msg ) ; return Result . ok ( [ ] as IWireMessage [ ] ) ; } if ( ! NodeAnnouncementMessage . verifySignatures ( msg ) ) { return Result . err ( new WireError ( WireErrorCode . nodeAnnSigFailed , [ msg ] ) ) ; } await this . gossipStore . saveNodeAnnouncement ( msg ) ; return Result . ok ( [ msg ] ) ; } /**
     * Validates a ChannelAnnouncementMessage by verifying the signatures
     * and validating the transaction on chain work. This message will
     */ private async _validateChannelAnnouncement ( msg ) { const existing = await this . gossipStore . findChannelAnnouncement ( msg . shortChannelId ) ; if ( existing && existing instanceof ExtendedChannelAnnouncementMessage ) { return Result . ok ( [ ] as IWireMessage [ ] ) ; } if ( existing && ! this . chainClient ) { return Result . ok ( [ ] as IWireMessage [ ] ) ; } if ( ! ChannelAnnouncementMessage . verifySignatures ( msg ) ) { return Result . err ( new WireError ( WireErrorCode . chanAnnSigFailed , [ msg ] ) ) ; } if ( this . chainClient ) { const blockHash = await this . chainClient . getBlockHash ( msg . shortChannelId . block ) ; if ( ! blockHash ) { return Result . err ( new WireError ( WireErrorCode . chanBadBlockHash , [ msg ] ) ) ; } const block = await this . chainClient . getBlock ( blockHash ) ; if ( ! block ) { return Result . err ( new WireError ( WireErrorCode . chanBadBlock , [ msg , blockHash ] ) ) ; } const txId = block . tx [ msg . shortChannelId . txIdx ] ; if ( ! txId ) { return Result . err ( new WireError ( WireErrorCode . chanAnnBadTx , [ msg ] ) ) ; } const utxo = await this . chainClient . getUtxo ( txId , msg . shortChannelId . voutIdx ) ; if ( ! utxo ) { return Result . err ( new WireError ( WireErrorCode . chanUtxoSpent , [ msg ] ) ) ; } const expectedScript = fundingScript ( [ msg . bitcoinKey1 , msg . bitcoinKey2 ] ) ; const actualScript = Buffer . from ( utxo . scriptPubKey . hex , "hex" ) ; if ( ! expectedScript . equals ( actualScript ) ) { return Result . err ( new WireError ( WireErrorCode . chanBadScript , [ msg , expectedScript , actualScript ] ) ) ; } const outpoint = new OutPoint ( HashValue . fromRpc ( txId ) , msg . shortChannelId . voutIdx ) ; const capacity = BigInt ( Math . round ( utxo . value * 10 ** 8 ) ) ; const extended = ExtendedChannelAnnouncementMessage . fromMessage ( msg ) ; extended . outpoint = outpoint ; extended . capacity = capacity ; msg = extended ; } await this . gossipStore . saveChannelAnnouncement ( msg ) ; const results = [ msg ] ; const pendingUpdates = [ await this . pendingStore . findChannelUpdate ( msg . shortChannelId , 0 ) , await this . pendingStore . findChannelUpdate ( msg . shortChannelId , 1 ) , ] ; for ( const pendingUpdate of pendingUpdates ) { if ( pendingUpdate ) { await this . pendingStore . deleteChannelUpdate ( pendingUpdate . shortChannelId , pendingUpdate . direction ) ; const result = await this . _validateChannelUpdate ( pendingUpdate ) ; if ( result . isErr ) return result ; else results . push ( ... result . value ) ; } } const pendingNodeAnns = [ await this . pendingStore . findNodeAnnouncement ( msg . nodeId1 ) , await this . pendingStore . findNodeAnnouncement ( msg . nodeId2 ) , ] ; for ( const pendingNodeAnn of pendingNodeAnns ) { if ( pendingNodeAnn ) { await this . pendingStore . deleteNodeAnnouncement ( pendingNodeAnn . nodeId ) ; const result = await this . _validateNodeAnnouncement ( pendingNodeAnn ) ; if ( result . isErr ) return result ; else results . push ( ... result . value ) ; } } return Result . ok ( results ) ; } /**
     * Validates a channel_update message by ensuring that:
     * - the channel_announcement has already been recieved
     * - the channel_update is not old
     * - the channel_update is correctly signed by the node
     */ private async _validateChannelUpdate ( msg ) { const channelMessage = await this . gossipStore . findChannelAnnouncement ( msg . shortChannelId ) ; if ( ! channelMessage ) { await this . pendingStore . saveChannelUpdate ( msg ) ; return Result . ok ( [ ] as IWireMessage [ ] ) ; } const existingUpdate = await this . gossipStore . findChannelUpdate ( msg . shortChannelId , msg . direction ) ; if ( existingUpdate && existingUpdate . timestamp >= msg . timestamp ) { return Result . ok ( [ ] as IWireMessage [ ] ) ; } const nodeId = msg . direction === 0 ? channelMessage . nodeId1 : channelMessage . nodeId2 ; if ( ! ChannelUpdateMessage . validateSignature ( msg , nodeId ) ) { return Result . err ( new WireError ( WireErrorCode . chanUpdSigFailed , [ msg , nodeId ] ) ) ; } await this . gossipStore . saveChannelUpdate ( msg ) ; return Result . ok ( [ msg ] ) ; } }