dev
dart 264 lines 8.57 KB
Raw
1 import 'dart:async';
2 import 'dart:convert';
3 import 'dart:developer';
4 import 'dart:io';
5 import 'dart:typed_data';
6
7 import 'package:cw_mweb/mweb_ffi.dart';
8 import 'package:cw_mweb/print_verbose.dart';
9 import 'package:grpc/grpc.dart';
10 import 'package:path_provider/path_provider.dart';
11 import 'mwebd.pbgrpc.dart';
12
13 class CwMweb {
14 static RpcClient? _rpcClient;
15 static ClientChannel? _clientChannel;
16 static int? _port;
17 static const TIMEOUT_DURATION = Duration(seconds: 15);
18 static Timer? logTimer;
19 static String? nodeUriOverride;
20
21 static Future<void> setNodeUriOverride(String uri) async {
22 nodeUriOverride = uri;
23 if (_rpcClient != null) {
24 await stop();
25 // will be re-started automatically when the next rpc call is made
26 }
27 }
28
29 static void readFileWithTimer(String filePath) {
30 final file = File(filePath);
31 int lastLength = 0;
32
33 logTimer?.cancel();
34 logTimer = Timer.periodic(const Duration(seconds: 1), (timer) async {
35 try {
36 final currentLength = await file.length();
37
38 if (currentLength != lastLength) {
39 final fileStream = file.openRead(lastLength, currentLength);
40 final newLines = await fileStream.transform(utf8.decoder).join();
41 lastLength = currentLength;
42 printV(newLines);
43 }
44 } on GrpcError catch (e) {
45 printV('Caught grpc error: ${e.message}');
46 } catch (e) {
47 printV('The mwebd debug log probably is not initialized yet.');
48 }
49 });
50 }
51
52 static Future<void> _initializeClient() async {
53 printV("_initializeClient() called!");
54 final appDir = await getApplicationSupportDirectory();
55 const ltcNodeUri = "ltc-electrum.cakewallet.com:9333";
56
57 String debugLogPath = "${appDir.path}/logs/debug.log";
58 readFileWithTimer(debugLogPath);
59
60 _port = MWebFfi.instance.start(appDir.path, nodeUriOverride ?? ltcNodeUri);
61 if (_port == null || _port == 0) {
62 throw Exception("Failed to start server");
63 }
64 printV("Attempting to connect to server on port: $_port");
65
66 _clientChannel = ClientChannel('127.0.0.1', port: _port!, channelShutdownHandler: () {
67 _rpcClient = null;
68 printV("Channel is shutting down!");
69 },
70 options: const ChannelOptions(
71 credentials: ChannelCredentials.insecure(),
72 keepAlive: ClientKeepAliveOptions(permitWithoutCalls: true),
73 ));
74 _rpcClient = RpcClient(_clientChannel!);
75 }
76
77 static Future<RpcClient> stub({int maxRetries = 3}) async {
78 for (int i = 0; i < maxRetries; i++) {
79 try {
80 if (_rpcClient == null) {
81 await _initializeClient();
82 }
83 final status = await _rpcClient!
84 .status(StatusRequest(), options: CallOptions(timeout: TIMEOUT_DURATION));
85 if (status.blockTime == 0) {
86 throw Exception("blockTime shouldn't be 0! (this connection is likely broken)");
87 }
88 return _rpcClient!;
89 } on GrpcError catch (e) {
90 printV("Attempt $i failed: $e");
91 printV('Caught grpc error: ${e.message}');
92 _rpcClient = null;
93 // necessary if the database isn't open:
94 await stop();
95 await Future.delayed(const Duration(seconds: 3));
96 } catch (e) {
97 printV("Attempt $i failed: $e");
98 _rpcClient = null;
99 await stop();
100 await Future.delayed(const Duration(seconds: 3));
101 }
102 }
103 throw Exception("Failed to connect after $maxRetries attempts");
104 }
105
106 static Future<void> stop() async {
107 try {
108 MWebFfi.instance.stop();
109 await cleanup();
110 } on GrpcError catch (e) {
111 printV('Caught grpc error: ${e.message}');
112 } catch (e) {
113 printV("Error stopping server: $e");
114 }
115 }
116
117 static String? address(Uint8List scanSecret, Uint8List spendPub, int index) {
118 try {
119 return MWebFfi.instance.addresses(scanSecret, spendPub, index, index + 1).split(',').first;
120 } on GrpcError catch (e) {
121 printV('Caught grpc error: ${e.message}');
122 } catch (e) {
123 printV("Error getting address: $e");
124 }
125 return null;
126 }
127
128 static List<String>? addresses(
129 Uint8List scanSecret, Uint8List spendPub, int fromIndex, int toIndex) {
130 try {
131 return MWebFfi.instance.addresses(scanSecret, spendPub, fromIndex, toIndex).split(',');
132 } on GrpcError catch (e) {
133 printV('Caught grpc error: ${e.message}');
134 } catch (e) {
135 printV("Error getting addresses: $e");
136 }
137 return null;
138 }
139
140 static Future<void> cleanup() async {
141 try {
142 await _clientChannel?.terminate();
143 } catch (_) {}
144 _rpcClient = null;
145 _clientChannel = null;
146 _port = null;
147 }
148
149 // wrappers that handle the connection issues:
150 static Future<SpentResponse> spent(SpentRequest request) async {
151 log("mweb.spent() called");
152 try {
153 _rpcClient = await stub();
154 return await _rpcClient!.spent(request, options: CallOptions(timeout: TIMEOUT_DURATION));
155 } on GrpcError catch (e) {
156 printV('Caught grpc error: ${e.message}');
157 } catch (e) {
158 printV("Error getting spent: $e");
159 }
160 return SpentResponse();
161 }
162
163 static Future<StatusResponse> status(StatusRequest request) async {
164 log("mweb.status() called");
165 try {
166 _rpcClient = await stub();
167 return await _rpcClient!.status(request, options: CallOptions(timeout: TIMEOUT_DURATION));
168 } on GrpcError catch (e) {
169 printV('Caught grpc error: ${e.message}');
170 } catch (e) {
171 printV("Error getting status: $e");
172 }
173 return StatusResponse();
174 }
175
176 static Future<CreateResponse> create(CreateRequest request) async {
177 log("mweb.create() called");
178 try {
179 _rpcClient = await stub();
180 return await _rpcClient!.create(request, options: CallOptions(timeout: TIMEOUT_DURATION));
181 } on GrpcError catch (e) {
182 printV('Caught grpc error: ${e.message}');
183 } catch (e) {
184 printV("Error getting create: $e");
185 }
186 return CreateResponse();
187 }
188
189 static Future<ResponseStream<Utxo>?> utxos(UtxosRequest request) async {
190 log("mweb.utxos() called");
191 try {
192 _rpcClient = await stub();
193 final resp = _rpcClient!
194 .utxos(request, options: CallOptions(timeout: const Duration(days: 1000 * 365)));
195 log("got utxo stream");
196 return resp;
197 } on GrpcError catch (e) {
198 printV('Caught grpc error: ${e.message}');
199 } catch (e) {
200 printV("Error getting utxos: $e");
201 }
202 return null;
203 }
204
205 static Future<BroadcastResponse> broadcast(BroadcastRequest request) async {
206 log("mweb.broadcast() called");
207 try {
208 _rpcClient = await stub();
209 return await _rpcClient!.broadcast(request, options: CallOptions(timeout: TIMEOUT_DURATION));
210 } on GrpcError catch (e) {
211 log('Caught grpc error: ${e.message}');
212 throw "error from broadcast mweb: $e";
213 } catch (e) {
214 printV("Error getting utxos: $e");
215 rethrow;
216 }
217 }
218
219 static Future<PsbtResponse> psbtCreate(PsbtCreateRequest request) async {
220 log("mweb.psbtCreate() called");
221 _rpcClient = await stub();
222 return await _rpcClient!.psbtCreate(request, options: CallOptions(timeout: TIMEOUT_DURATION));
223 }
224
225 static Future<PsbtResponse> psbtAddInput(PsbtAddInputRequest request) async {
226 log("mweb.psbtAddInput() called");
227 _rpcClient = await stub();
228 return await _rpcClient!.psbtAddInput(request, options: CallOptions(timeout: TIMEOUT_DURATION));
229 }
230
231 static Future<PsbtResponse> psbtAddRecipient(PsbtAddRecipientRequest request) async {
232 log("mweb.psbtAddRecipient() called");
233 _rpcClient = await stub();
234 return await _rpcClient!
235 .psbtAddRecipient(request, options: CallOptions(timeout: TIMEOUT_DURATION));
236 }
237
238 static Future<PsbtGetRecipientsResponse> psbtGetRecipients(
239 PsbtGetRecipientsRequest request) async {
240 log("mweb.psbtGetRecipients() called");
241 _rpcClient = await stub();
242 return await _rpcClient!
243 .psbtGetRecipients(request, options: CallOptions(timeout: TIMEOUT_DURATION));
244 }
245
246 static Future<CreateResponse> psbtExtract(PsbtExtractRequest request) async {
247 log("mweb.psbtExtract() called");
248 _rpcClient = await stub();
249 return await _rpcClient!.psbtExtract(request, options: CallOptions(timeout: TIMEOUT_DURATION));
250 }
251
252 static Future<PsbtResponse> psbtSign(PsbtSignRequest request) async {
253 printV("mweb.psbtSign() called");
254 _rpcClient = await stub();
255 return await _rpcClient!.psbtSign(request, options: CallOptions(timeout: TIMEOUT_DURATION));
256 }
257
258 static Future<PsbtResponse> psbtSignNonMweb(PsbtSignNonMwebRequest request) async {
259 printV("mweb.psbtSignNonMweb() called");
260 _rpcClient = await stub();
261 return await _rpcClient!
262 .psbtSignNonMweb(request, options: CallOptions(timeout: TIMEOUT_DURATION));
263 }
264 }