github.com/mit-dci/lit@v0.0.0-20221102210550-8c3d3b49f2ce/powless/nsight.go (about) 1 package powless 2 3 // GetAdrTxos 4 import ( 5 "bytes" 6 "encoding/hex" 7 "encoding/json" 8 "fmt" 9 "io" 10 "net/http" 11 "os" 12 "time" 13 14 "github.com/mit-dci/lit/lnutil" 15 "github.com/mit-dci/lit/logging" 16 "github.com/mit-dci/lit/wire" 17 ) 18 19 // ARGHGH all fields have to be exported (caps) or the json unmarshaller won't 20 // populate them ! 21 type AdrUtxoResponse struct { 22 Txid string 23 Height int32 24 Satoshis int64 25 } 26 27 type TxResponse struct { 28 Txid string 29 Blockheight int32 30 Vout []VoutJson 31 } 32 33 // Get txid of spending tx 34 type VoutJson struct { 35 N uint32 36 SpentTxId string 37 SpentHeight int32 38 } 39 40 // ...use insight api. at least that's open source, can run yourself, seems to have 41 // some dev activity behind it. 42 func (a *APILink) NsightGetAdrTxos() error { 43 44 apitxourl := "https://test-insight.bitpay.com/api" 45 // make a comma-separated list of base58 addresses 46 var adrlist string 47 48 a.TrackingAdrsMtx.Lock() 49 for adr160, _ := range a.TrackingAdrs { 50 adr58 := lnutil.OldAddressFromPKH(adr160, a.p.PubKeyHashAddrID) 51 adrlist += adr58 52 adrlist += "," 53 } 54 a.TrackingAdrsMtx.Unlock() 55 56 // chop off last comma, and add /utxo 57 adrlist = adrlist[:len(adrlist)-1] + "/utxo" 58 59 client := &http.Client{ 60 Timeout: time.Second * 5, // 5s to accomodate the 10s RPC timeout 61 } 62 response, err := client.Get(apitxourl + "/addrs/" + adrlist) 63 if err != nil { 64 return err 65 } 66 67 ars := new([]AdrUtxoResponse) 68 69 err = json.NewDecoder(response.Body).Decode(ars) 70 if err != nil { 71 return err 72 } 73 74 if len(*ars) == 0 { 75 return fmt.Errorf("no ars \n") 76 } 77 78 // go through txids, request hex tx, build txahdheight and send that up 79 for _, adrUtxo := range *ars { 80 81 // only request if higher than current 'sync' height 82 if adrUtxo.Height < a.height { 83 // skip this address; it's lower than we've already seen 84 continue 85 } 86 87 tx, err := GetRawTx(adrUtxo.Txid) 88 if err != nil { 89 return err 90 } 91 92 var txah lnutil.TxAndHeight 93 txah.Height = int32(adrUtxo.Height) 94 txah.Tx = tx 95 96 logging.Infof("tx %s at height %d\n", txah.Tx.TxHash().String(), txah.Height) 97 a.TxUpToWallit <- txah 98 99 // don't know what order we get these in, so update APILink height at the end 100 // I think it's OK to do this? Seems OK but haven't seen this use of defer() 101 defer a.UpdateHeight(adrUtxo.Height) 102 } 103 104 return nil 105 } 106 107 func (a *APILink) GetOPTxs() error { 108 apitxourl := "https://test-insight.bitpay.com/api/" 109 110 var oplist []wire.OutPoint 111 112 // copy registered ops here to minimize time mutex is locked 113 a.TrackingOPsMtx.Lock() 114 for op, _ := range a.TrackingOPs { 115 oplist = append(oplist, op) 116 } 117 a.TrackingOPsMtx.Unlock() 118 119 // need to query each txid with a different http request 120 for _, op := range oplist { 121 logging.Infof("asking for %s\n", op.String()) 122 // get full tx info for the outpoint's tx 123 // (if we have 2 outpoints with the same txid we query twice...) 124 client := &http.Client{ 125 Timeout: time.Second * 5, // 5s to accomodate the 10s RPC timeout 126 } 127 response, err := client.Get(apitxourl + "tx/" + op.Hash.String()) 128 if err != nil { 129 return err 130 } 131 132 var txr TxResponse 133 // parse the response to get the spending txid 134 err = json.NewDecoder(response.Body).Decode(&txr) 135 if err != nil { 136 logging.Errorf("json decode error; op %s not found\n", op.String()) 137 continue 138 } 139 140 // what is the "v" for here? 141 for _, txout := range txr.Vout { 142 if op.Index == txout.N { // hit; request this outpoint's spend tx 143 // see if it's been spent 144 if txout.SpentTxId == "" { 145 logging.Errorf("%s has nil spenttxid\n", op.String()) 146 // this outpoint is not yet spent, can't request 147 continue 148 } 149 150 tx, err := GetRawTx(txout.SpentTxId) 151 if err != nil { 152 return err 153 } 154 155 var txah lnutil.TxAndHeight 156 txah.Tx = tx 157 txah.Height = txout.SpentHeight 158 a.TxUpToWallit <- txah 159 160 a.UpdateHeight(txout.SpentHeight) 161 } 162 } 163 164 // TODO -- REMOVE once there is any API support for segwit addresses. 165 // This queries the outpoints we already have and re-downloads them to 166 // update their height. (IF the height is non-zero) It's a huge hack 167 // and not scalable. But allows segwit outputs to be confirmed now. 168 //if txr. 169 170 if txr.Blockheight > 1 { 171 // this outpoint is confirmed; redownload it and send to wallit 172 tx, err := GetRawTx(txr.Txid) 173 if err != nil { 174 return err 175 } 176 177 var txah lnutil.TxAndHeight 178 txah.Tx = tx 179 txah.Height = txr.Blockheight 180 a.TxUpToWallit <- txah 181 182 a.UpdateHeight(txr.Blockheight) 183 } 184 185 // TODO -- end REMOVE section 186 187 } 188 189 return nil 190 } 191 192 // GetRawTx is a helper function to get a tx from the insight api 193 func GetRawTx(txid string) (*wire.MsgTx, error) { 194 rawTxURL := "https://test-insight.bitpay.com/api/rawtx/" 195 client := &http.Client{ 196 Timeout: time.Second * 5, // 5s to accomodate the 10s RPC timeout 197 } 198 response, err := client.Get(rawTxURL + txid) 199 if err != nil { 200 return nil, err 201 } 202 203 var rtx RawTxResponse 204 205 err = json.NewDecoder(response.Body).Decode(&rtx) 206 if err != nil { 207 return nil, err 208 } 209 210 txBytes, err := hex.DecodeString(rtx.RawTx) 211 if err != nil { 212 return nil, err 213 } 214 buf := bytes.NewBuffer(txBytes) 215 tx := wire.NewMsgTx() 216 err = tx.Deserialize(buf) 217 if err != nil { 218 return nil, err 219 } 220 221 return tx, nil 222 } 223 224 /* smartbit structs 225 226 (put in other file?) 227 228 type AdrResponse struct { 229 Success bool 230 // Paging interface{} 231 Unspent []JsUtxo 232 } 233 234 type TxResponse struct { 235 Success bool 236 Transaction []TxJson 237 } 238 239 type TxJson struct { 240 Block int32 241 Txid string 242 } 243 244 type TxHexResponse struct { 245 Success bool 246 Hex []TxHexString 247 } 248 249 type TxHexString struct { 250 Txid string 251 Hex string 252 } 253 254 type JsUtxo struct { 255 Value_int int64 256 Txid string 257 N uint32 258 Addresses []string // why more than 1 ..? always 1. 259 } 260 261 */ 262 263 // PushTx pushes a tx to the network via the smartbit site / api 264 // smartbit supports segwit so 265 func (a *APILink) PushTxSmartBit(tx *wire.MsgTx) error { 266 if tx == nil { 267 return fmt.Errorf("tx is nil") 268 } 269 var b bytes.Buffer 270 271 err := tx.Serialize(&b) 272 if err != nil { 273 return err 274 } 275 276 // turn into hex 277 txHexString := fmt.Sprintf("{\"hex\": \"%x\"}", b.Bytes()) 278 279 logging.Infof("tx hex string is %s\n", txHexString) 280 281 apiurl := "https://testnet-api.smartbit.com.au/v1/blockchain/pushtx" 282 response, err := http.Post( 283 apiurl, "application/json", bytes.NewBuffer([]byte(txHexString))) 284 if err != nil { 285 return err 286 } 287 288 logging.Infof("response: %s", response.Status) 289 _, err = io.Copy(os.Stdout, response.Body) 290 if err != nil { 291 return err 292 } 293 294 return err 295 }