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

     1  package lib
     2  
     3  import (
     4  	"fmt"
     5  	"github.com/btcsuite/btcd/btcec"
     6  	"github.com/btcsuite/btcd/chaincfg"
     7  	"github.com/btcsuite/btcd/txscript"
     8  	"github.com/btcsuite/btcd/wire"
     9  	"github.com/btcsuite/btcutil"
    10  	"github.com/pkg/errors"
    11  	"math"
    12  	"math/big"
    13  )
    14  
    15  // The blockchain used to store the USD to BTC exchange rate in bav.USDCentsPerBitcoin, which was set by a
    16  // UPDATE_BITCOIN_USD_EXCHANGE_RATE txn, but has since moved to the GlobalParamsEntry, which is set by a
    17  // UPDATE_GLOBAL_PARAMS txn.
    18  func (bav *UtxoView) GetCurrentUSDCentsPerBitcoin() uint64 {
    19  	usdCentsPerBitcoin := bav.USDCentsPerBitcoin
    20  	if bav.GlobalParamsEntry.USDCentsPerBitcoin != 0 {
    21  		usdCentsPerBitcoin = bav.GlobalParamsEntry.USDCentsPerBitcoin
    22  	}
    23  	return usdCentsPerBitcoin
    24  }
    25  
    26  func (bav *UtxoView) _existsBitcoinTxIDMapping(bitcoinBurnTxID *BlockHash) bool {
    27  	// If an entry exists in the in-memory map, return the value of that mapping.
    28  	mapValue, existsMapValue := bav.BitcoinBurnTxIDs[*bitcoinBurnTxID]
    29  	if existsMapValue {
    30  		return mapValue
    31  	}
    32  
    33  	// If we get here it means no value exists in our in-memory map. In this case,
    34  	// defer to the db. If a mapping exists in the db, return true. If not, return
    35  	// false. Either way, save the value to the in-memory view mapping got later.
    36  	dbHasMapping := DbExistsBitcoinBurnTxID(bav.Handle, bitcoinBurnTxID)
    37  	bav.BitcoinBurnTxIDs[*bitcoinBurnTxID] = dbHasMapping
    38  	return dbHasMapping
    39  }
    40  
    41  func (bav *UtxoView) _setBitcoinBurnTxIDMappings(bitcoinBurnTxID *BlockHash) {
    42  	bav.BitcoinBurnTxIDs[*bitcoinBurnTxID] = true
    43  }
    44  
    45  func (bav *UtxoView) _deleteBitcoinBurnTxIDMappings(bitcoinBurnTxID *BlockHash) {
    46  	bav.BitcoinBurnTxIDs[*bitcoinBurnTxID] = false
    47  }
    48  
    49  func ExtractBitcoinPublicKeyFromBitcoinTransactionInputs(
    50  	bitcoinTransaction *wire.MsgTx, btcdParams *chaincfg.Params) (
    51  	_publicKey *btcec.PublicKey, _err error) {
    52  
    53  	for _, input := range bitcoinTransaction.TxIn {
    54  		// P2PKH follows the form: <sig len> <sig> <pubKeyLen> <pubKey>
    55  		if len(input.SignatureScript) == 0 {
    56  			continue
    57  		}
    58  		sigLen := input.SignatureScript[0]
    59  		pubKeyStart := sigLen + 2
    60  		pubKeyBytes := input.SignatureScript[pubKeyStart:]
    61  		addr, err := btcutil.NewAddressPubKey(pubKeyBytes, btcdParams)
    62  		if err != nil {
    63  			continue
    64  		}
    65  
    66  		// If we were able to successfully decode the bytes into a public key, return it.
    67  		if addr.PubKey() != nil {
    68  			return addr.PubKey(), nil
    69  		}
    70  
    71  		// If we get here it means we could not extract a public key from this
    72  		// particular input. This is OK as long as we can find a public key in
    73  		// one of the other inputs.
    74  	}
    75  
    76  	// If we get here it means we went through all the inputs and were not able to
    77  	// successfully decode a public key from the inputs. Error in this case.
    78  	return nil, fmt.Errorf("ExtractBitcoinPublicKeyFromBitcoinTransactionInputs: " +
    79  		"No valid public key found after scanning all input signature scripts")
    80  }
    81  
    82  func _computeBitcoinBurnOutput(bitcoinTransaction *wire.MsgTx, bitcoinBurnAddress string,
    83  	btcdParams *chaincfg.Params) (_burnedOutputSatoshis int64, _err error) {
    84  
    85  	totalBurnedOutput := int64(0)
    86  	for _, output := range bitcoinTransaction.TxOut {
    87  		class, addresses, _, err := txscript.ExtractPkScriptAddrs(
    88  			output.PkScript, btcdParams)
    89  		if err != nil {
    90  			// If we hit an error processing an output just let it slide. We only honor
    91  			// P2PKH transactions and even this we do on a best-effort basis.
    92  			//
    93  			// TODO: Run this over a few Bitcoin blocks to see what its errors look like
    94  			// so we can catch them here.
    95  			continue
    96  		}
    97  		// We only allow P2PK and P2PKH transactions to be counted as burns. Allowing
    98  		// anything else would require making this logic more sophisticated. Additionally,
    99  		// limiting the gamut of possible transactions protects us from weird attacks
   100  		// whereby someone could make us think that some Bitcoin was burned when really
   101  		// it's just some fancy script that fools us into thinking that.
   102  		if !(class == txscript.PubKeyTy || class == txscript.PubKeyHashTy) {
   103  			continue
   104  		}
   105  		// We only process outputs if they have a single address in them, which should
   106  		// be the case anyway given the classes we're limiting ourselves to above.
   107  		if len(addresses) != 1 {
   108  			continue
   109  		}
   110  
   111  		// At this point we're confident that we're dealing with a nice vanilla
   112  		// P2PK or P2PKH output that contains just one address that its making a
   113  		// simple payment to.
   114  
   115  		// Extract the address and add its output to the total if it happens to be
   116  		// equal to the burn address.
   117  		outputAddress := addresses[0]
   118  		if outputAddress.EncodeAddress() == bitcoinBurnAddress {
   119  			// Check for overflow just in case.
   120  			if output.Value < 0 || totalBurnedOutput > math.MaxInt64-output.Value {
   121  				return 0, fmt.Errorf("_computeBitcoinBurnOutput: output value %d would "+
   122  					"overflow totalBurnedOutput %d; this should never happen",
   123  					output.Value, totalBurnedOutput)
   124  			}
   125  			totalBurnedOutput += output.Value
   126  		}
   127  	}
   128  
   129  	return totalBurnedOutput, nil
   130  }
   131  
   132  func (bav *UtxoView) _connectBitcoinExchange(
   133  	txn *MsgDeSoTxn, txHash *BlockHash, blockHeight uint32, verifySignatures bool) (
   134  	_totalInput uint64, _totalOutput uint64, _utxoOps []*UtxoOperation, _err error) {
   135  
   136  	if bav.Params.DeflationBombBlockHeight != 0 &&
   137  		uint64(blockHeight) >= bav.Params.DeflationBombBlockHeight {
   138  
   139  		return 0, 0, nil, RuleErrorDeflationBombForbidsMintingAnyMoreDeSo
   140  	}
   141  
   142  	// Check that the transaction has the right TxnType.
   143  	if txn.TxnMeta.GetTxnType() != TxnTypeBitcoinExchange {
   144  		return 0, 0, nil, fmt.Errorf("_connectBitcoinExchange: called with bad TxnType %s",
   145  			txn.TxnMeta.GetTxnType().String())
   146  	}
   147  	txMetaa := txn.TxnMeta.(*BitcoinExchangeMetadata)
   148  
   149  	// Verify that the the transaction has:
   150  	// - no inputs
   151  	// - no outputs
   152  	// - no public key
   153  	// - no signature
   154  	//
   155  	// For BtcExchange transactions the only thing that should be set is the
   156  	// BitcoinExchange metadata. This is because we derive all of the other
   157  	// fields for this transaction from the underlying BitcoinTransaction in
   158  	// the metadata. Not doing this would potentially open up avenues for people
   159  	// to repackage Bitcoin burn transactions paying themselves rather than the person
   160  	// who originally burned the Bitcoin.
   161  	if len(txn.TxInputs) != 0 {
   162  		return 0, 0, nil, RuleErrorBitcoinExchangeShouldNotHaveInputs
   163  	}
   164  	if len(txn.TxOutputs) != 0 {
   165  		return 0, 0, nil, RuleErrorBitcoinExchangeShouldNotHaveOutputs
   166  	}
   167  	if len(txn.PublicKey) != 0 {
   168  		return 0, 0, nil, RuleErrorBitcoinExchangeShouldNotHavePublicKey
   169  	}
   170  	if txn.Signature != nil {
   171  		return 0, 0, nil, RuleErrorBitcoinExchangeShouldNotHaveSignature
   172  	}
   173  
   174  	// Check that the BitcoinTransactionHash has not been used in a BitcoinExchange
   175  	// transaction in the past. This ensures that all the Bitcoin that is burned can
   176  	// be converted to DeSo precisely one time. No need to worry about malleability
   177  	// because we also verify that the transaction was mined into a valid Bitcoin block
   178  	// with a lot of work on top of it, which means we can't be tricked by someone
   179  	// twiddling the transaction to give it a different hash (unless the Bitcoin chain
   180  	// is also tricked, in which case we have bigger problems).
   181  	bitcoinTxHash := (BlockHash)(txMetaa.BitcoinTransaction.TxHash())
   182  	if bav._existsBitcoinTxIDMapping(&bitcoinTxHash) {
   183  		return 0, 0, nil, RuleErrorBitcoinExchangeDoubleSpendingBitcoinTransaction
   184  	}
   185  
   186  	if verifySignatures {
   187  		// We don't check for signatures and we don't do any checks to verify that
   188  		// the inputs of the BitcoinTransaction are actually entitled to spend their
   189  		// outputs. We get away with this because we check that the transaction
   190  		// was mined into a Bitcoin block with a lot of work on top of it, which
   191  		// would presumably be near-impossible if the Bitcoin transaction were invalid.
   192  	}
   193  
   194  	// Extract a public key from the BitcoinTransaction's inputs. Note that we only
   195  	// consider P2PKH inputs to be valid. If no P2PKH inputs are found then we consider
   196  	// the transaction as a whole to be invalid since we don't know who to credit the
   197  	// new DeSo to. If we find more than one P2PKH input, we consider the public key
   198  	// corresponding to the first of these inputs to be the one that will receive the
   199  	// DeSo that will be created.
   200  	publicKey, err := ExtractBitcoinPublicKeyFromBitcoinTransactionInputs(
   201  		txMetaa.BitcoinTransaction, bav.Params.BitcoinBtcdParams)
   202  	if err != nil {
   203  		return 0, 0, nil, RuleErrorBitcoinExchangeValidPublicKeyNotFoundInInputs
   204  	}
   205  	// At this point, we should have extracted a public key from the Bitcoin transaction
   206  	// that we expect to credit the newly-created DeSo to.
   207  
   208  	// The burn address cannot create this type of transaction.
   209  	addrFromPubKey, err := btcutil.NewAddressPubKey(
   210  		publicKey.SerializeCompressed(), bav.Params.BitcoinBtcdParams)
   211  	if err != nil {
   212  		return 0, 0, nil, fmt.Errorf("_connectBitcoinExchange: Error "+
   213  			"converting public key to Bitcoin address: %v", err)
   214  	}
   215  	if addrFromPubKey.AddressPubKeyHash().EncodeAddress() == bav.Params.BitcoinBurnAddress {
   216  		return 0, 0, nil, RuleErrorBurnAddressCannotBurnBitcoin
   217  	}
   218  
   219  	// Go through the transaction's outputs and count up the satoshis that are being
   220  	// allocated to the burn address. If no Bitcoin is being sent to the burn address
   221  	// then we consider the transaction to be invalid. Watch out for overflow as we do
   222  	// this.
   223  	totalBurnOutput, err := _computeBitcoinBurnOutput(
   224  		txMetaa.BitcoinTransaction, bav.Params.BitcoinBurnAddress,
   225  		bav.Params.BitcoinBtcdParams)
   226  	if err != nil {
   227  		return 0, 0, nil, RuleErrorBitcoinExchangeProblemComputingBurnOutput
   228  	}
   229  	if totalBurnOutput <= 0 {
   230  		return 0, 0, nil, RuleErrorBitcoinExchangeTotalOutputLessThanOrEqualZero
   231  	}
   232  
   233  	// At this point we know how many satoshis were burned and we know the public key
   234  	// that should receive the DeSo we are going to create.
   235  	usdCentsPerBitcoin := bav.GetCurrentUSDCentsPerBitcoin()
   236  	// Compute the amount of DeSo that we should create as a result of this transaction.
   237  	nanosToCreate := CalcNanosToCreate(bav.NanosPurchased, uint64(totalBurnOutput), usdCentsPerBitcoin)
   238  
   239  	// Compute the amount of DeSo that the user will receive. Note
   240  	// that we allocate a small fee to the miner to incentivize her to include the
   241  	// transaction in a block. The fee for BitcoinExchange transactions is fixed because
   242  	// if it weren't then a miner could theoretically repackage the BitcoinTransaction
   243  	// into a new BitcoinExchange transaction that spends all of the newly-created DeSo as
   244  	// a fee. This way of doing it is a bit annoying because it means that for small
   245  	// BitcoinExchange transactions they might have to wait a long time and for large
   246  	// BitcoinExchange transactions they are highly likely to be overpaying. But it has
   247  	// the major benefit that all miners can autonomously scan the Bitcoin chain for
   248  	// burn transactions that they can turn into BitcoinExchange transactions, effectively
   249  	// making it so that the user doesn't have to manage the process of wrapping the
   250  	// Bitcoin burn into a BitcoinExchange transaction herself.
   251  	//
   252  	// We use bigints because we're paranoid about overflow. Realistically, though,
   253  	// it will never happen.
   254  	nanosToCreateBigint := big.NewInt(int64(nanosToCreate))
   255  	bitcoinExchangeFeeBigint := big.NewInt(
   256  		int64(bav.Params.BitcoinExchangeFeeBasisPoints))
   257  	// = nanosToCreate * bitcoinExchangeFeeBps
   258  	nanosTimesFeeBps := big.NewInt(0).Mul(nanosToCreateBigint, bitcoinExchangeFeeBigint)
   259  	// feeNanos = nanosToCreate * bitcoinExchangeFeeBps / 10000
   260  	feeNanosBigint := big.NewInt(0).Div(nanosTimesFeeBps, big.NewInt(10000))
   261  	if feeNanosBigint.Cmp(big.NewInt(math.MaxInt64)) > 0 ||
   262  		nanosToCreate < uint64(feeNanosBigint.Int64()) {
   263  
   264  		return 0, 0, nil, RuleErrorBitcoinExchangeFeeOverflow
   265  	}
   266  	feeNanos := feeNanosBigint.Uint64()
   267  	userNanos := nanosToCreate - feeNanos
   268  
   269  	// Now that we have all the information we need, save a UTXO allowing the user to
   270  	// spend the DeSo she's purchased in the future.
   271  	outputKey := UtxoKey{
   272  		TxID: *txn.Hash(),
   273  		// We give all UTXOs that are created as a result of BitcoinExchange transactions
   274  		// an index of zero. There is generally only one UTXO created in a BitcoinExchange
   275  		// transaction so this field doesn't really matter.
   276  		Index: 0,
   277  	}
   278  	utxoEntry := UtxoEntry{
   279  		AmountNanos: userNanos,
   280  		PublicKey:   publicKey.SerializeCompressed(),
   281  		BlockHeight: blockHeight,
   282  		UtxoType:    UtxoTypeBitcoinBurn,
   283  		UtxoKey:     &outputKey,
   284  		// We leave the position unset and isSpent to false by default.
   285  		// The position will be set in the call to _addUtxo.
   286  	}
   287  	// If we have a problem adding this utxo return an error but don't
   288  	// mark this block as invalid since it's not a rule error and the block
   289  	// could therefore benefit from being processed in the future.
   290  	newUtxoOp, err := bav._addUtxo(&utxoEntry)
   291  	if err != nil {
   292  		return 0, 0, nil, errors.Wrapf(err, "_connectBitcoinExchange: Problem adding output utxo")
   293  	}
   294  
   295  	// Rosetta uses this UtxoOperation to provide INPUT amounts
   296  	var utxoOpsForTxn []*UtxoOperation
   297  	utxoOpsForTxn = append(utxoOpsForTxn, newUtxoOp)
   298  
   299  	// Increment NanosPurchased to reflect the total nanos we created with this
   300  	// transaction, which includes the fee paid to the miner. Save the previous
   301  	// value so it can be easily reverted.
   302  	prevNanosPurchased := bav.NanosPurchased
   303  	bav.NanosPurchased += nanosToCreate
   304  
   305  	// Add the Bitcoin TxID to our unique mappings
   306  	bav._setBitcoinBurnTxIDMappings(&bitcoinTxHash)
   307  
   308  	// Save a UtxoOperation of type OperationTypeBitcoinExchange that will allow
   309  	// us to easily revert NanosPurchased when we disconnect the transaction.
   310  	utxoOpsForTxn = append(utxoOpsForTxn, &UtxoOperation{
   311  		Type:               OperationTypeBitcoinExchange,
   312  		PrevNanosPurchased: prevNanosPurchased,
   313  	})
   314  
   315  	// Note that the fee is implicitly equal to (nanosToCreate - userNanos)
   316  	return nanosToCreate, userNanos, utxoOpsForTxn, nil
   317  }
   318  
   319  func (bav *UtxoView) _connectUpdateBitcoinUSDExchangeRate(
   320  	txn *MsgDeSoTxn, txHash *BlockHash, blockHeight uint32, verifySignatures bool) (
   321  	_totalInput uint64, _totalOutput uint64, _utxoOps []*UtxoOperation, _err error) {
   322  
   323  	// Check that the transaction has the right TxnType.
   324  	if txn.TxnMeta.GetTxnType() != TxnTypeUpdateBitcoinUSDExchangeRate {
   325  		return 0, 0, nil, fmt.Errorf("_connectUpdateBitcoinUSDExchangeRate: called with bad TxnType %s",
   326  			txn.TxnMeta.GetTxnType().String())
   327  	}
   328  	txMeta := txn.TxnMeta.(*UpdateBitcoinUSDExchangeRateMetadataa)
   329  
   330  	// Validate that the exchange rate is not less than the floor as a sanity-check.
   331  	if txMeta.USDCentsPerBitcoin < MinUSDCentsPerBitcoin {
   332  		return 0, 0, nil, RuleErrorExchangeRateTooLow
   333  	}
   334  	if txMeta.USDCentsPerBitcoin > MaxUSDCentsPerBitcoin {
   335  		return 0, 0, nil, RuleErrorExchangeRateTooHigh
   336  	}
   337  
   338  	// Validate the public key. Only a paramUpdater is allowed to trigger this.
   339  	_, updaterIsParamUpdater := bav.Params.ParamUpdaterPublicKeys[MakePkMapKey(txn.PublicKey)]
   340  	if !updaterIsParamUpdater {
   341  		return 0, 0, nil, RuleErrorUserNotAuthorizedToUpdateExchangeRate
   342  	}
   343  
   344  	// Connect basic txn to get the total input and the total output without
   345  	// considering the transaction metadata.
   346  	totalInput, totalOutput, utxoOpsForTxn, err := bav._connectBasicTransfer(
   347  		txn, txHash, blockHeight, verifySignatures)
   348  	if err != nil {
   349  		return 0, 0, nil, errors.Wrapf(err, "_connectUpdateBitcoinUSDExchangeRate: ")
   350  	}
   351  
   352  	// Output must be non-zero
   353  	if totalOutput == 0 {
   354  		return 0, 0, nil, RuleErrorUserOutputMustBeNonzero
   355  	}
   356  
   357  	if verifySignatures {
   358  		// _connectBasicTransfer has already checked that the transaction is
   359  		// signed by the top-level public key, which is all we need.
   360  	}
   361  
   362  	// Update the exchange rate using the txn metadata. Save the previous value
   363  	// so it can be easily reverted.
   364  	prevUSDCentsPerBitcoin := bav.USDCentsPerBitcoin
   365  	bav.USDCentsPerBitcoin = txMeta.USDCentsPerBitcoin
   366  
   367  	// Save a UtxoOperation of type OperationTypeUpdateBitcoinUSDExchangeRate that will allow
   368  	// us to easily revert  when we disconnect the transaction.
   369  	utxoOpsForTxn = append(utxoOpsForTxn, &UtxoOperation{
   370  		Type:                   OperationTypeUpdateBitcoinUSDExchangeRate,
   371  		PrevUSDCentsPerBitcoin: prevUSDCentsPerBitcoin,
   372  	})
   373  
   374  	return totalInput, totalOutput, utxoOpsForTxn, nil
   375  }
   376  
   377  func (bav *UtxoView) _disconnectBitcoinExchange(
   378  	operationType OperationType, currentTxn *MsgDeSoTxn, txnHash *BlockHash,
   379  	utxoOpsForTxn []*UtxoOperation, blockHeight uint32) error {
   380  
   381  	// Check that the last operation has the required OperationType
   382  	operationIndex := len(utxoOpsForTxn) - 1
   383  	if len(utxoOpsForTxn) == 0 {
   384  		return fmt.Errorf("_disconnectBitcoinExchange: Trying to revert "+
   385  			"%v but utxoOperations are missing",
   386  			OperationTypeBitcoinExchange)
   387  	}
   388  	if utxoOpsForTxn[operationIndex].Type != OperationTypeBitcoinExchange {
   389  		return fmt.Errorf("_disconnectBitcoinExchange: Trying to revert "+
   390  			"%v but found type %v",
   391  			OperationTypeBitcoinExchange, utxoOpsForTxn[operationIndex].Type)
   392  	}
   393  	operationData := utxoOpsForTxn[operationIndex]
   394  
   395  	// Get the transaction metadata from the transaction now that we know it has
   396  	// OperationTypeBitcoinExchange.
   397  	txMeta := currentTxn.TxnMeta.(*BitcoinExchangeMetadata)
   398  
   399  	// Remove the BitcoinTransactionHash from our TxID mappings since we are
   400  	// unspending it. This makes it so that this hash can be processed again in
   401  	// the future in order to re-grant the public key the DeSo they are entitled
   402  	// to (though possibly more or less than the amount of DeSo they had before
   403  	// because they might execute at a different conversion price).
   404  	bitcoinTxHash := (BlockHash)(txMeta.BitcoinTransaction.TxHash())
   405  	bav._deleteBitcoinBurnTxIDMappings(&bitcoinTxHash)
   406  
   407  	// Un-add the UTXO taht was created as a result of this transaction. It should
   408  	// be the one at the end of our UTXO list at this point.
   409  	//
   410  	// The UtxoKey is simply the transaction hash with index zero.
   411  	utxoKey := UtxoKey{
   412  		TxID: *currentTxn.Hash(),
   413  		// We give all UTXOs that are created as a result of BitcoinExchange transactions
   414  		// an index of zero. There is generally only one UTXO created in a BitcoinExchange
   415  		// transaction so this field doesn't really matter.
   416  		Index: 0,
   417  	}
   418  	if err := bav._unAddUtxo(&utxoKey); err != nil {
   419  		return errors.Wrapf(err, "_disconnectBitcoinExchange: Problem unAdding utxo %v: ", utxoKey)
   420  	}
   421  
   422  	// Reset NanosPurchased to the value it was before granting this DeSo to this user.
   423  	// This previous value comes from the UtxoOperation data.
   424  	prevNanosPurchased := operationData.PrevNanosPurchased
   425  	bav.NanosPurchased = prevNanosPurchased
   426  
   427  	// At this point the BitcoinExchange transaction should be fully reverted.
   428  	return nil
   429  }
   430  
   431  func (bav *UtxoView) _disconnectUpdateBitcoinUSDExchangeRate(
   432  	operationType OperationType, currentTxn *MsgDeSoTxn, txnHash *BlockHash,
   433  	utxoOpsForTxn []*UtxoOperation, blockHeight uint32) error {
   434  
   435  	// Check that the last operation has the required OperationType
   436  	operationIndex := len(utxoOpsForTxn) - 1
   437  	if len(utxoOpsForTxn) == 0 {
   438  		return fmt.Errorf("_disconnectUpdateBitcoinUSDExchangeRate: Trying to revert "+
   439  			"%v but utxoOperations are missing",
   440  			OperationTypeUpdateBitcoinUSDExchangeRate)
   441  	}
   442  	if utxoOpsForTxn[operationIndex].Type != OperationTypeUpdateBitcoinUSDExchangeRate {
   443  		return fmt.Errorf("_disconnectUpdateBitcoinUSDExchangeRate: Trying to revert "+
   444  			"%v but found type %v",
   445  			OperationTypeUpdateBitcoinUSDExchangeRate, utxoOpsForTxn[operationIndex].Type)
   446  	}
   447  	operationData := utxoOpsForTxn[operationIndex]
   448  
   449  	// Get the transaction metadata from the transaction now that we know it has
   450  	// OperationTypeUpdateBitcoinUSDExchangeRate.
   451  	txMeta := currentTxn.TxnMeta.(*UpdateBitcoinUSDExchangeRateMetadataa)
   452  	_ = txMeta
   453  
   454  	// Reset exchange rate to the value it was before granting this DeSo to this user.
   455  	// This previous value comes from the UtxoOperation data.
   456  	prevUSDCentsPerBitcoin := operationData.PrevUSDCentsPerBitcoin
   457  	bav.USDCentsPerBitcoin = prevUSDCentsPerBitcoin
   458  
   459  	// Now revert the basic transfer with the remaining operations. Cut off
   460  	// the UpdateBitcoinUSDExchangeRate operation at the end since we just reverted it.
   461  	return bav._disconnectBasicTransfer(
   462  		currentTxn, txnHash, utxoOpsForTxn[:operationIndex], blockHeight)
   463  }