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 }