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 }}