decred.org/dcrdex@v1.0.5/client/asset/btc/electrum_client_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  	"encoding/hex"
    12  	"sync"
    13  	"testing"
    14  	"time"
    15  
    16  	"decred.org/dcrdex/client/asset/btc/electrum"
    17  	"decred.org/dcrdex/dex"
    18  
    19  	"github.com/btcsuite/btcd/chaincfg"
    20  	"github.com/btcsuite/btcd/chaincfg/chainhash"
    21  	"github.com/btcsuite/btcd/txscript"
    22  )
    23  
    24  func Test_electrumWallet(t *testing.T) {
    25  	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
    26  	defer cancel()
    27  
    28  	// This test uses a *testnet* BTC wallet listening on localhost port 6789.
    29  	// Testnet is used because it tests interaction with actual remote servers
    30  	// (and the bitcoin network) and there are historical transactions that can
    31  	// be inspected in public block explorers.
    32  	const walletPass = "walletpass" // set me
    33  	ewc := electrum.NewWalletClient("user", "pass", "http://127.0.0.1:5678", "~/.electrum/testnet/wallets/default_wallet")
    34  	ew := newElectrumWallet(ewc, &electrumWalletConfig{
    35  		params: &chaincfg.TestNet3Params,
    36  		log:    dex.StdOutLogger("ELECTRUM-TEST", dex.LevelTrace),
    37  		segwit: true,
    38  	})
    39  	var wg sync.WaitGroup
    40  	err := ew.Connect(ctx, &wg)
    41  	if err != nil {
    42  		t.Fatal(err)
    43  	}
    44  
    45  	medianTime, err := ew.calcMedianTime(ctx, 2286089) // ltc testnet
    46  	if err != nil {
    47  		t.Fatal(err)
    48  	}
    49  	t.Log(medianTime.UTC()) // 2022-07-15 13:09:39 +0000 UTC
    50  
    51  	// Output:
    52  	// https://tbtc.bitaps.com/b9890847952be7ca8918368cbfa88fea0efb71ffe9549de0c4ae6e23c2b02e4c/output/0
    53  	// Spending input:
    54  	// https://tbtc.bitaps.com/9dbed377dbbee2ac98dcf5edfc504e04f16afa73b07afc81bc0711b7625f66d1/input/0
    55  
    56  	// findOutputSpender
    57  	fundHash, _ := chainhash.NewHashFromStr("b9890847952be7ca8918368cbfa88fea0efb71ffe9549de0c4ae6e23c2b02e4c")
    58  	fundVout := uint32(0)
    59  	fundVal := int64(1922946974)
    60  	fundPkScript, _ := hex.DecodeString("00145bb911c50e0f79e0212aae1e0503ee8192038b57")
    61  	fundAddr := "tb1qtwu3r3gwpau7qgf24c0q2qlwsxfq8z6hl6efm4"
    62  	spendMsgTx, spendVin, err := ew.findOutputSpender(ctx, fundHash, fundVout)
    63  	if err != nil {
    64  		t.Fatal(err)
    65  	}
    66  	spendHash := spendMsgTx.TxHash()
    67  	wantSpendTxID := "9dbed377dbbee2ac98dcf5edfc504e04f16afa73b07afc81bc0711b7625f66d1"
    68  	if spendHash.String() != wantSpendTxID {
    69  		t.Errorf("Incorrect spending tx hash %v, want %v", spendHash, wantSpendTxID)
    70  	}
    71  	wantSpendVin := uint32(0)
    72  	if spendVin != wantSpendVin {
    73  		t.Errorf("Incorrect spending tx input index %d, want %d", spendVin, wantSpendVin)
    74  	}
    75  
    76  	// gettxout - first the spent output
    77  	fundTxOut, fundConfs, err := ew.GetTxOut(fundHash, fundVout, nil, time.Time{})
    78  	if err != nil {
    79  		t.Fatal(err)
    80  	}
    81  	if fundTxOut != nil {
    82  		t.Errorf("expected a nil TxOut for a spent output, but got one")
    83  	}
    84  
    85  	// gettxout - next an old unspent output that's likely to stay unspent
    86  	fundHash, _ = chainhash.NewHashFromStr("34fd12be115549ec482fdb20bc5196e0107cfcda32c252ffea9e8116ee6fcf8e")
    87  	fundVout = 0
    88  	fundVal = 20000
    89  	fundPkScript, _ = hex.DecodeString("76a914b3e0f80ce29ac48793de504ae9aa0b6579dda29a88ac")
    90  	fundAddr = "mwv4kWRXkc2w42823FU9SJ16cvZH8Aobke"
    91  	fundTxOut, fundConfs, err = ew.GetTxOut(fundHash, fundVout, nil, time.Time{})
    92  	if err != nil {
    93  		t.Fatal(err)
    94  	}
    95  	t.Logf("%v confs = %d", fundHash, fundConfs)
    96  	if fundTxOut.Value != fundVal {
    97  		t.Errorf("wrong output value %d, wanted %d", fundTxOut.Value, fundVal)
    98  	}
    99  	if !bytes.Equal(fundTxOut.PkScript, fundPkScript) {
   100  		t.Errorf("wanted pkScript %x, got %x", fundPkScript, fundTxOut.PkScript)
   101  	}
   102  	scriptClass, addrs, reqSigs, err := txscript.ExtractPkScriptAddrs(fundTxOut.PkScript, ew.chainParams)
   103  	if err != nil {
   104  		t.Fatal(err)
   105  	}
   106  	if scriptClass != txscript.PubKeyHashTy {
   107  		t.Errorf("got script class %v, wanted %v", scriptClass, txscript.PubKeyHashTy)
   108  	}
   109  	if len(addrs) != 1 {
   110  		t.Fatalf("got %d addresses, wanted 1", len(addrs))
   111  	}
   112  	if addrs[0].String() != fundAddr {
   113  		t.Errorf("got address %v, wanted %v", addrs[0], fundAddr)
   114  	}
   115  	if reqSigs != 1 {
   116  		t.Fatalf("requires %d sigs, expected 1", reqSigs)
   117  	}
   118  
   119  	addr, err := ew.wallet.GetUnusedAddress(ctx) // or ew.externalAddress(), but let's not blow up the gap limit testing
   120  	if err != nil {
   121  		t.Fatal(err)
   122  	}
   123  	t.Log(addr)
   124  
   125  	err = ew.WalletUnlock([]byte(walletPass))
   126  	if err != nil {
   127  		t.Fatal(err)
   128  	}
   129  
   130  	_, err = ew.PrivKeyForAddress(addr)
   131  	if err != nil {
   132  		t.Fatal(err)
   133  	}
   134  
   135  	// Try the following with your own addresses!
   136  	// sent1, err := ew.sendToAddress("tltc1qn7dsn7glncdgf078pauerlqum6ma8peyvfa7gz", toSatoshi(0.2345), 4, false)
   137  	// if err != nil {
   138  	// 	t.Fatal(err)
   139  	// }
   140  	// t.Log(sent1)
   141  	// sent2, err := ew.sendToAddress("tltc1qpth226vjw2vp3mk8fvjfnrc4ygy8vtnx8p2q78", toSatoshi(0.2345), 5, true)
   142  	// if err != nil {
   143  	// 	t.Fatal(err)
   144  	// }
   145  	// t.Log(sent2)
   146  
   147  	// addr, err := ew.externalAddress()
   148  	// if err != nil {
   149  	// 	t.Fatal(err)
   150  	// }
   151  	// sweepTx, err := ew.sweep(addr.EncodeAddress(), 6)
   152  	// if err != nil {
   153  	// 	t.Fatal(err)
   154  	// }
   155  	// t.Log(sweepTx)
   156  
   157  	// Hang out until the context times out. Maybe see a new block. Think about
   158  	// other tests...
   159  	wg.Wait()
   160  }