decred.org/dcrdex@v1.0.5/client/asset/btc/electrum_test.go (about)

     1  // This code is available on the terms of the project LICENSE.md file,
     2  // also available online at https://blueoakcouncil.org/license/1.0.0.
     3  
     4  //go:build electrumlive
     5  
     6  package btc
     7  
     8  import (
     9  	"bytes"
    10  	"context"
    11  	"crypto/sha256"
    12  	"encoding/hex"
    13  	"fmt"
    14  	"testing"
    15  	"time"
    16  
    17  	"decred.org/dcrdex/client/asset"
    18  	"github.com/btcsuite/btcd/btcutil/psbt"
    19  	"github.com/btcsuite/btcd/chaincfg"
    20  	"github.com/btcsuite/btcd/chaincfg/chainhash"
    21  	"github.com/btcsuite/btcd/wire"
    22  )
    23  
    24  const walletPass = "walletpass"
    25  
    26  func TestPSBT(t *testing.T) {
    27  	// an unsigned swap txn funded by a single p2wpkh
    28  	txRaw, _ := hex.DecodeString("010000000118f36e05994f8e69dace16f4a3d4c9ff8db6f58bf48d498b1c0d6daf63c6f8690100000000ffffffff02408e2c0000000000220020e0133024bb27f510f6959467df91ed5daa791f598ba1c09ef1f1ac4540bfdda16e7710000000000016001460a8dbedd538501e2ad9e9f5a4d8a11e35a6c4e700000000")
    29  	msgTx := wire.NewMsgTx(wire.TxVersion)
    30  	err := msgTx.Deserialize(bytes.NewReader(txRaw))
    31  	if err != nil {
    32  		t.Fatal(err)
    33  	}
    34  
    35  	packet, err := psbt.NewFromUnsignedTx(msgTx)
    36  	if err != nil {
    37  		t.Fatal(err)
    38  	}
    39  	enc, err := packet.B64Encode()
    40  	if err != nil {
    41  		t.Fatal(err)
    42  	}
    43  	fmt.Println(enc)
    44  }
    45  
    46  // This test uses a testnet BTC Electrum wallet listening on localhost 6789.
    47  
    48  func TestElectrumExchangeWallet(t *testing.T) {
    49  	ctx, cancel := context.WithCancel(context.Background())
    50  	defer cancel()
    51  
    52  	notes := make(chan asset.WalletNotification)
    53  	walletCfg := &asset.WalletConfig{
    54  		Type: walletTypeElectrum,
    55  		Settings: map[string]string{
    56  			"rpcuser":     "user",
    57  			"rpcpassword": "pass",
    58  			"rpcbind":     "127.0.0.1:6789",
    59  		},
    60  		Emit: asset.NewWalletEmitter(notes, BipID, tLogger),
    61  		PeersChange: func(num uint32, err error) {
    62  			t.Logf("peer count = %d, err = %v", num, err)
    63  		},
    64  	}
    65  	cfg := &BTCCloneCFG{
    66  		WalletCFG:           walletCfg,
    67  		Symbol:              "btc",
    68  		Logger:              tLogger,
    69  		ChainParams:         &chaincfg.TestNet3Params,
    70  		WalletInfo:          WalletInfo,
    71  		DefaultFallbackFee:  defaultFee,
    72  		DefaultFeeRateLimit: defaultFeeRateLimit,
    73  		Segwit:              true,
    74  		// ElectrumWallet constructor overrides btc.localFeeRate = eew.walletFeeRate
    75  	}
    76  	eew, err := ElectrumWallet(cfg)
    77  	if err != nil {
    78  		t.Fatal(err)
    79  	}
    80  
    81  	wg, err := eew.Connect(ctx)
    82  	if err != nil {
    83  		t.Fatal(err)
    84  	}
    85  
    86  	addr, err := eew.DepositAddress()
    87  	if err != nil {
    88  		t.Fatal(err)
    89  	}
    90  	t.Log(addr)
    91  
    92  	feeRate := eew.FeeRate()
    93  	if feeRate == 0 {
    94  		t.Fatal("zero fee rate")
    95  	}
    96  	t.Log("feeRate:", feeRate)
    97  
    98  	// findRedemption
    99  	swapTxHash, _ := chainhash.NewHashFromStr("1e99930f76638e3eddd79de94bf0ff574c7a400d1e6986cd61b3b5fd8212b1a3")
   100  	swapVout := uint32(0)
   101  	redeemTxHash, _ := chainhash.NewHashFromStr("4283c1fefa4898eb1bd2041547cd6361f8167dec004890b58ba1817914dc8541")
   102  	redeemVin := uint32(0)
   103  	// P2WSH: tb1q9vqw464klshj80vklss0ms4h82082y8q86x8cen3934r7zrvt6vs4e3m8u / 00202b00eaeab6fc2f23bd96fc20fdc2b73a9e7510e03e8c7c66712c6a3f086c5e99
   104  	// contract: 6382012088a820135a45665765d68dc255ecd5f1870a1b29b8f1fd95c1e02ca5151e354d2a2cf68876a9144fdb2cad8e98983b25675d3ebe2133e650c56dbf6704ecb0d262b17576a9144fdb2cad8e98983b25675d3ebe2133e650c56dbf6888ac
   105  	contract, _ := hex.DecodeString("6382012088a820135a45665765d68dc255ecd5f1870a1b29b8f1fd95c1e02ca5151e354d2a2cf68876a9144fdb2cad8e98983b25675d3ebe2133e650c56dbf6704ecb0d262b17576a9144fdb2cad8e98983b25675d3ebe2133e650c56dbf6888ac")
   106  	// contractHash, _ := hex.DecodeString("2b00eaeab6fc2f23bd96fc20fdc2b73a9e7510e03e8c7c66712c6a3f086c5e99")
   107  	contractHash := sha256.Sum256(contract)
   108  	wantSecret, _ := hex.DecodeString("aa8e04bb335da65d362b89ec0630dc76fd02ffaca783ae58cb712a2820f504ce")
   109  	foundTxHash, foundVin, secret, err := eew.findRedemption(ctx, NewOutPoint(swapTxHash, swapVout), contractHash[:])
   110  	if err != nil {
   111  		t.Fatal(err)
   112  	}
   113  	if !bytes.Equal(secret, wantSecret) {
   114  		t.Errorf("incorrect secret %x, wanted %x", secret, wantSecret)
   115  	}
   116  	if !foundTxHash.IsEqual(redeemTxHash) {
   117  		t.Errorf("incorrect redeem tx hash %v, wanted %v", foundTxHash, redeemTxHash)
   118  	}
   119  	if redeemVin != foundVin {
   120  		t.Errorf("incorrect redeem tx input %d, wanted %d", foundVin, redeemVin)
   121  	}
   122  
   123  	// FindRedemption
   124  	redeemCoin, secretBytes, err := eew.FindRedemption(ctx, ToCoinID(swapTxHash, swapVout), contract)
   125  	if err != nil {
   126  		t.Fatal(err)
   127  	}
   128  	foundTxHash, foundVin, err = decodeCoinID(redeemCoin)
   129  	if err != nil {
   130  		t.Fatal(err)
   131  	}
   132  	if !foundTxHash.IsEqual(redeemTxHash) {
   133  		t.Errorf("incorrect redeem tx hash %v, wanted %v", foundTxHash, redeemTxHash)
   134  	}
   135  	if redeemVin != foundVin {
   136  		t.Errorf("incorrect redeem tx input %d, wanted %d", foundVin, redeemVin)
   137  	}
   138  	if !secretBytes.Equal(wantSecret) {
   139  		t.Errorf("incorrect secret %v, wanted %x", secretBytes, wantSecret)
   140  	}
   141  
   142  	t.Logf("Found redemption of contract %v:%d at %v:%d!", swapTxHash, swapVout, foundTxHash, foundVin)
   143  
   144  	// err = eew.Unlock([]byte(walletPass))
   145  	// if err != nil {
   146  	// 	t.Fatal(err)
   147  	// }
   148  
   149  	// wdCoin, err := eew.Withdraw(addr, toSatoshi(1.2435), feeRate)
   150  	// if err != nil {
   151  	// 	t.Fatal(err)
   152  	// }
   153  	// t.Log(wdCoin.String())
   154  
   155  	select {
   156  	case <-time.After(10 * time.Second): // a bit of best block polling
   157  		cancel()
   158  	case ni := <-notes:
   159  		if _, is := ni.(*asset.TipChangeNote); is {
   160  			cancel()
   161  		}
   162  
   163  	case <-ctx.Done(): // or until TipChange cancels the context
   164  	}
   165  
   166  	wg.Wait()
   167  }