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  }