decred.org/dcrwallet/v3@v3.1.0/rpc/grpc_example/main.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"crypto/x509"
     7  	"encoding/hex"
     8  	"errors"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"os"
    12  	"path/filepath"
    13  
    14  	"google.golang.org/grpc/codes"
    15  	"google.golang.org/grpc/status"
    16  
    17  	pb "decred.org/dcrwallet/v3/rpc/walletrpc"
    18  	"google.golang.org/grpc"
    19  	"google.golang.org/grpc/credentials"
    20  	"google.golang.org/protobuf/encoding/prototext"
    21  
    22  	"github.com/decred/dcrd/dcrutil/v4"
    23  )
    24  
    25  var (
    26  	certificateFile      = filepath.Join(dcrutil.AppDataDir("dcrwallet", false), "rpc.cert")
    27  	walletClientCertFile = "client.pem" // must be part of ~/.dcrwallet/clients.pem
    28  	walletClientKeyFile  = "client-key.pem"
    29  )
    30  
    31  func main() {
    32  	if err := run(); err != nil {
    33  		fmt.Println(err)
    34  		os.Exit(1)
    35  	}
    36  	os.Exit(0)
    37  }
    38  
    39  func run() error {
    40  	ctx, cancel := context.WithCancel(context.Background())
    41  	defer cancel()
    42  	serverCAs := x509.NewCertPool()
    43  	serverCert, err := ioutil.ReadFile(certificateFile)
    44  	if err != nil {
    45  		return err
    46  	}
    47  	if !serverCAs.AppendCertsFromPEM(serverCert) {
    48  		return fmt.Errorf("no certificates found in %s\n", certificateFile)
    49  	}
    50  	keypair, err := tls.LoadX509KeyPair(walletClientCertFile, walletClientKeyFile)
    51  	if err != nil {
    52  		return err
    53  	}
    54  	creds := credentials.NewTLS(&tls.Config{
    55  		Certificates: []tls.Certificate{keypair},
    56  		RootCAs:      serverCAs,
    57  	})
    58  	conn, err := grpc.Dial("localhost:19111", grpc.WithTransportCredentials(creds))
    59  	if err != nil {
    60  		return err
    61  	}
    62  	defer conn.Close()
    63  	c := pb.NewWalletServiceClient(conn)
    64  
    65  	balanceRequest := &pb.BalanceRequest{
    66  		AccountNumber:         0,
    67  		RequiredConfirmations: 1,
    68  	}
    69  	balanceResponse, err := c.Balance(ctx, balanceRequest)
    70  	if err != nil {
    71  		return err
    72  	}
    73  
    74  	fmt.Println("Spendable balance: ", dcrutil.Amount(balanceResponse.Spendable))
    75  
    76  	decodedTx := func(tx string) error {
    77  		rawTx, _ := hex.DecodeString(tx)
    78  		dmClient := pb.NewDecodeMessageServiceClient(conn)
    79  		decodeRequest := &pb.DecodeRawTransactionRequest{
    80  			SerializedTransaction: rawTx,
    81  		}
    82  		decodeResponse, err := dmClient.DecodeRawTransaction(ctx, decodeRequest)
    83  		if err != nil {
    84  			return err
    85  		}
    86  
    87  		// tj, _ := json.MarshalIndent(decodeResponse.Transaction, "", "   ")
    88  		// fmt.Println(string(tj))
    89  		fmt.Println(prototext.MarshalOptions{Multiline: true}.Format(decodeResponse.Transaction))
    90  		return nil
    91  	}
    92  
    93  	for _, tx := range txns {
    94  		if err := decodedTx(tx); err != nil {
    95  			return err
    96  		}
    97  	}
    98  
    99  	wsClient := pb.NewWalletServiceClient(conn)
   100  
   101  	for _, script := range importScripts {
   102  		// NOTE: These scripts will forever be imported into your
   103  		// testing wallet.
   104  		scriptB, err := hex.DecodeString(script)
   105  		if err != nil {
   106  			return err
   107  		}
   108  		importScriptRequest := &pb.ImportScriptRequest{
   109  			Script: scriptB,
   110  		}
   111  		_, err = wsClient.ImportScript(ctx, importScriptRequest)
   112  		if err != nil {
   113  			return err
   114  		}
   115  	}
   116  
   117  	const acctName = "testing_acct"
   118  	var (
   119  		seed     [32]byte
   120  		acctPass = []byte("pass123")
   121  		acctN    uint32
   122  	)
   123  	seed[31] = 1
   124  
   125  	// Import a voting account from seed.
   126  	importVAFSRequest := &pb.ImportVotingAccountFromSeedRequest{
   127  		Seed:       seed[:],
   128  		Name:       acctName,
   129  		Passphrase: acctPass,
   130  	}
   131  	importVAFSReqResp, err := wsClient.ImportVotingAccountFromSeed(ctx, importVAFSRequest)
   132  	if err != nil {
   133  		if status.Code(err) != codes.AlreadyExists {
   134  			return err
   135  		}
   136  		acctsResp, err := wsClient.Accounts(ctx, &pb.AccountsRequest{})
   137  		if err != nil {
   138  			return err
   139  		}
   140  		var found bool
   141  		for _, acct := range acctsResp.Accounts {
   142  			if acct.AccountName == acctName {
   143  				found = true
   144  				acctN = acct.AccountNumber
   145  			}
   146  		}
   147  		if !found {
   148  			return errors.New("testing account not found")
   149  		}
   150  	} else {
   151  		acctN = importVAFSReqResp.Account
   152  	}
   153  
   154  	fmt.Println("Testing account number is", acctN)
   155  	fmt.Println()
   156  
   157  	// Add requests for addresses from the voting account which should fail.
   158  	nextAddrs = append(nextAddrs,
   159  		nextAddr{name: "imported voting account external", acct: acctN, branch: 0, wantErr: true},
   160  		nextAddr{name: "imported voting account internal", acct: acctN, branch: 1, wantErr: true})
   161  
   162  	for _, addr := range nextAddrs {
   163  		// Add an owned address to validated addresses.
   164  		nextAddrReq := &pb.NextAddressRequest{
   165  			Account:   addr.acct,
   166  			Kind:      addr.branch,
   167  			GapPolicy: pb.NextAddressRequest_GAP_POLICY_IGNORE,
   168  		}
   169  		nextAddressResp, err := wsClient.NextAddress(context.Background(), nextAddrReq)
   170  		if addr.wantErr {
   171  			if err != nil {
   172  				continue
   173  			}
   174  			return fmt.Errorf("nextAddrs: expected error for %s", addr.name)
   175  		}
   176  		if err != nil {
   177  			return err
   178  		}
   179  		validateAddrs = append(validateAddrs, nextAddressResp.Address)
   180  	}
   181  
   182  	fmt.Println("ValidateAddress...")
   183  	fmt.Println()
   184  
   185  	for _, addr := range validateAddrs {
   186  		validateAddrRequest := &pb.ValidateAddressRequest{
   187  			Address: addr,
   188  		}
   189  		validateAddrResp, err := wsClient.ValidateAddress(ctx, validateAddrRequest)
   190  		if err != nil {
   191  			return err
   192  		}
   193  		fmt.Println(validateAddrResp.ScriptType)
   194  		fmt.Println(prototext.MarshalOptions{Multiline: true}.Format(validateAddrResp))
   195  	}
   196  
   197  	fmt.Println("Address...")
   198  	fmt.Println()
   199  
   200  	for _, path := range addrPaths {
   201  		addrRequest := &pb.AddressRequest{
   202  			Account: path.acct,
   203  			Kind:    pb.AddressRequest_Kind(path.branch),
   204  			Index:   path.idx,
   205  		}
   206  		fmt.Printf("Name: %s\n", path.name)
   207  		addrResp, err := wsClient.Address(context.Background(), addrRequest)
   208  		if path.wantErr {
   209  			if err != nil {
   210  				fmt.Println(err)
   211  				continue
   212  			}
   213  			return fmt.Errorf("Address: expected error for %v", path.name)
   214  		}
   215  		if err != nil {
   216  			return err
   217  		}
   218  		fmt.Println(prototext.MarshalOptions{Multiline: true}.Format(addrResp))
   219  	}
   220  
   221  	_, err = wsClient.UnlockAccount(ctx, &pb.UnlockAccountRequest{Passphrase: acctPass, AccountNumber: acctN})
   222  	if err != nil {
   223  		return err
   224  	}
   225  
   226  	pKey, err := wsClient.DumpPrivateKey(ctx, &pb.DumpPrivateKeyRequest{Address: importedAcctAddr})
   227  	if err != nil {
   228  		return err
   229  	}
   230  
   231  	fmt.Println()
   232  	fmt.Println("Private key is", pKey.PrivateKeyWif)
   233  
   234  	return nil
   235  }
   236  
   237  var txns = []string{
   238  	// vote (stakegen)
   239  	"01000000020000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff24c5e81c429df60b4bfd33b44b9535a7874a9d06053a815e1ee9d0d1a121b2290000000001ffffffff0300000000000000000000266a248cfb198b12330d1c36ed45a84d41ab0e661793aa6560b6504f0b53670000000058720c0000000000000000000000086a06010009000000da72c2620200000000001abb76a91454e2d8ebbad00e0609c5c5e8e8890848b23126f388ac000000000000000002319f2b000000000000000000ffffffff020000a9d396620200000003720c00060000006b483045022100c7c78d30bda7dd1b95f675f26e16adb7d3a086958ad312d64b21005b6d10293102205d39dc2846452d941ab47bb0e3ccec82b493630d5fdbaa06edb15396e1c55e0a0121037bb6c86704276d6753e348f85f48c53fc47a4fe34fa3ce81d31356ae7e8f7643",
   240  	// 1 pubkeyhash and 1 pubkey output
   241  	"01000000016e341707db12587ec25d5ea5f8ed0014c52d529ca31f380bf1be5f1d4bcf4bdf0100000000ffffffff02afef35070000000000002321022ce8072be4a73c268d6330fa6455628c6ff54fb7ece550ebc0d8f0746a702291ac950e97160000000000001976a91425bccf8268059f1ef9f7767f05fa7ded8195674588ac0000000000000000010065cd1d0000000003ad0200030000006b483045022100fa94b17b70e748f5ef0436350dd82736f9f1ca1c45e2476d3ddf0ba9db402a1a022052349cca845b8bd1df9320078b115f3ce324e3892b37ca1cc6361b6df470fb8a0121021c80d0573a302b239ed2b4db8f9324e9411e26abdb47661092daf664ddc311bd",
   242  	// treasurebase with a tadd
   243  	"03000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff02fdb3480000000000000001c1000000000000000000000e6a0c56720c00093c28052bb4dd16000000000000000001fdb348000000000000000000ffffffff00",
   244  	// treasuryspend with a tgen
   245  	"03000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0200000000000000000000226a20f6eaf505000000000c9ef1c885c62d089ff9988eb4ca4c2c620614eb75c620bc00e1f5050000000000001ac376a914bd15503ed7d24fc5b36ceba70ee8741a36fe3dca88ac00000000f28e080001f6eaf5050000000000000000ffffffff644054a36eb67457facef5a075f8ebba5d7a9342dd13b45c6d3dd43b22bead35428603fd805eea93674aa7cd120e46e3fa7a2c563926686909c8eb1b06b40237f3a52103beca9bbd227ca6bb5a58e03a36ba2b52fff09093bd7a50aee1193bccd257fb8ac2",
   246  
   247  	// coinbase (1 nulldata and 1 pubkeyhash)
   248  	"03000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff02000000000000000000000e6a0c55720c00e88a9c9e5c49d15b8021b5010000000000001976a914d251921c9857791815e97750afcea48fbd30568a88ac000000000000000001f237b4010000000000000000ffffffff0800002f646372642f",
   249  	// legacy stakepool pool ticket (note that gRPC's DecodeRawTransaction calls the sstxcommitment/nulldata outputs stakesubmission instead)
   250  	"0100000002793d7447b9b4463cb069c684afadc757225b6d21cb395208cb8740244bf8598a0200000000ffffffff793d7447b9b4463cb069c684afadc757225b6d21cb395208cb8740244bf8598a0300000000ffffffff057aee520600000000000018baa914c8148420843952f6085ce051c66c66e538eed5cd8700000000000000000000206a1ec667be7328c77abff2f9f35d2aad91cc80a9c4fdc2880300000000000058000000000000000000001abd76a914000000000000000000000000000000000000000088ac00000000000000000000206a1e2b51ac564b85c04d874f1075960fc17d86e3777de47a4f06000000000058000000000000000000001abd76a914000000000000000000000000000000000000000088ac000000002011050002c2880300000000000c110500070000006a4730440220227d7b5cec71179918fc982a8b05bb5e4fe2267f8a7f584322cfdbd989943267022046a921072177590120d7a2365952d151ecc89dc627b65da43056f20646383002012103130967a676b0762272729278a78afbeb234b0a1831c51e857395ab565baee24ce47a4f06000000000c110500070000006a47304402205e5b12a9f11b5bd65e56ec7d21e306dedf79a717527567028121a326d5ccc02d02202f58a2e6ec9d29aa4a3bcd2b25e00aa1844f05abf3a3e2e55bad05f96b30c96e012103130967a676b0762272729278a78afbeb234b0a1831c51e857395ab565baee24c",
   251  	// solo ticket
   252  	"01000000016db59a8f8e1c9db0a963b08a30f02f1cea95ddd430303a8d9859ee730c01d8650000000000ffffffff03a9d396620200000000001aba76a914fd5e20128a2e5c4bd9abf93386912b627951e5c488ac00000000000000000000206a1e899eb23ed3b51cfe839f708678af833daa5f28794ddf966202000000004e000000000000000000001abd76a914000000000000000000000000000000000000000088ac0000000090720c00014ddf966202000000ffffff7fffffffff6b483045022100e04b03b2844ade2ab84847902dac3ccd6022bc23dc012a11390a59046c59186902202c0f6bddfa4b4c2494f40bfc8a7ec07ab570c4be6d66fa10b48555deb66148d101210248be5d2501e3a9ed3079138a48e943c12e78da56ed752caf590c510f1ce3c929",
   253  }
   254  
   255  var importScripts = []string{
   256  	// TSPEND
   257  	"c376a914d17043c104a57393aa7353e1510e39eab811e3db88ac",
   258  	// 2 of 2 multisig
   259  	"522103d484eb60ad03549e731ae9045281f8ee14ff6ea11b697f32cde3d8a18992261b210342b0b9c0ecb53cb9761beb0d010bbf08b5049d2a4d3bea5d3a1d95eb664931cb52ae",
   260  	// NonStandard
   261  	"01",
   262  }
   263  
   264  var importedAcctAddr = "TsSAzyUaa2KSytEuu1hiGdeYJqu4om63ZQb" // Address at imported account/0/0
   265  
   266  var validateAddrs = []string{
   267  	"TcpEWwGdCN3RCNAQUhBn8f2Xdko2JzcQSQs",
   268  
   269  	"TkKkYvSrnu8orwhtedcJGkD7guarvZUbUAtjr4iKqD9Y8pNEf8iHu", // PubKeyEcdsaSecp256k1V0
   270  	"Tsp18L8qTcjzigYXrD5GSdwDmhVYBpKmfUL",                   // PubKeyHashEcdsaSecp256k1V0
   271  	"TkKnVfd6EvzEYAqiELWstkASHgVyYH8JK3gNvAxUX79C9CrnsV8W6", // PubKeyEd25519V0
   272  	"TedZCnJ5uQ8z7VzKqdBhP1WP2RBYaaoCiUe",                   // PubKeyHashEd25519V0
   273  	"TkKpSQoKgxqfDPyXp3RTWk7ktTR69zn19vU1zHCdD18r9bMTvDKT3", // PubKeySchnorrSecp256k1V0
   274  	"TSs3jHQMbbZGPyftUh4cgaALzgDZhfXGtxn",                   // PubKeyHashSchnorrSecp256k1V0
   275  	"TcvVou7ooM4rJRWNeJYwehJ9fQq1HTc5pbK",                   // ScriptHashV0
   276  
   277  	// These are owned imported scripts.
   278  	"TcfdqCrK2fiFJBZnGj5N6xs6rMsbQBsJBYf", // TSPEND
   279  	"TcrzaAVMbFURm1PpukWru8yE2uBTjvQePoa", // 2 of 2 multisig
   280  	"TckSpBht36nMZgnDDjv7xaHUrgCyJpxQiLA", // NonStandard
   281  
   282  	importedAcctAddr,
   283  }
   284  
   285  type nextAddr struct {
   286  	name    string
   287  	acct    uint32
   288  	branch  pb.NextAddressRequest_Kind
   289  	wantErr bool
   290  }
   291  
   292  var nextAddrs = []nextAddr{{
   293  	name: "default external",
   294  }}
   295  
   296  var addrPaths = []struct {
   297  	name              string
   298  	acct, branch, idx uint32
   299  	wantErr           bool
   300  }{{
   301  	name: "all zeros",
   302  }, {
   303  	name:   "internal branch",
   304  	branch: 1,
   305  }, {
   306  	name:    "bad branch",
   307  	branch:  2,
   308  	wantErr: true,
   309  }}