github.com/deso-protocol/core@v1.2.9/lib/bitcoin_burner.go (about)

     1  package lib
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/hex"
     6  	"encoding/json"
     7  	"fmt"
     8  	"github.com/davecgh/go-spew/spew"
     9  	"io/ioutil"
    10  	"math"
    11  	"net/http"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/golang/glog"
    16  	"github.com/pkg/errors"
    17  
    18  	"github.com/btcsuite/btcd/btcec"
    19  	"github.com/btcsuite/btcd/chaincfg/chainhash"
    20  	"github.com/btcsuite/btcd/txscript"
    21  	"github.com/btcsuite/btcd/wire"
    22  	"github.com/btcsuite/btcutil"
    23  )
    24  
    25  // bitcoin_burner.go finds the Bitcoin UTXOs associated with a Bitcoin
    26  // address and constructs a burn transaction on behalf of the user. Note that the
    27  // use of an API here is strictly cosmetic, and that none of this
    28  // logic is used for actually validating anything (that is all done by bitcoin_manager.go,
    29  // which relies on Bitcoin peers and is fully decentralized). The user can
    30  // also simply use an existing Bitcoin wallet to send Bitcoin to the burn address
    31  // rather than this utility, but that is slightly less convenient than just
    32  // baking this functionality in, which is why we do it.
    33  //
    34  // TODO: If this API doesn't work in the long run, I think the ElectrumX network would
    35  // be a pretty good alternative.
    36  
    37  type BitcoinUtxo struct {
    38  	TxID           *chainhash.Hash
    39  	Index          int64
    40  	AmountSatoshis int64
    41  }
    42  
    43  func _estimateBitcoinTxSize(numInputs int, numOutputs int) int {
    44  	return numInputs*180 + numOutputs*34
    45  }
    46  
    47  func EstimateBitcoinTxFee(
    48  	numInputs int, numOutputs int, feeRateSatoshisPerKB uint64) uint64 {
    49  
    50  	return uint64(_estimateBitcoinTxSize(numInputs, numOutputs)) * feeRateSatoshisPerKB / 1000
    51  }
    52  
    53  func CreateUnsignedBitcoinSpendTransaction(
    54  	spendAmountSatoshis uint64,
    55  	feeRateSatoshisPerKB uint64,
    56  	spendAddrString string,
    57  	recipientAddrString string,
    58  	params *DeSoParams,
    59  	utxoSource func(addr string, params *DeSoParams) ([]*BitcoinUtxo, error)) (
    60  	_txn *wire.MsgTx, _totalInput uint64, _fee uint64, _err error) {
    61  
    62  	// Get the utxos for the spend key.
    63  	bitcoinUtxos, err := utxoSource(spendAddrString, params)
    64  	if err != nil {
    65  		return nil, 0, 0, errors.Wrapf(err, "CreateUnsignedBitcoinSpendTransaction: Problem getting "+
    66  			"Bitcoin utxos for Bitcoin address %s",
    67  			spendAddrString)
    68  	}
    69  
    70  	glog.V(2).Infof("CreateUnsignedBitcoinSpendTransaction: Found %d BitcoinUtxos", len(bitcoinUtxos))
    71  
    72  	// Create the transaction we'll be returning.
    73  	retTxn := &wire.MsgTx{}
    74  	// Set locktime to 0 since we're not using it.
    75  	retTxn.LockTime = 0
    76  	// The version seems to be 2 these days.
    77  	retTxn.Version = 2
    78  
    79  	// Add an output sending spendAmountSatoshis to the recipientAddress.
    80  	//
    81  	// We decode it twice in order to guarantee that we're sending to an *address*
    82  	// rather than to a public key.
    83  	recipientOutput := &wire.TxOut{}
    84  	recipientAddrTmp, err := btcutil.DecodeAddress(
    85  		recipientAddrString, params.BitcoinBtcdParams)
    86  	if err != nil {
    87  		return nil, 0, 0, errors.Wrapf(err, "CreateUnsignedBitcoinSpendTransaction: Problem decoding "+
    88  			"recipient address %s", recipientAddrString)
    89  	}
    90  	recipientAddr, err := btcutil.DecodeAddress(
    91  		recipientAddrTmp.EncodeAddress(), params.BitcoinBtcdParams)
    92  	if err != nil {
    93  		return nil, 0, 0, errors.Wrapf(err, "CreateUnsignedBitcoinSpendTransaction: Problem decoding "+
    94  			"recipient address pubkeyhash %s %s", recipientAddrString, recipientAddrTmp.EncodeAddress())
    95  	}
    96  	recipientOutput.PkScript, err = txscript.PayToAddrScript(recipientAddr)
    97  	if err != nil {
    98  		return nil, 0, 0, errors.Wrapf(err, "CreateUnsignedBitcoinSpendTransaction: Problem adding script: %v", err)
    99  	}
   100  	recipientOutput.Value = int64(spendAmountSatoshis)
   101  	retTxn.TxOut = append(retTxn.TxOut, recipientOutput)
   102  
   103  	// Decode the spendAddrString.
   104  	//
   105  	// We decode it twice in order to guarantee that we're sending to an *address*
   106  	// rather than to a public key.
   107  	spendAddrTmp, err := btcutil.DecodeAddress(
   108  		spendAddrString, params.BitcoinBtcdParams)
   109  	if err != nil {
   110  		return nil, 0, 0, errors.Wrapf(err, "CreateUnsignedBitcoinSpendTransaction: Problem decoding "+
   111  			"spend address %s", spendAddrString)
   112  	}
   113  	spendAddr, err := btcutil.DecodeAddress(
   114  		spendAddrTmp.EncodeAddress(), params.BitcoinBtcdParams)
   115  	if err != nil {
   116  		return nil, 0, 0, errors.Wrapf(err, "CreateUnsignedBitcoinSpendTransaction: Problem decoding "+
   117  			"spend address pubkeyhash %s %s", spendAddrString, spendAddrTmp.EncodeAddress())
   118  	}
   119  
   120  	// Rack up the number of utxos you need to pay the spend amount. Add each
   121  	// one to our transaction until we have enough to cover the spendAmount
   122  	// plus the fee.
   123  	totalInputSatoshis := uint64(0)
   124  	for _, bUtxo := range bitcoinUtxos {
   125  		totalInputSatoshis += uint64(bUtxo.AmountSatoshis)
   126  
   127  		// Construct an input corresponding to this utxo.
   128  		txInput := &wire.TxIn{}
   129  		txInput.PreviousOutPoint = *wire.NewOutPoint(bUtxo.TxID, uint32(bUtxo.Index))
   130  		// Set Sequence to the max value to disable locktime and opt out of RBF.
   131  		txInput.Sequence = math.MaxUint32
   132  		// Don't set the SignatureScript yet.
   133  
   134  		// Set the input on the transaction.
   135  		retTxn.TxIn = append(retTxn.TxIn, txInput)
   136  
   137  		// If the total input we've accrued thus far covers the spend amount and the
   138  		// fee precisely, then we're done.
   139  		txFee := EstimateBitcoinTxFee(
   140  			len(retTxn.TxIn), len(retTxn.TxOut), feeRateSatoshisPerKB)
   141  		if totalInputSatoshis == spendAmountSatoshis+txFee {
   142  			break
   143  		}
   144  
   145  		// Since our input did not precisely equal the spend amount plus the fee,
   146  		// see if if the input exceeds the spend amount plus the fee after we add
   147  		// a change output. If it does, then we're done. Note the +1 in the function
   148  		// call below.
   149  		txFeeWithChange := EstimateBitcoinTxFee(
   150  			len(retTxn.TxIn), len(retTxn.TxOut)+1, feeRateSatoshisPerKB)
   151  		if totalInputSatoshis >= spendAmountSatoshis+txFeeWithChange {
   152  			// In this case we add a change output to the transaction that sends the
   153  			// excess Bitcoin back to the spend address.
   154  			changeOutput := &wire.TxOut{}
   155  			changeOutput.PkScript, err = txscript.PayToAddrScript(spendAddr)
   156  			if err != nil {
   157  				return nil, 0, 0, errors.Wrapf(err, "CreateUnsignedBitcoinSpendTransaction: Problem adding script: %v", err)
   158  			}
   159  
   160  			totalOutputSatoshis := spendAmountSatoshis + txFeeWithChange
   161  			changeOutput.Value = int64(totalInputSatoshis - totalOutputSatoshis)
   162  			// Don't append a change output if it is below the dust threshold.
   163  			// TODO: Is 1,000 enough for the dust threshold?
   164  			dustOutputSatoshis := int64(1000)
   165  			if changeOutput.Value > dustOutputSatoshis {
   166  				retTxn.TxOut = append(retTxn.TxOut, changeOutput)
   167  			}
   168  
   169  			break
   170  		}
   171  	}
   172  
   173  	// At this point, if the totalInputSatoshis is not greater than or equal to
   174  	// the spend amount plus the estimated fee then we didn't have enough input
   175  	// to successfully form the transaction.
   176  	finalFee := EstimateBitcoinTxFee(
   177  		len(retTxn.TxIn), len(retTxn.TxOut), feeRateSatoshisPerKB)
   178  	if totalInputSatoshis < spendAmountSatoshis+finalFee {
   179  		return nil, 0, 0, fmt.Errorf("CreateUnsignedBitcoinSpendTransaction: Total input satoshis %d is "+
   180  			"not sufficient to cover the spend amount %d plus the fee %d",
   181  			totalInputSatoshis, spendAmountSatoshis, finalFee)
   182  	}
   183  
   184  	return retTxn, totalInputSatoshis, finalFee, nil
   185  }
   186  
   187  func CreateBitcoinSpendTransaction(
   188  	spendAmountSatoshis uint64, feeRateSatoshisPerKB uint64,
   189  	pubKey *btcec.PublicKey,
   190  	recipientAddrString string, params *DeSoParams,
   191  	utxoSource func(addr string, params *DeSoParams) ([]*BitcoinUtxo, error)) (_txn *wire.MsgTx, _totalInput uint64, _fee uint64, _unsignedHashes []string, _err error) {
   192  
   193  	// Convert the public key into a Bitcoin address.
   194  	spendAddrTmp, err := btcutil.NewAddressPubKey(pubKey.SerializeCompressed(), params.BitcoinBtcdParams)
   195  	spendAddrr := spendAddrTmp.AddressPubKeyHash()
   196  	if err != nil {
   197  		return nil, 0, 0, nil, errors.Wrapf(err, "CreateBitcoinSpendTransaction: Problem converting "+
   198  			"pubkey to address: ")
   199  	}
   200  	spendAddrString := spendAddrr.EncodeAddress()
   201  
   202  	glog.V(2).Infof("CreateBitcoinSpendTransaction: Creating spend for "+
   203  		"<from: %s, to: %s> for amount %d, feeRateSatoshisPerKB %d",
   204  		spendAddrString,
   205  		recipientAddrString, spendAmountSatoshis, feeRateSatoshisPerKB)
   206  
   207  	retTxn, totalInputSatoshis, finalFee, err := CreateUnsignedBitcoinSpendTransaction(
   208  		spendAmountSatoshis, feeRateSatoshisPerKB, spendAddrString,
   209  		recipientAddrString, params, utxoSource)
   210  	if err != nil {
   211  		return nil, 0, 0, nil, errors.Wrapf(err, "CreateBitcoinSpendTransaction: Problem "+
   212  			"creating unsigned Bitcoin spend: ")
   213  	}
   214  
   215  	// At this point we are confident the transaction has enough input to cover its
   216  	// outputs. Now go through and set the input scripts.
   217  	//
   218  	// All the inputs are signing an identical output script corresponding to the
   219  	// spend address.
   220  	outputScriptToSign, err := txscript.PayToAddrScript(spendAddrr)
   221  	if err != nil {
   222  		return nil, 0, 0, nil, errors.Wrapf(err, "CreateBitcoinSpendTransaction: Problem computing "+
   223  			"output script for spendAddr %v", spendAddrr)
   224  	}
   225  
   226  	// Calculate the unsigned hash for each input
   227  	// We pass these to the identity service on the frontend for the client to sign
   228  	unsignedHashes := []string{}
   229  	for ii := range retTxn.TxIn {
   230  		hashBytes, err := txscript.CalcSignatureHash(outputScriptToSign, txscript.SigHashAll, retTxn, ii)
   231  		if err != nil {
   232  			return nil, 0, 0, nil, errors.Wrapf(err, "CreateBitcoinSpendTransaction: Problem "+
   233  				"creating signature hash for input %d", ii)
   234  		}
   235  
   236  		unsignedHashes = append(unsignedHashes, hex.EncodeToString(hashBytes))
   237  	}
   238  
   239  	// At this point all the inputs should be signed and the total input should cover
   240  	// the spend amount plus the fee with any change going back to the spend address.
   241  	glog.V(2).Infof("CreateBitcoinSpendTransaction: Created transaction with "+
   242  		"(%d inputs, %d outputs, %d total input, %d spend amount, %d change, %d fee)",
   243  		len(retTxn.TxIn), len(retTxn.TxOut), totalInputSatoshis,
   244  		spendAmountSatoshis, totalInputSatoshis-spendAmountSatoshis-finalFee, finalFee)
   245  
   246  	return retTxn, totalInputSatoshis, finalFee, unsignedHashes, nil
   247  }
   248  
   249  func IsBitcoinTestnet(params *DeSoParams) bool {
   250  	return params.BitcoinBtcdParams.Name == "testnet3"
   251  }
   252  
   253  // ======================================================================================
   254  // BlockCypher API code
   255  //
   256  // This is bascially a user experience hack. We use BlockCypher to fetch
   257  // BitcoinUtxos to determine what the user is capable of spending from their address
   258  // and to craft burn transactions for the user using their address and available utxos. Note
   259  // that we could do this in the BitcoinManager and cut reliance on BlockCypher to make
   260  // decentralized, but it would increase complexity significantly. Moreover, this
   261  // piece is not critical to the protocol or to consensus, and it breaking doesn't even
   262  // stop people from being able to purchase DeSo since they can always do that by sending
   263  // Bitcoin to the burn address from any standard Bitcoin client after entering their
   264  // seed phrase.
   265  // ======================================================================================
   266  
   267  type BlockCypherAPIInputResponse struct {
   268  	PrevTxIDHex    string   `json:"prev_hash"`
   269  	Index          int64    `json:"output_index"`
   270  	ScriptHex      string   `json:"script"`
   271  	AmountSatoshis int64    `json:"output_value"`
   272  	Sequence       int64    `json:"sequence"`
   273  	Addresses      []string `json:"addresses"`
   274  	ScriptType     string   `json:"script_type"`
   275  	Age            int64    `json:"age"`
   276  }
   277  
   278  type BlockCypherAPIOutputResponse struct {
   279  	AmountSatoshis int64    `json:"value"`
   280  	ScriptHex      string   `json:"script"`
   281  	Addresses      []string `json:"addresses"`
   282  	ScriptType     string   `json:"script_type"`
   283  	SpentBy        string   `json:"spent_by"`
   284  }
   285  
   286  type BlockCypherAPITxnResponse struct {
   287  	BlockHashHex  string                          `json:"block_hash"`
   288  	BlockHeight   int64                           `json:"block_height"`
   289  	LockTime      int64                           `json:"lock_time"`
   290  	TxIDHex       string                          `json:"hash"`
   291  	Inputs        []*BlockCypherAPIInputResponse  `json:"inputs"`
   292  	Outputs       []*BlockCypherAPIOutputResponse `json:"outputs"`
   293  	Confirmations int64                           `json:"confirmations"`
   294  	DoubleSpend   bool                            `json:"double_spend"`
   295  }
   296  
   297  type BlockCypherAPIFullAddressResponse struct {
   298  	Address string `json:"address"`
   299  	// Balance data
   300  	ConfirmedBalance   int64 `json:"balance"`
   301  	UnconfirmedBalance int64 `json:"unconfirmed_balance"`
   302  	FinalBalance       int64 `json:"final_balance"`
   303  
   304  	// Transaction data
   305  	Txns []*BlockCypherAPITxnResponse `json:"txs"`
   306  
   307  	HasMore bool `json:"hasMore"`
   308  
   309  	Error string `json:"error"`
   310  }
   311  
   312  type BlockchainInfoAPIResponse struct {
   313  	DoubleSpend bool `json:"double_spend"`
   314  }
   315  
   316  func BlockCypherExtractBitcoinUtxosFromResponse(
   317  	apiData *BlockCypherAPIFullAddressResponse, addrString string, params *DeSoParams) (
   318  	[]*BitcoinUtxo, error) {
   319  
   320  	glog.V(2).Infof("BlockCypherExtractBitcoinUtxosFromResponse: Extracting BitcoinUtxos "+
   321  		"from %d txns", len(apiData.Txns))
   322  	addr, err := btcutil.DecodeAddress(addrString, params.BitcoinBtcdParams)
   323  	if err != nil {
   324  		return nil, fmt.Errorf("BlockCypherExtractBitcoinUtxosFromResponse: "+
   325  			"Error decoding address %v: %v", addrString, err)
   326  	}
   327  	outputScriptForAddr, err := txscript.PayToAddrScript(addr)
   328  	if err != nil {
   329  		return nil, fmt.Errorf("BlockCypherExtractBitcoinUtxosFromResponse: "+
   330  			"Error creating output script for addr %v: %v", addrString, err)
   331  	}
   332  
   333  	// Go through and index all of the outputs that appear as inputs. The reason
   334  	// we must do this is because the API only sets to SpentBy field if a transaction
   335  	// has at least one confirmation. So, in order to mark outputs as spent when they
   336  	// appear in transactions that aren't confirmed, we need to make this adjustment.
   337  	//
   338  	// We overload the BitcoinUtxo here because it's easier than defining a new struct.
   339  	type utxoKey struct {
   340  		TxIDHex string
   341  		Index   int64
   342  	}
   343  	outputsSpentInResponseInputs := make(map[utxoKey]bool)
   344  	for _, txn := range apiData.Txns {
   345  		for _, input := range txn.Inputs {
   346  			currentUtxo := utxoKey{
   347  				TxIDHex: input.PrevTxIDHex,
   348  				Index:   input.Index,
   349  			}
   350  			outputsSpentInResponseInputs[currentUtxo] = true
   351  		}
   352  
   353  	}
   354  
   355  	bitcoinUtxos := []*BitcoinUtxo{}
   356  	for _, txn := range apiData.Txns {
   357  		for outputIndex, output := range txn.Outputs {
   358  			if output.SpentBy != "" {
   359  				// This is how we determine if an output is spent or not.
   360  				continue
   361  			}
   362  			if output.ScriptHex != hex.EncodeToString(outputScriptForAddr) {
   363  				// Only outputs that are destined for the passed-in address are considered.
   364  				continue
   365  			}
   366  			if output.AmountSatoshis == 0 {
   367  				// Ignore outputs that don't have any BTC in them. This is also helpful
   368  				// to prevent weird bugs where the API response changed the name of a
   369  				// field or something.
   370  				continue
   371  			}
   372  
   373  			// If the output is spent in one of the inputs of the API response then
   374  			// consider it spent here. We do this to count confirmed transactions in
   375  			// the utxo set.
   376  			currentUtxo := utxoKey{
   377  				TxIDHex: txn.TxIDHex,
   378  				Index:   int64(outputIndex),
   379  			}
   380  			if _, exists := outputsSpentInResponseInputs[currentUtxo]; exists {
   381  				continue
   382  			}
   383  
   384  			// If we get here we know we are dealing with an unspent output destined
   385  			// for the address passed in.
   386  
   387  			bUtxo := &BitcoinUtxo{}
   388  			bUtxo.AmountSatoshis = output.AmountSatoshis
   389  			bUtxo.Index = int64(outputIndex)
   390  			txid := (chainhash.Hash)(*mustDecodeHexBlockHashBitcoin(txn.TxIDHex))
   391  			bUtxo.TxID = &txid
   392  
   393  			bitcoinUtxos = append(bitcoinUtxos, bUtxo)
   394  		}
   395  	}
   396  
   397  	glog.V(2).Infof("BlockCypherExtractBitcoinUtxosFromResponse: Extracted %d BitcoinUtxos",
   398  		len(bitcoinUtxos))
   399  
   400  	return bitcoinUtxos, nil
   401  }
   402  
   403  func GetBlockCypherAPIFullAddressResponse(addrString string, params *DeSoParams) (
   404  	_apiData *BlockCypherAPIFullAddressResponse, _err error) {
   405  
   406  	URL := fmt.Sprintf("http://api.blockcypher.com/v1/btc/main/addrs/%s/full", addrString)
   407  	if IsBitcoinTestnet(params) {
   408  		URL = fmt.Sprintf("http://api.blockcypher.com/v1/btc/test3/addrs/%s/full", addrString)
   409  	}
   410  	glog.V(2).Infof("GetBlockCypherAPIFullAddressResponse: Querying URL: %s", URL)
   411  
   412  	// jsonValue, err := json.Marshal(postData)
   413  	req, _ := http.NewRequest("GET", URL, nil)
   414  	req.Header.Set("Content-Type", "application/json")
   415  
   416  	// To add a ?a=b query string, use the below.
   417  	q := req.URL.Query()
   418  	// TODO: Right now we'll only fetch a maximum of 50 transactions from the API.
   419  	// This means if the user has done more than 50 transactions with their address
   420  	// we'll start missing some of the older utxos. This is easy to fix, though, and
   421  	// just amounts to cycling through the API's pages. Note also that this does not
   422  	// prevent a user from buying DeSo in this case nor does it prevent her from being
   423  	// able to recover her Bitcoin. Both of these can be accomplished by loading the
   424  	// address in a standard Bitcoin wallet like Electrum.
   425  	q.Add("limit", "50")
   426  	req.URL.RawQuery = q.Encode()
   427  	glog.V(2).Infof("GetBlockCypherAPIFullAddressResponse: URL with params: %s", req.URL)
   428  
   429  	client := &http.Client{}
   430  	resp, err := client.Do(req)
   431  	if err != nil {
   432  		return nil, fmt.Errorf("GetBlockCypherAPIFullAddressResponse: Problem with HTTP request %s: %v", URL, err)
   433  	}
   434  	defer resp.Body.Close()
   435  
   436  	// Decode the response into the appropriate struct.
   437  	body, _ := ioutil.ReadAll(resp.Body)
   438  	responseData := &BlockCypherAPIFullAddressResponse{}
   439  	decoder := json.NewDecoder(bytes.NewReader(body))
   440  	if err := decoder.Decode(responseData); err != nil {
   441  		return nil, fmt.Errorf("GetBlockCypherAPIFullAddressResponse: Problem decoding response JSON into "+
   442  			"interface %v, response: %v, error: %v", responseData, resp, err)
   443  	}
   444  	//glog.V(2).Infof("BlockCypherUtxoSource: Received response: %v", responseData)
   445  
   446  	if responseData.Error != "" {
   447  		return nil, fmt.Errorf("GetBlockCypherAPIFullAddressResponse: Had an "+
   448  			"error in the response: %s", responseData.Error)
   449  	}
   450  
   451  	return responseData, nil
   452  }
   453  
   454  func BlockCypherUtxoSource(addrString string, params *DeSoParams) (
   455  	[]*BitcoinUtxo, error) {
   456  
   457  	apiData, err := GetBlockCypherAPIFullAddressResponse(addrString, params)
   458  	if err != nil {
   459  		return nil, errors.Wrapf(err, "BlockCypherUtxoSource: Problem getting API data "+
   460  			"for address %s: ", addrString)
   461  	}
   462  
   463  	return BlockCypherExtractBitcoinUtxosFromResponse(apiData, addrString, params)
   464  }
   465  
   466  // The frontend passes in the apiData. We do this so that our server doesn't
   467  // get rate-limited by the free tier.
   468  func FrontendBlockCypherUtxoSource(
   469  	apiData *BlockCypherAPIFullAddressResponse, addrString string,
   470  	params *DeSoParams) (
   471  	[]*BitcoinUtxo, error) {
   472  
   473  	return BlockCypherExtractBitcoinUtxosFromResponse(apiData, addrString, params)
   474  }
   475  
   476  func BlockchainInfoCheckBitcoinDoubleSpend(txnHash *chainhash.Hash, blockCypherAPIKey string, params *DeSoParams) (
   477  	_isDoubleSpend bool, _err error) {
   478  
   479  	// Always pass on testnet for simplicity.
   480  	if IsBitcoinTestnet(params) {
   481  		return false, nil
   482  	}
   483  	URL := fmt.Sprintf("https://blockchain.info/rawtx/%s", txnHash.String())
   484  	glog.V(2).Infof("BlockchainInfoCheckBitcoinDoubleSpend: Querying URL: %s", URL)
   485  
   486  	req, _ := http.NewRequest("GET", URL, nil)
   487  	req.Header.Set("Content-Type", "application/json")
   488  
   489  	client := &http.Client{}
   490  	resp, err := client.Do(req)
   491  	if err != nil {
   492  		return false, fmt.Errorf("BlockchainInfoCheckBitcoinDoubleSpend: "+
   493  			"Problem with HTTP request %s: %v", URL, err)
   494  	}
   495  	defer resp.Body.Close()
   496  
   497  	if resp.StatusCode != 200 {
   498  		glog.V(2).Infof("BlockchainInfoCheckBitcoinDoubleSpend: Bitcoin txn with "+
   499  			"hash %v was not found in Blockchain.info OR an error occurred: %v", txnHash, spew.Sdump(resp))
   500  		return true, nil
   501  	}
   502  
   503  	// Decode the response into the appropriate struct.
   504  	body, _ := ioutil.ReadAll(resp.Body)
   505  	responseData := &BlockchainInfoAPIResponse{}
   506  	decoder := json.NewDecoder(bytes.NewReader(body))
   507  	if err := decoder.Decode(responseData); err != nil {
   508  		return false, fmt.Errorf("BlockchainInfoCheckBitcoinDoubleSpend: "+
   509  			"Problem decoding response JSON into "+
   510  			"interface %v, response: %v, error: %v", responseData, resp, err)
   511  	}
   512  
   513  	if responseData.DoubleSpend {
   514  		glog.V(2).Infof("BlockchainInfoCheckBitcoinDoubleSpend: Bitcoin "+
   515  			"txn with hash %v was a double spend", txnHash)
   516  		return true, nil
   517  	}
   518  
   519  	return false, nil
   520  }
   521  
   522  func GetBlockCypherTxnResponse(txnHash *chainhash.Hash, blockCypherAPIKey string, params *DeSoParams) (*BlockCypherAPITxnResponse, error) {
   523  	URL := fmt.Sprintf("http://api.blockcypher.com/v1/btc/main/txs/%s", txnHash.String())
   524  	if IsBitcoinTestnet(params) {
   525  		URL = fmt.Sprintf("http://api.blockcypher.com/v1/btc/test3/txs/%s", txnHash.String())
   526  	}
   527  	glog.V(2).Infof("CheckBitcoinDoubleSpend: Querying URL: %s", URL)
   528  
   529  	// jsonValue, err := json.Marshal(postData)
   530  	req, _ := http.NewRequest("GET", URL, nil)
   531  	req.Header.Set("Content-Type", "application/json")
   532  
   533  	// To add a ?a=b query string, use the below.
   534  	q := req.URL.Query()
   535  	// TODO: Right now we'll only fetch a maximum of 50 transactions from the API.
   536  	// This means if the user has done more than 50 transactions with their address
   537  	// we'll start missing some of the older utxos. This is easy to fix, though, and
   538  	// just amounts to cycling through the API's pages. Note also that this does not
   539  	// prevent a user from buying DeSo in this case nor does it prevent her from being
   540  	// able to recover her Bitcoin. Both of these can be accomplished by loading the
   541  	// address in a standard Bitcoin wallet like Electrum.
   542  	q.Add("token", blockCypherAPIKey)
   543  	req.URL.RawQuery = q.Encode()
   544  	glog.V(2).Infof("CheckBitcoinDoubleSpend: URL with params: %s", req.URL)
   545  
   546  	client := &http.Client{}
   547  	resp, err := client.Do(req)
   548  	if err != nil {
   549  		return nil, fmt.Errorf("CheckBitcoinDoubleSpend: Problem with HTTP request %s: %v", URL, err)
   550  	}
   551  	defer resp.Body.Close()
   552  
   553  	if resp.StatusCode == 404 {
   554  		return nil, nil
   555  	} else if resp.StatusCode != 200 {
   556  		body, _ := ioutil.ReadAll(resp.Body)
   557  		return nil, fmt.Errorf("CheckBitcoinDoubleSpend: Error code returned "+
   558  			"from BlockCypher: %v %v", resp.StatusCode, string(body))
   559  	}
   560  
   561  	// Decode the response into the appropriate struct.
   562  	body, _ := ioutil.ReadAll(resp.Body)
   563  	responseData := &BlockCypherAPITxnResponse{}
   564  	decoder := json.NewDecoder(bytes.NewReader(body))
   565  	if err := decoder.Decode(responseData); err != nil {
   566  		return nil, fmt.Errorf("CheckBitcoinDoubleSpend: Problem decoding response JSON into "+
   567  			"interface %v, response: %v, error: %v", responseData, resp, err)
   568  	}
   569  
   570  	return responseData, nil
   571  }
   572  
   573  func BlockCypherCheckBitcoinDoubleSpend(txnHash *chainhash.Hash, blockCypherAPIKey string, params *DeSoParams) (
   574  	_isDoubleSpend bool, _err error) {
   575  
   576  	glog.Infof("CheckBitcoinDoubleSpend: Checking txn %v for double-spend", txnHash)
   577  	responseData, err := GetBlockCypherTxnResponse(txnHash, blockCypherAPIKey, params)
   578  	if err != nil {
   579  		return false, errors.Wrapf(err, "BlockCypherCheckBitcoinDoubleSpend: Error fetching txn: ")
   580  	}
   581  
   582  	// If we didn't find the txn in BlockCypher then we consider this a double-spend
   583  	if responseData == nil {
   584  		glog.V(2).Infof("CheckBitcoinDoubleSpend: Bitcoin txn with hash %v was not found in BlockCypher", txnHash)
   585  		return true, nil
   586  	}
   587  
   588  	if responseData.DoubleSpend {
   589  		glog.V(2).Infof("CheckBitcoinDoubleSpend: Bitcoin txn with hash %v was a double spend", txnHash)
   590  		return true, nil
   591  	}
   592  
   593  	// If the transaction is not mined, then we need to recursively check
   594  	// its inputs to determine whether they are double-spends.
   595  	if responseData.Confirmations == 0 {
   596  		// If there are too many inputs on this txn, then just mark it as a double-spend.
   597  		// This avoids us having to use our API quota unnecessarily.
   598  		if len(responseData.Inputs) > 25 {
   599  			glog.Warningf("CheckBitcoinDoubleSpend: Unmined bitcoin txn with hash %v had %v inputs, "+
   600  				"which made us label it as a double-spend. If this is happening a lot, consider "+
   601  				"increasing the limit on this if statement", txnHash, len(responseData.Inputs))
   602  			return true, nil
   603  		}
   604  		// Recursively scan through the unmined inputs one at a time to see
   605  		// if any ancestors contain double-spends. If they do, then this txn is
   606  		// a double-spend by transitivity.
   607  		for _, txIn := range responseData.Inputs {
   608  			inputTxHash, err := chainhash.NewHashFromStr(txIn.PrevTxIDHex)
   609  			if err != nil {
   610  				return false, errors.Wrapf(
   611  					err, "BlockCypherCheckBitcoinDoubleSpend: Error parsing INPUT txn hash: %v", inputTxHash)
   612  			}
   613  			glog.V(1).Infof("CheckBitcoinDoubleSpend: Checking INPUT %v for double-spend", inputTxHash)
   614  			inputIsDoubleSpend, err := BlockCypherCheckBitcoinDoubleSpend(inputTxHash, blockCypherAPIKey, params)
   615  			if err != nil {
   616  				return false, errors.Wrapf(
   617  					err, "BlockCypherCheckBitcoinDoubleSpend: Error fetching INPUT txn: %v", inputTxHash)
   618  			}
   619  			if inputIsDoubleSpend {
   620  				return true, nil
   621  			}
   622  		}
   623  	}
   624  	// If we get here, it means that this txn wasn't a double-spend *and*
   625  	// none of its unmined inputs were double-spends either.
   626  
   627  	return false, nil
   628  }
   629  func BlockCypherPushTransaction(txnHex string, txnHash *chainhash.Hash, blockCypherAPIKey string, params *DeSoParams) (
   630  	_added bool, _err error) {
   631  
   632  	URL := fmt.Sprintf("http://api.blockcypher.com/v1/btc/main/txs/push")
   633  	if IsBitcoinTestnet(params) {
   634  		URL = fmt.Sprintf("http://api.blockcypher.com/v1/btc/test3/txs/push")
   635  	}
   636  	glog.V(2).Infof("PushTransaction: Querying URL: %s", URL)
   637  
   638  	json_data, err := json.Marshal(map[string]string{
   639  		"tx": txnHex,
   640  	})
   641  	if err != nil {
   642  		return false, fmt.Errorf(
   643  			"PushTransaction: Error encoding request as JSON %s: %v", URL, err)
   644  	}
   645  
   646  	// jsonValue, err := json.Marshal(postData)
   647  	req, _ := http.NewRequest("POST", URL, bytes.NewBuffer(json_data))
   648  	req.Header.Set("Content-Type", "application/json")
   649  
   650  	// To add a ?a=b query string, use the below.
   651  	q := req.URL.Query()
   652  	// TODO: Right now we'll only fetch a maximum of 50 transactions from the API.
   653  	// This means if the user has done more than 50 transactions with their address
   654  	// we'll start missing some of the older utxos. This is easy to fix, though, and
   655  	// just amounts to cycling through the API's pages. Note also that this does not
   656  	// prevent a user from buying DeSo in this case nor does it prevent her from being
   657  	// able to recover her Bitcoin. Both of these can be accomplished by loading the
   658  	// address in a standard Bitcoin wallet like Electrum.
   659  	q.Add("token", blockCypherAPIKey)
   660  	req.URL.RawQuery = q.Encode()
   661  	glog.V(2).Infof("PushTransaction: URL with params: %s", req.URL)
   662  
   663  	client := &http.Client{}
   664  	resp, err := client.Do(req)
   665  	if err != nil {
   666  		return false, fmt.Errorf("PushTransaction: Problem with HTTP request %s: %v", URL, err)
   667  	}
   668  	defer resp.Body.Close()
   669  
   670  	body, _ := ioutil.ReadAll(resp.Body)
   671  	if resp.StatusCode == 201 {
   672  		glog.V(1).Infof("PushTransaction: Successfully added BitcoinExchange "+
   673  			"txn hash: %v, full txn: %v body: %v", txnHash, txnHex, string(body))
   674  		return true, nil
   675  	}
   676  
   677  	// If we get here then we had a bad status code.
   678  	return false, fmt.Errorf("PushTransaction: Failed to submit transaction "+
   679  		"to Bitcoin blockchain: %v, Body: %v, Txn Hash: %v", resp.StatusCode, string(body), txnHash)
   680  }
   681  
   682  func CheckBitcoinDoubleSpend(txnHash *chainhash.Hash, blockCypherAPIKey string, params *DeSoParams) error {
   683  
   684  	{
   685  		isDoubleSpend, err := BlockCypherCheckBitcoinDoubleSpend(txnHash, blockCypherAPIKey, params)
   686  		if err != nil {
   687  			return fmt.Errorf("PushAndWaitForTxn: Error occurred when checking for "+
   688  				"double-spend on BlockCypher. Your transaction will go through once it "+
   689  				"has been mined into a Bitcoin block. Txn hash: %v", txnHash)
   690  		}
   691  		if isDoubleSpend {
   692  			return fmt.Errorf("PushAndWaitForTxn: Error: double-spend detected "+
   693  				"by BlockCypher. Your transaction will go through once it mines into the "+
   694  				"next Bitcoin block, which should take about ten minutes. Txn hash: %v", txnHash)
   695  		}
   696  	}
   697  
   698  	// Also check the Blockchain.com API for a double-spend. This prevents an attack
   699  	// that exploits a weakness in BlockCypher's APIs.
   700  	//
   701  	// TODO: Document this attack later.
   702  	{
   703  		isDoubleSpend, err := BlockchainInfoCheckBitcoinDoubleSpend(txnHash, blockCypherAPIKey, params)
   704  		if err != nil {
   705  			return fmt.Errorf("PushAndWaitForTxn: Error occurred when checking for "+
   706  				"double-spend on blockchain.info. Your transaction will go through once "+
   707  				"it has been mined into a Bitcoin block. Txn hash: %v", txnHash)
   708  		}
   709  		if isDoubleSpend {
   710  			return fmt.Errorf("PushAndWaitForTxn: Error: double-spend detected "+
   711  				"by blockchain.info. Your transaction will go through once it mines "+
   712  				"into the next Bitcoin block. Txn hash: %v", txnHash)
   713  		}
   714  	}
   715  
   716  	// If we get here then there was no double-spend detected.
   717  	return nil
   718  }
   719  
   720  func BlockCypherPushAndWaitForTxn(txnHex string, txnHash *chainhash.Hash,
   721  	blockCypherAPIKey string, doubleSpendWaitSeconds float64, params *DeSoParams) (_isDoubleSpend bool, _err error) {
   722  	_, err := BlockCypherPushTransaction(txnHex, txnHash, blockCypherAPIKey, params)
   723  	if err != nil {
   724  		return false, fmt.Errorf("PushAndWaitForTxn: %v", err)
   725  	}
   726  	// Wait some amount of time before checking for a double-spend.
   727  	time.Sleep(time.Duration(doubleSpendWaitSeconds) * time.Second)
   728  
   729  	if err := CheckBitcoinDoubleSpend(txnHash, blockCypherAPIKey, params); err != nil {
   730  		return true, err
   731  	}
   732  
   733  	return false, nil
   734  }
   735  
   736  type BlockonomicsRBFResponse struct {
   737  	RBF    int64  `json:"rbf"`
   738  	Status string `json:"status"`
   739  }
   740  
   741  func BlockonomicsCheckRBF(bitcoinTxnHash string) (
   742  	_hasRBF bool, _err error) {
   743  
   744  	URL := fmt.Sprintf("https://www.blockonomics.co/api/tx_detail?txid=%s", bitcoinTxnHash)
   745  	glog.V(1).Infof("BlockonomicsCheckRBF: Querying URL: %s", URL)
   746  
   747  	req, _ := http.NewRequest("GET", URL, nil)
   748  	req.Header.Set("Content-Type", "application/json")
   749  
   750  	client := &http.Client{}
   751  	resp, err := client.Do(req)
   752  	if err != nil {
   753  		return false, fmt.Errorf("BlockonomicsCheckRBF: Problem with HTTP request %s: %v", URL, err)
   754  	}
   755  	defer resp.Body.Close()
   756  
   757  	// Decode the body.
   758  	body, _ := ioutil.ReadAll(resp.Body)
   759  	if resp.StatusCode != 200 {
   760  		retErr := fmt.Errorf("BlockonomicsCheckRBF: Error when checking "+
   761  			"RBF tatus for txn %v: %v", bitcoinTxnHash, string(body))
   762  		return false, retErr
   763  	}
   764  
   765  	responseData := &BlockonomicsRBFResponse{}
   766  	decoder := json.NewDecoder(bytes.NewReader(body))
   767  	if err := decoder.Decode(responseData); err != nil {
   768  		return false, fmt.Errorf("BlockonomicsCheckRBF: Problem decoding response JSON into "+
   769  			"interface %v, response: %v, body: %v, error: %v", responseData, resp, string(body), err)
   770  	}
   771  	//glog.V(2).Infof("UtxoSource: Received response: %v", responseData)
   772  
   773  	if strings.ToLower(responseData.Status) == "unconfirmed" &&
   774  		(responseData.RBF == 1 || responseData.RBF == 2) {
   775  
   776  		glog.V(1).Infof("BlockonomicsCheckRBF: Bitcoin txn with hash %v has RBF set", bitcoinTxnHash)
   777  		return true, nil
   778  	}
   779  
   780  	return false, nil
   781  }