github.com/mit-dci/lit@v0.0.0-20221102210550-8c3d3b49f2ce/uspv/chainhook.go (about)

     1  package uspv
     2  
     3  import (
     4  	"path/filepath"
     5  
     6  	"github.com/mit-dci/lit/logging"
     7  
     8  	"github.com/mit-dci/lit/btcutil/chaincfg/chainhash"
     9  	"github.com/mit-dci/lit/coinparam"
    10  	"github.com/mit-dci/lit/lnutil"
    11  	"github.com/mit-dci/lit/wire"
    12  )
    13  
    14  // ChainHook is an interface which provides access to a blockchain for the
    15  // wallit.  Right now the USPV package conforms to this interface, but it
    16  // should also be possible to plug in things like fullnode-rpc clients, and
    17  // (yuck) trusted block explorers.
    18  
    19  /*  The model here is that the wallit has lots of state on disk about
    20  what it's seen.  The ChainHook is ephemeral, and has some state in RAM but
    21  shouldn't have to keep track of too much; or at least what it keeps track of
    22  is internal and not exported out to the wallit (eg a fullnode keeps track
    23  of a lot, and SPV node, somewhat less, and a block explorer shim basically nothing)
    24  */
    25  
    26  // ChainHook is a thing that lets you interact with the actual blockchains
    27  type ChainHook interface {
    28  
    29  	// Start turns on the ChainHook.  Later on, pass more parameters here.
    30  	// Also gets the txChan where txs come in from the ChainHook to the wallit.
    31  	// The TxChannel should never give txs that the wallit doesn't care about.  In the
    32  	// case of bloom filters, false positives should be handled and stopped at the
    33  	// ChainHook layer; wallit should not have to handle ingesting irrelevant txs.
    34  	// You get back an error and 2 channels: one for txs with height attached, and
    35  	// one with block heights.  Subject to change; maybe this is redundant.
    36  
    37  	// Note that for reorgs, the height chan just sends a lower height than you
    38  	// already have, and that means "reorg back!"
    39  	Start(height int32, host, path string, proxyURL string, params *coinparam.Params) (
    40  		chan lnutil.TxAndHeight, chan int32, error)
    41  
    42  	// The Register functions send information to the ChainHook about what txs to
    43  	// return.  Txs matching either the addresses or outpoints will be returned
    44  	// via the TxChannel
    45  
    46  	// RegisterAddress tells the ChainHook about an address of interest.
    47  	// Give it an array; Currently needs to be 20 bytes.  Only p2pkh / p2wpkh
    48  	// are supported.
    49  	// Later could add a separate function for script hashes (20/32)
    50  	RegisterAddress(address [20]byte) error
    51  
    52  	// RegisterOutPoint tells the ChainHook about an outpoint of interest.
    53  	RegisterOutPoint(wire.OutPoint) error
    54  
    55  	// UnregisterOutPoint tells the ChainHook about loss of interest in an outpoint.
    56  	UnregisterOutPoint(wire.OutPoint) error
    57  
    58  	// SetHeight sets the height ChainHook needs to look above.
    59  	// Returns a channel which tells the wallit what height the ChainHook has
    60  	// sync'd up to.  This chan should push int32s *after* the TxAndHeights
    61  	// have come in; so getting a "54" here means block 54 is done and fully parsed.
    62  	// Removed, put into Start().
    63  	//	SetHeight(startHeight int32) chan int32
    64  
    65  	// NewRawBlocksChannel returns a new channel to receive blocks.
    66  	NewRawBlocksChannel() chan *wire.MsgBlock
    67  
    68  	// NewHeightChannel returns a new channel to receive height events.
    69  	NewHeightChannel() chan int32
    70  
    71  	// PushTx sends a tx out to the network via the ChainHook.
    72  	// Note that this does NOT register anything in the tx, so by just using this,
    73  	// nothing will come back about confirmation.  It WILL come back with errors
    74  	// though, so this takes some time.
    75  	PushTx(tx *wire.MsgTx) error
    76  
    77  	// Request all incoming blocks over this channel.  If RawBlocks isn't called,
    78  	// then the undelying hook package doesn't need to get full blocks.
    79  	// Currently you always call it with uspv...
    80  	RawBlocks() chan *wire.MsgBlock
    81  	// TODO -- reorgs.  Oh and doublespends and stuff.
    82  }
    83  
    84  /*
    85  type ChainHook interface {
    86  
    87  	Start() chan lnutil.TxAndHeight
    88  	RegisterAddress(address [20]byte) error
    89  	RegisterOutPoint(wire.OutPoint) error
    90  	SetHeight(startHeight int32) chan int32
    91  	PushTx(tx *wire.MsgTx) error
    92  	RawBlocks() chan *wire.MsgBlock
    93  }
    94  */
    95  
    96  // --- implementation of ChainHook interface ----
    97  
    98  func (s *SPVCon) Start(
    99  	startHeight int32, host, path string, proxyURL string, params *coinparam.Params) (
   100  	chan lnutil.TxAndHeight, chan int32, error) {
   101  
   102  	// These can be set before calling Start()
   103  	s.HardMode = true
   104  	s.Ironman = false
   105  	s.ProxyURL = proxyURL
   106  
   107  	s.Param = params
   108  
   109  	s.inMsgQueue = make(chan wire.Message)
   110  	s.outMsgQueue = make(chan wire.Message)
   111  	s.TrackingAdrs = make(map[[20]byte]bool)
   112  	s.TrackingOPs = make(map[wire.OutPoint]bool)
   113  
   114  	s.TxMap = make(map[chainhash.Hash]*wire.MsgTx)
   115  
   116  	s.OKTxids = make(map[chainhash.Hash]int32)
   117  
   118  	s.TxUpToWallit = make(chan lnutil.TxAndHeight, 1)
   119  	s.CurrentHeightChan = make(chan int32, 1)
   120  
   121  	s.syncHeight = startHeight
   122  
   123  	headerFilePath := filepath.Join(path, "header.bin")
   124  	// open header file
   125  	err := s.openHeaderFile(headerFilePath)
   126  	if err != nil {
   127  		return nil, nil, err
   128  	}
   129  
   130  	err = s.Connect(host)
   131  	if err != nil {
   132  		logging.Errorf("Can't connect to host %s\n", host)
   133  		return nil, nil, err
   134  	}
   135  
   136  	err = s.AskForHeaders()
   137  	if err != nil {
   138  		logging.Errorf("AskForHeaders error\n")
   139  		return nil, nil, err
   140  	}
   141  
   142  	return s.TxUpToWallit, s.CurrentHeightChan, nil
   143  }
   144  
   145  // RegisterAddress ...
   146  func (s *SPVCon) RegisterAddress(adr160 [20]byte) error {
   147  	s.TrackingAdrsMtx.Lock()
   148  	s.TrackingAdrs[adr160] = true
   149  	s.TrackingAdrsMtx.Unlock()
   150  	return nil
   151  }
   152  
   153  // RegisterOutPoint ...
   154  func (s *SPVCon) RegisterOutPoint(op wire.OutPoint) error {
   155  	s.TrackingOPsMtx.Lock()
   156  	s.TrackingOPs[op] = true
   157  	s.TrackingOPsMtx.Unlock()
   158  	return nil
   159  }
   160  
   161  func (s *SPVCon) UnregisterOutPoint(op wire.OutPoint) error {
   162  	s.TrackingOPsMtx.Lock()
   163  	delete(s.TrackingOPs, op)
   164  	s.TrackingOPsMtx.Unlock()
   165  	return nil
   166  }
   167  
   168  // PushTx sends a tx out to the global network
   169  func (s *SPVCon) PushTx(tx *wire.MsgTx) error {
   170  	// store tx in the RAM map for when other nodes ask for it
   171  	txid := tx.TxHash()
   172  	s.TxMap[txid] = tx
   173  
   174  	// since we never delete txs, this will eventually run out of RAM.
   175  	// But might take years... might be nice to fix.
   176  
   177  	// send out an inv message telling nodes we have this new tx
   178  	iv1 := wire.NewInvVect(wire.InvTypeWitnessTx, &txid)
   179  	invMsg := wire.NewMsgInv()
   180  	err := invMsg.AddInvVect(iv1)
   181  	if err != nil {
   182  		return err
   183  	}
   184  	// broadcast inv message
   185  	s.outMsgQueue <- invMsg
   186  
   187  	// TODO wait a few seconds here for a reject message and return it
   188  	return nil
   189  }
   190  
   191  // RawBlocks returns a channel where all the blocks appear.
   192  func (s *SPVCon) RawBlocks() chan *wire.MsgBlock {
   193  	s.RawBlockActive = true
   194  	s.RawBlockSender = make(chan *wire.MsgBlock, 8) // I dunno, 8?
   195  	return s.RawBlockSender
   196  }
   197  
   198  // NewRawBlocksChannel appends to the raw block distribution list and returns a new channel to receive blocks.
   199  func (s *SPVCon) NewRawBlocksChannel() chan *wire.MsgBlock {
   200  	newBlockSender := make(chan *wire.MsgBlock, 8)
   201  	s.RawBlockDistribute = append(s.RawBlockDistribute, newBlockSender)
   202  	return newBlockSender
   203  }
   204  
   205  // NewHeightChannel appends to the height distribution list and returns a new channel to receive height events.
   206  func (s *SPVCon) NewHeightChannel() chan int32 {
   207  	newHeightSender := make(chan int32, 8)
   208  	s.HeightDistribute = append(s.HeightDistribute, newHeightSender)
   209  	return newHeightSender
   210  }