decred.org/dcrwallet/v3@v3.1.0/rpc/documentation/clientusage.md (about) 1 # Client usage 2 3 Clients use RPC to interact with the wallet. A client may be implemented in any 4 language directly supported by [gRPC](https://www.grpc.io/), languages capable of 5 performing [FFI](https://en.wikipedia.org/wiki/Foreign_function_interface) with 6 these, and languages that share a common runtime (e.g. Scala, Kotlin, and Ceylon 7 for the JVM, F# for the CLR, etc.). Exact instructions differ slightly 8 depending on the language being used, but the general process is the same for 9 each. In short summary, to call RPC server methods, a client must: 10 11 1. Generate client bindings specific for the [wallet RPC server API](./api.md) 12 2. Import or include the gRPC dependency 13 3. (Optional) Wrap the client bindings with application-specific types 14 4. Open a gRPC channel using the wallet server's self-signed TLS certificate 15 16 The only exception to these steps is if the client is being written in Go. In 17 that case, the first step may be omitted by importing the bindings from 18 dcrwallet itself. 19 20 The rest of this document provides short examples of how to quickly get started 21 by implementing a basic client that fetches the balance of the default account 22 (account 0) from a testnet wallet listening on `localhost:19111` in several 23 different languages: 24 25 - [Go](#go) 26 - [C++](#cpp) 27 - [C#](#csharp) 28 - [Node.js](#nodejs) 29 - [Python](#python) 30 31 Unless otherwise stated under the language example, it is assumed that 32 gRPC is already already installed. The gRPC installation procedure 33 can vary greatly depending on the operating system being used and 34 whether a gRPC source install is required. Follow the [gRPC install 35 instructions](https://github.com/grpc/grpc/blob/master/INSTALL) if 36 gRPC is not already installed. A full gRPC install also includes 37 [Protocol Buffers](https://github.com/google/protobuf) (compiled with 38 support for the proto3 language version), which contains the protoc 39 tool and language plugins used to compile this project's `.proto` 40 files to language-specific bindings. 41 42 ## Go 43 44 The native gRPC library (gRPC Core) is not required for Go clients (a 45 pure Go implementation is used instead) and no additional setup is 46 required to generate Go bindings. 47 48 ```Go 49 package main 50 51 import ( 52 "context" 53 "crypto/tls" 54 "crypto/x509" 55 "fmt" 56 "io/ioutil" 57 "path/filepath" 58 59 pb "decred.org/dcrwallet/v3/rpc/walletrpc" 60 "google.golang.org/grpc" 61 "google.golang.org/grpc/credentials" 62 63 "github.com/decred/dcrd/dcrutil/v4" 64 ) 65 66 var ( 67 certificateFile = filepath.Join(dcrutil.AppDataDir("dcrwallet", false), "rpc.cert") 68 walletClientCertFile = "client.pem" // must be part of ~/.dcrwallet/clients.pem 69 walletClientKeyFile = "client-key.pem" 70 ) 71 72 func main() { 73 serverCAs := x509.NewCertPool() 74 serverCert, err := ioutil.ReadFile(certificateFile) 75 if err != nil { 76 fmt.Println(err) 77 return 78 } 79 if !serverCAs.AppendCertsFromPEM(serverCert) { 80 fmt.Printf("no certificates found in %s\n", certificateFile) 81 return 82 } 83 keypair, err := tls.LoadX509KeyPair(walletClientCertFile, walletClientKeyFile) 84 if err != nil { 85 fmt.Println(err) 86 return 87 } 88 creds := credentials.NewTLS(&tls.Config{ 89 Certificates: []tls.Certificate{keypair}, 90 RootCAs: serverCAs, 91 }) 92 conn, err := grpc.Dial("localhost:19111", grpc.WithTransportCredentials(creds)) 93 if err != nil { 94 fmt.Println(err) 95 return 96 } 97 defer conn.Close() 98 c := pb.NewWalletServiceClient(conn) 99 100 balanceRequest := &pb.BalanceRequest{ 101 AccountNumber: 0, 102 RequiredConfirmations: 1, 103 } 104 balanceResponse, err := c.Balance(context.Background(), balanceRequest) 105 if err != nil { 106 fmt.Println(err) 107 return 108 } 109 110 fmt.Println("Spendable balance: ", dcrutil.Amount(balanceResponse.Spendable)) 111 } 112 ``` 113 114 <a name="cpp"/> 115 ## C++ 116 117 **Note:** Protocol Buffers and gRPC require at least C++11. The example client 118 is written using C++14. 119 120 **Note:** The following instructions assume the client is being written on a 121 Unix-like platform (with instructions using the `sh` shell and Unix-isms in the 122 example source code) with a source gRPC install in `/usr/local`. 123 124 First, generate the C++ language bindings by compiling the `.proto`: 125 126 ```bash 127 $ protoc -I/path/to/dcrwallet/rpc --cpp_out=. --grpc_out=. \ 128 --plugin=protoc-gen-grpc=$(which grpc_cpp_plugin) \ 129 /path/to/dcrwallet/rpc/api.proto 130 ``` 131 132 Once the `.proto` file has been compiled, the example client can be completed. 133 Note that the following code uses synchronous calls which will block the main 134 thread on all gRPC IO. 135 136 ```C++ 137 // example.cc 138 #include <unistd.h> 139 #include <sys/types.h> 140 #include <pwd.h> 141 142 #include <fstream> 143 #include <iostream> 144 #include <sstream> 145 #include <string> 146 147 #include <grpc++/grpc++.h> 148 149 #include "api.grpc.pb.h" 150 151 using namespace std::string_literals; 152 153 struct NoHomeDirectoryException : std::exception { 154 char const* what() const noexcept override { 155 return "Failed to lookup home directory"; 156 } 157 }; 158 159 auto read_file(std::string const& file_path) -> std::string { 160 std::ifstream in{file_path}; 161 std::stringstream ss{}; 162 ss << in.rdbuf(); 163 return ss.str(); 164 } 165 166 auto main() -> int { 167 // Before the gRPC native library (gRPC Core) is lazily loaded and 168 // initialized, an environment variable must be set so BoringSSL is 169 // configured to use ECDSA TLS certificates (required by dcrwallet). 170 setenv("GRPC_SSL_CIPHER_SUITES", "HIGH+ECDSA", 1); 171 172 // Note: This path is operating system-dependent. This can be created 173 // portably using boost::filesystem or the experimental filesystem class 174 // expected to ship in C++17. 175 auto wallet_tls_cert_file = []{ 176 auto pw = getpwuid(getuid()); 177 if (pw == nullptr || pw->pw_dir == nullptr) { 178 throw NoHomeDirectoryException{}; 179 } 180 return pw->pw_dir + "/.dcrwallet/rpc.cert"s; 181 }(); 182 183 grpc::SslCredentialsOptions cred_options{ 184 .pem_root_certs = read_file(wallet_tls_cert_file), 185 }; 186 auto creds = grpc::SslCredentials(cred_options); 187 auto channel = grpc::CreateChannel("localhost:19111", creds); 188 auto stub = walletrpc::WalletService::NewStub(channel); 189 190 grpc::ClientContext context{}; 191 192 walletrpc::BalanceRequest request{}; 193 request.set_account_number(0); 194 request.set_required_confirmations(1); 195 196 walletrpc::BalanceResponse response{}; 197 auto status = stub->Balance(&context, request, &response); 198 if (!status.ok()) { 199 std::cout << status.error_message() << std::endl; 200 } else { 201 std::cout << "Spendable balance: " << response.spendable() << " atoms" << std::endl; 202 } 203 } 204 ``` 205 206 The example can then be built with the following commands: 207 208 ```bash 209 $ c++ -std=c++14 -I/usr/local/include -pthread -c -o api.pb.o api.pb.cc 210 $ c++ -std=c++14 -I/usr/local/include -pthread -c -o api.grpc.pb.o api.grpc.pb.cc 211 $ c++ -std=c++14 -I/usr/local/include -pthread -c -o example.o example.cc 212 $ c++ *.o -L/usr/local/lib -lgrpc++ -lgrpc -lgpr -lprotobuf -lpthread -ldl -o example 213 ``` 214 215 <a name="csharp"/> 216 ## C# 217 218 The quickest way of generating client bindings in a Windows .NET environment is 219 by using the protoc binary included in the gRPC NuGet package. From the NuGet 220 package manager PowerShell console, this can be performed with: 221 222 ``` 223 PM> Install-Package Grpc 224 ``` 225 226 The protoc and C# plugin binaries can then be found in the packages directory. 227 For example, `.\packages\Google.Protobuf.x.x.x\tools\protoc.exe` and 228 `.\packages\Grpc.Tools.x.x.x\tools\grpc_csharp_plugin.exe`. 229 230 When writing a client on other platforms (e.g. Mono on OS X), or when doing a 231 full gRPC source install on Windows, protoc and the C# plugin must be installed 232 by other means. Consult the [official documentation](https://github.com/grpc/grpc/blob/master/src/csharp/README.md) 233 for these steps. 234 235 Once protoc and the C# plugin have been obtained, client bindings can be 236 generated. The following command generates the files `Api.cs` and `ApiGrpc.cs` 237 in the `Example` project directory using the `Walletrpc` namespace: 238 239 ```PowerShell 240 PS> & protoc.exe -I \Path\To\dcrwallet\rpc --csharp_out=Example --grpc_out=Example ` 241 --plugin=protoc-gen-grpc=\Path\To\grpc_csharp_plugin.exe ` 242 \Path\To\dcrwallet\rpc\api.proto 243 ``` 244 245 Once references have been added to the project for the `Google.Protobuf` and 246 `Grpc.Core` assemblies, the example client can be implemented. 247 248 ```C# 249 using Grpc.Core; 250 using System; 251 using System.IO; 252 using System.Text; 253 using System.Threading.Tasks; 254 using Walletrpc; 255 256 namespace Example 257 { 258 static class Program 259 { 260 static void Main(string[] args) 261 { 262 ExampleAsync().Wait(); 263 } 264 265 static async Task ExampleAsync() 266 { 267 // Before the gRPC native library (gRPC Core) is lazily loaded and initialized, 268 // an environment variable must be set so BoringSSL is configured to use ECDSA TLS 269 // certificates (required by dcrwallet). 270 Environment.SetEnvironmentVariable("GRPC_SSL_CIPHER_SUITES", "HIGH+ECDSA"); 271 272 var walletAppData = Portability.LocalAppData(Environment.OSVersion.Platform, "Dcrwallet"); 273 var walletTlsCertFile = Path.Combine(walletAppData, "rpc.cert"); 274 var cert = await FileUtils.ReadFileAsync(walletTlsCertFile); 275 var channel = new Channel("localhost:19111", new SslCredentials(cert)); 276 try 277 { 278 var c = WalletService.NewClient(channel); 279 var balanceRequest = new BalanceRequest 280 { 281 AccountNumber = 0, 282 RequiredConfirmations = 1, 283 }; 284 var balanceResponse = await c.BalanceAsync(balanceRequest); 285 Console.WriteLine($"Spendable balance: {balanceResponse.Spendable} atoms"); 286 } 287 finally 288 { 289 await channel.ShutdownAsync(); 290 } 291 } 292 } 293 294 static class FileUtils 295 { 296 public static async Task<string> ReadFileAsync(string filePath) 297 { 298 using (var r = new StreamReader(filePath, Encoding.UTF8)) 299 { 300 return await r.ReadToEndAsync(); 301 } 302 } 303 } 304 305 static class Portability 306 { 307 public static string LocalAppData(PlatformID platform, string processName) 308 { 309 if (processName == null) 310 throw new ArgumentNullException(nameof(processName)); 311 if (processName.Length == 0) 312 throw new ArgumentException(nameof(processName) + " may not have zero length"); 313 314 switch (platform) 315 { 316 case PlatformID.Win32NT: 317 return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), 318 ToUpper(processName)); 319 case PlatformID.MacOSX: 320 return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), 321 "Library", "Application Support", ToUpper(processName)); 322 case PlatformID.Unix: 323 return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), 324 ToDotLower(processName)); 325 default: 326 throw new PlatformNotSupportedException($"PlatformID={platform}"); 327 } 328 } 329 330 private static string ToUpper(string value) 331 { 332 var firstChar = value[0]; 333 if (char.IsUpper(firstChar)) 334 return value; 335 else 336 return char.ToUpper(firstChar) + value.Substring(1); 337 } 338 339 private static string ToDotLower(string value) 340 { 341 var firstChar = value[0]; 342 return "." + char.ToLower(firstChar) + value.Substring(1); 343 } 344 } 345 } 346 ``` 347 348 ## Node.js 349 350 First, install the gRPC `npm` package, which includes a pre-compiled gRPC Core 351 native library and the bindings for Node.js clients: 352 353 ``` 354 npm install grpc 355 ``` 356 357 A Node.js client does not require generating JavaScript stub files for 358 the wallet's API from the `.proto`. Instead, a call to `grpc.load` 359 with the `.proto` file path dynamically loads the Protobuf descriptor 360 and generates bindings for each service. Either copy the `.proto` to 361 the client project directory, or reference the file from the 362 `dcrwallet` project directory. 363 364 ```JavaScript 365 // Before the gRPC native library (gRPC Core) is lazily loaded and 366 // initialized, an environment variable must be set so BoringSSL is 367 // configured to use ECDSA TLS certificates (required by dcrwallet). 368 process.env['GRPC_SSL_CIPHER_SUITES'] = 'HIGH+ECDSA'; 369 370 var fs = require('fs'); 371 var path = require('path'); 372 var os = require('os'); 373 var grpc = require('grpc'); 374 var protoDescriptor = grpc.load('./api.proto'); 375 var walletrpc = protoDescriptor.walletrpc; 376 377 var certPath = ''; 378 if (os.platform() == 'win32') { 379 certPath = path.join(process.env.LOCALAPPDATA, 'Dcrwallet', 'rpc.cert'); 380 } else if (os.platform() == 'darwin') { 381 certPath = path.join(process.env.HOME, 'Library', 'Application Support', 382 'Dcrwallet', 'rpc.cert'); 383 } else { 384 certPath = path.join(process.env.HOME, '.dcrwallet', 'rpc.cert'); 385 } 386 387 var cert = fs.readFileSync(certPath); 388 var creds = grpc.credentials.createSsl(cert); 389 var client = new walletrpc.WalletService('localhost:19111', creds); 390 391 var request = { 392 account_number: 0, 393 required_confirmations: 1 394 }; 395 client.balance(request, function(err, response) { 396 if (err) { 397 console.error(err); 398 } else { 399 console.log('Spendable balance:', response.spendable, 'atoms'); 400 } 401 }); 402 ``` 403 404 ## Python 405 406 **Note:** gRPC requires Python 2.7. 407 408 First, install the gRPC `pip` package, which includes a pre-compiled gRPC Core 409 native library and the bindings for Python clients: 410 411 ``` 412 pip install grpcio 413 ``` 414 415 Generate Python stubs from the `.proto`: 416 417 ```bash 418 $ protoc -I /path/to/decred/dcrwallet/rpc --python_out=. --grpc_out=. \ 419 --plugin=protoc-gen-grpc=$(which grpc_python_plugin) \ 420 /path/to/dcrwallet/rpc/api.proto 421 ``` 422 423 Implement the client: 424 425 ```Python 426 import os 427 import platform 428 from grpc.beta import implementations 429 430 import api_pb2 as walletrpc 431 432 timeout = 1 # seconds 433 434 def main(): 435 # Before the gRPC native library (gRPC Core) is lazily loaded and 436 # initialized, an environment variable must be set so BoringSSL is 437 # configured to use ECDSA TLS certificates (required by dcrwallet). 438 os.environ['GRPC_SSL_CIPHER_SUITES'] = 'HIGH+ECDSA' 439 440 if platform.system() == 'Windows': 441 cert_file_path = os.path.join(os.environ['LOCALAPPDATA'], "Dcrwallet", "rpc.cert") 442 elif platform.system() == 'Darwin': 443 cert_file_path = os.path.join(os.environ['HOME'], 'Library', 'Application Support', 444 'Dcrwallet', 'rpc.cert') 445 else: 446 cert_file_path = os.path.join(os.environ['HOME'], '.dcrwallet', 'rpc.cert') 447 448 with open(cert_file_path, 'r') as f: 449 cert = f.read() 450 creds = implementations.ssl_client_credentials(cert, None, None) 451 channel = implementations.secure_channel('localhost', 19111, creds) 452 stub = walletrpc.beta_create_WalletService_stub(channel) 453 454 request = walletrpc.BalanceRequest(account_number = 0, required_confirmations = 1) 455 response = stub.Balance(request, timeout) 456 print 'Spendable balance: %d atoms' % response.spendable 457 458 if __name__ == '__main__': 459 main() 460 ```