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

     1  package wallit
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  
     9  	"github.com/boltdb/bolt"
    10  	"github.com/mit-dci/lit/btcutil/hdkeychain"
    11  	"github.com/mit-dci/lit/coinparam"
    12  	"github.com/mit-dci/lit/lnutil"
    13  	"github.com/mit-dci/lit/logging"
    14  	"github.com/mit-dci/lit/powless"
    15  	"github.com/mit-dci/lit/uspv"
    16  	"github.com/mit-dci/lit/wire"
    17  )
    18  
    19  func NewWallit(
    20  	rootkey *hdkeychain.ExtendedKey, birthHeight int32, resync bool,
    21  	spvhost, path string, proxyURL string, p *coinparam.Params) (*Wallit, int, error) {
    22  
    23  	var w Wallit
    24  	w.rootPrivKey = rootkey
    25  	w.Param = p
    26  	w.FreezeSet = make(map[wire.OutPoint]*FrozenTx)
    27  
    28  	w.FeeRate = w.Param.FeePerByte
    29  
    30  	wallitpath := filepath.Join(path, p.Name)
    31  
    32  	// create wallit sub dir if it's not there
    33  	_, err := os.Stat(wallitpath)
    34  	if os.IsNotExist(err) {
    35  		os.Mkdir(wallitpath, 0700)
    36  	}
    37  
    38  	// Tricky part here is that we want the sync height to tell the chainhook,
    39  	// so we have to open the db first, then turn on the chainhook, THEN tell
    40  	// chainhook about all our addresses.
    41  
    42  	// use powless for chainhook if the host string has https in it
    43  	// this is a bit hacky for now
    44  
    45  	if strings.Contains(spvhost, "https") {
    46  		w.Hook = new(powless.APILink)
    47  	} else {
    48  		// no https; use uSPV for chainhook
    49  		w.Hook = new(uspv.SPVCon)
    50  	}
    51  
    52  	wallitdbname := filepath.Join(wallitpath, "utxo.db")
    53  	err = w.OpenDB(wallitdbname)
    54  	if err != nil {
    55  		logging.Errorf("NewWallit crash  %s ", err.Error())
    56  	}
    57  	// get height
    58  	height := w.CurrentHeight()
    59  	logging.Infof("DB current height %d\n", height)
    60  
    61  	// bring height up to birthheight, or back down in case of resync
    62  	if height < birthHeight || resync {
    63  		height = birthHeight
    64  		w.SetDBSyncHeight(height)
    65  	}
    66  	hookFail := false
    67  	logging.Infof("DB corrected height %d\n", height)
    68  	incomingTx, incomingBlockheight, err := w.Hook.Start(height, spvhost, wallitpath, proxyURL, p)
    69  	if err != nil {
    70  		hookFail = true
    71  		logging.Errorf("NewWallit Hook.Start crash  %s ", err.Error())
    72  	}
    73  
    74  	// check if there are any addresses.  If there aren't (initial wallet setup)
    75  	// then make an address.
    76  	adrs, err := w.AdrDump()
    77  	if err != nil {
    78  		logging.Errorf("NewWallit crash  %s ", err.Error())
    79  	}
    80  	if len(adrs) == 0 {
    81  		_, err := w.NewAdr()
    82  		if err != nil {
    83  			logging.Errorf("NewWallit crash  %s ", err.Error())
    84  		}
    85  	}
    86  
    87  	// send all those adrs to the hook
    88  	for _, a := range adrs {
    89  		err = w.Hook.RegisterAddress(a)
    90  		if err != nil {
    91  			logging.Errorf("NewWallit RegisterAddress crash %s ", err.Error())
    92  		}
    93  	}
    94  
    95  	// send outpoints (if any) to the hook
    96  	utxos, err := w.UtxoDump()
    97  	if err != nil {
    98  		logging.Errorf("NewWallit crash  %s ", err.Error())
    99  	}
   100  	for _, utxo := range utxos {
   101  		err = w.Hook.RegisterOutPoint(utxo.Op)
   102  		if err != nil {
   103  			logging.Errorf("NewWallit crash  %s ", err.Error())
   104  		}
   105  	}
   106  
   107  	// deal with the incoming txs
   108  	go w.TxHandler(incomingTx)
   109  
   110  	// deal with incoming height
   111  	go w.HeightHandler(incomingBlockheight)
   112  	if !hookFail {
   113  		return &w, int(p.HDCoinType), nil
   114  	}
   115  	return &w, 0, fmt.Errorf("Unsupported coin daemon or coin daemon not running")
   116  }
   117  
   118  // TxHandler is the goroutine that receives & ingests new txs for the wallit.
   119  func (w *Wallit) TxHandler(incomingTxAndHeight chan lnutil.TxAndHeight) {
   120  	for {
   121  		txah := <-incomingTxAndHeight
   122  		w.Ingest(txah.Tx, txah.Height)
   123  		logging.Infof("got tx %s at height %d\n",
   124  			txah.Tx.TxHash().String(), txah.Height)
   125  	}
   126  }
   127  
   128  func (w *Wallit) HeightHandler(incomingHeight chan int32) {
   129  	var prevHeight int32
   130  	for {
   131  		h := <-incomingHeight
   132  		// detect reorg
   133  		if h < prevHeight {
   134  			logging.Infof("HeightHandler: oh no, reorg!\n")
   135  			err := w.RollBack(h)
   136  			if err != nil {
   137  				logging.Errorf("Rollback crash  %s ", err.Error())
   138  			}
   139  		}
   140  
   141  		if w.HeightEventChan != nil {
   142  			he := lnutil.HeightEvent{Height: h, CoinType: w.Param.HDCoinType}
   143  			w.HeightEventChan <- he
   144  		}
   145  
   146  		err := w.SetDBSyncHeight(h)
   147  		if err != nil {
   148  			logging.Errorf("HeightHandler crash  %s ", err.Error())
   149  		}
   150  		prevHeight = h
   151  	}
   152  }
   153  
   154  // OpenDB starts up the database.  Creates the file if it doesn't exist.
   155  func (w *Wallit) OpenDB(filename string) error {
   156  	var err error
   157  	var numKeys uint32
   158  	w.StateDB, err = bolt.Open(filename, 0644, nil)
   159  	if err != nil {
   160  		return err
   161  	}
   162  	// create buckets if they're not already there
   163  	err = w.StateDB.Update(func(btx *bolt.Tx) error {
   164  		_, err = btx.CreateBucketIfNotExists(BKToutpoint)
   165  		if err != nil {
   166  			return err
   167  		}
   168  		_, err = btx.CreateBucketIfNotExists(BKTadr)
   169  		if err != nil {
   170  			return err
   171  		}
   172  		_, err = btx.CreateBucketIfNotExists(BKTStxos)
   173  		if err != nil {
   174  			return err
   175  		}
   176  		_, err = btx.CreateBucketIfNotExists(BKTTxns)
   177  		if err != nil {
   178  			return err
   179  		}
   180  
   181  		sta, err := btx.CreateBucketIfNotExists(BKTState)
   182  		if err != nil {
   183  			return err
   184  		}
   185  
   186  		numKeysBytes := sta.Get(KEYNumKeys)
   187  		if numKeysBytes != nil { // NumKeys exists, read into uint32
   188  			numKeys = lnutil.BtU32(numKeysBytes)
   189  			logging.Infof("db says %d keys\n", numKeys)
   190  		} else { // no adrs yet, make it 0.  Then make an address.
   191  			logging.Infof("NumKeys not in DB, must be new DB. 0 Keys\n")
   192  			numKeys = 0
   193  			b0 := lnutil.U32tB(numKeys)
   194  			err = sta.Put(KEYNumKeys, b0)
   195  			if err != nil {
   196  				return err
   197  			}
   198  		}
   199  
   200  		return nil
   201  	})
   202  	if err != nil {
   203  		return err
   204  	}
   205  
   206  	return nil
   207  }