Skip to content

Commit 79d0db2

Browse files
committed
fix: fix race condition in cryptonote coins
1 parent c90c9e9 commit 79d0db2

1 file changed

Lines changed: 83 additions & 82 deletions

File tree

lib/wallets/isar/models/wallet_info.dart

Lines changed: 83 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -187,78 +187,79 @@ class WalletInfo implements IsarId {
187187
required Balance newBalance,
188188
required Isar isar,
189189
}) async {
190-
// try to get latest instance of this from db
191-
final thisInfo = await isar.walletInfo.get(id) ?? this;
192-
193190
final newEncoded = newBalance.toJsonIgnoreCoin();
194-
195-
// only update if there were changes to the balance
196-
if (thisInfo.cachedBalanceString != newEncoded) {
197-
await isar.writeTxn(() async {
191+
// Read inside the tx so concurrent updates don't create a race condition.
192+
await isar.writeTxn(() async {
193+
final thisInfo = await isar.walletInfo
194+
.where()
195+
.walletIdEqualTo(walletId)
196+
.findFirst();
197+
if (thisInfo != null && thisInfo.cachedBalanceString != newEncoded) {
198198
await isar.walletInfo.delete(thisInfo.id);
199199
await isar.walletInfo.put(
200200
thisInfo.copyWith(cachedBalanceString: newEncoded),
201201
);
202-
});
203-
}
202+
}
203+
});
204204
}
205205

206206
Future<void> updateBalanceSecondary({
207207
required Balance newBalance,
208208
required Isar isar,
209209
}) async {
210-
// try to get latest instance of this from db
211-
final thisInfo = await isar.walletInfo.get(id) ?? this;
212-
213210
final newEncoded = newBalance.toJsonIgnoreCoin();
214-
215-
// only update if there were changes to the balance
216-
if (thisInfo.cachedBalanceSecondaryString != newEncoded) {
217-
await isar.writeTxn(() async {
211+
await isar.writeTxn(() async {
212+
final thisInfo = await isar.walletInfo
213+
.where()
214+
.walletIdEqualTo(walletId)
215+
.findFirst();
216+
if (thisInfo != null &&
217+
thisInfo.cachedBalanceSecondaryString != newEncoded) {
218218
await isar.walletInfo.delete(thisInfo.id);
219219
await isar.walletInfo.put(
220220
thisInfo.copyWith(cachedBalanceSecondaryString: newEncoded),
221221
);
222-
});
223-
}
222+
}
223+
});
224224
}
225225

226226
Future<void> updateBalanceTertiary({
227227
required Balance newBalance,
228228
required Isar isar,
229229
}) async {
230-
// try to get latest instance of this from db
231-
final thisInfo = await isar.walletInfo.get(id) ?? this;
232-
233230
final newEncoded = newBalance.toJsonIgnoreCoin();
234-
235-
// only update if there were changes to the balance
236-
if (thisInfo.cachedBalanceTertiaryString != newEncoded) {
237-
await isar.writeTxn(() async {
231+
await isar.writeTxn(() async {
232+
final thisInfo = await isar.walletInfo
233+
.where()
234+
.walletIdEqualTo(walletId)
235+
.findFirst();
236+
if (thisInfo != null &&
237+
thisInfo.cachedBalanceTertiaryString != newEncoded) {
238238
await isar.walletInfo.delete(thisInfo.id);
239239
await isar.walletInfo.put(
240240
thisInfo.copyWith(cachedBalanceTertiaryString: newEncoded),
241241
);
242-
});
243-
}
242+
}
243+
});
244244
}
245245

246246
/// copies this with a new chain height and updates the db
247247
Future<void> updateCachedChainHeight({
248248
required int newHeight,
249249
required Isar isar,
250250
}) async {
251-
// try to get latest instance of this from db
252-
final thisInfo = await isar.walletInfo.get(id) ?? this;
253-
// only update if there were changes to the height
254-
if (thisInfo.cachedChainHeight != newHeight) {
255-
await isar.writeTxn(() async {
251+
await isar.writeTxn(() async {
252+
final thisInfo = await isar.walletInfo
253+
.where()
254+
.walletIdEqualTo(walletId)
255+
.findFirst();
256+
if (thisInfo != null && thisInfo.cachedChainHeight != newHeight) {
256257
await isar.walletInfo.delete(thisInfo.id);
257258
await isar.walletInfo.put(
258259
thisInfo.copyWith(cachedChainHeight: newHeight),
259260
);
260-
});
261-
}
261+
}
262+
});
262263
}
263264

264265
/// update favourite wallet and its index it the ui list.
@@ -283,18 +284,18 @@ class WalletInfo implements IsarId {
283284
index = -1;
284285
}
285286

286-
// try to get latest instance of this from db
287-
final thisInfo = await isar.walletInfo.get(id) ?? this;
288-
289-
// only update if there were changes to the height
290-
if (thisInfo.favouriteOrderIndex != index) {
291-
await isar.writeTxn(() async {
287+
await isar.writeTxn(() async {
288+
final thisInfo = await isar.walletInfo
289+
.where()
290+
.walletIdEqualTo(walletId)
291+
.findFirst();
292+
if (thisInfo != null && thisInfo.favouriteOrderIndex != index) {
292293
await isar.walletInfo.delete(thisInfo.id);
293294
await isar.walletInfo.put(
294295
thisInfo.copyWith(favouriteOrderIndex: index),
295296
);
296-
});
297-
}
297+
}
298+
});
298299
}
299300

300301
/// copies this with a new name and updates the db
@@ -303,59 +304,60 @@ class WalletInfo implements IsarId {
303304
if (newName.isEmpty) {
304305
throw Exception("Empty wallet name not allowed!");
305306
}
306-
307-
// try to get latest instance of this from db
308-
final thisInfo = await isar.walletInfo.get(id) ?? this;
309-
310-
// only update if there were changes to the name
311-
if (thisInfo.name != newName) {
312-
await isar.writeTxn(() async {
307+
await isar.writeTxn(() async {
308+
final thisInfo = await isar.walletInfo
309+
.where()
310+
.walletIdEqualTo(walletId)
311+
.findFirst();
312+
if (thisInfo != null && thisInfo.name != newName) {
313313
await isar.walletInfo.delete(thisInfo.id);
314314
await isar.walletInfo.put(thisInfo.copyWith(name: newName));
315-
});
316-
}
315+
}
316+
});
317317
}
318318

319-
/// copies this with a new name and updates the db
319+
/// copies this with a new receiving address and updates the db
320320
Future<void> updateReceivingAddress({
321321
required String newAddress,
322322
required Isar isar,
323323
}) async {
324-
// try to get latest instance of this from db
325-
final thisInfo = await isar.walletInfo.get(id) ?? this;
326-
// only update if there were changes to the name
327-
if (thisInfo.cachedReceivingAddress != newAddress) {
328-
await isar.writeTxn(() async {
324+
await isar.writeTxn(() async {
325+
final thisInfo = await isar.walletInfo
326+
.where()
327+
.walletIdEqualTo(walletId)
328+
.findFirst();
329+
if (thisInfo != null && thisInfo.cachedReceivingAddress != newAddress) {
329330
await isar.walletInfo.delete(thisInfo.id);
330331
await isar.walletInfo.put(
331332
thisInfo.copyWith(cachedReceivingAddress: newAddress),
332333
);
333-
});
334-
}
334+
}
335+
});
335336
}
336337

337338
/// update [otherData] with the map entries in [newEntries]
338339
Future<void> updateOtherData({
339340
required Map<String, dynamic> newEntries,
340341
required Isar isar,
341342
}) async {
342-
// try to get latest instance of this from db
343-
final thisInfo = await isar.walletInfo.get(id) ?? this;
343+
await isar.writeTxn(() async {
344+
final thisInfo = await isar.walletInfo
345+
.where()
346+
.walletIdEqualTo(walletId)
347+
.findFirst();
348+
if (thisInfo == null) return;
344349

345-
final Map<String, dynamic> newMap = {};
346-
newMap.addAll(thisInfo.otherData);
347-
newMap.addAll(newEntries);
348-
final encodedNew = jsonEncode(newMap);
350+
final newMap = Map<String, dynamic>.from(thisInfo.otherData)
351+
..addAll(newEntries);
352+
final encodedNew = jsonEncode(newMap);
349353

350-
// only update if there were changes
351-
if (thisInfo.otherDataJsonString != encodedNew) {
352-
await isar.writeTxn(() async {
354+
if (thisInfo.otherDataJsonString != encodedNew) {
353355
await isar.walletInfo.delete(thisInfo.id);
354356
await isar.walletInfo.put(
355357
thisInfo.copyWith(otherDataJsonString: encodedNew),
356358
);
357-
});
358-
}
359+
}
360+
});
359361
}
360362

361363
/// Can be dangerous. Don't use unless you know the consequences
@@ -385,28 +387,26 @@ class WalletInfo implements IsarId {
385387
}
386388
}
387389

388-
/// copies this with a new name and updates the db
390+
/// copies this with a new restore height and updates the db
389391
Future<void> updateRestoreHeight({
390392
required int newRestoreHeight,
391393
required Isar isar,
392394
}) async {
393-
// don't allow empty names
394395
if (newRestoreHeight < 0) {
395396
throw Exception("Negative restore height not allowed!");
396397
}
397-
398-
// try to get latest instance of this from db
399-
final thisInfo = await isar.walletInfo.get(id) ?? this;
400-
401-
// only update if there were changes to the name
402-
if (thisInfo.restoreHeight != newRestoreHeight) {
403-
await isar.writeTxn(() async {
398+
await isar.writeTxn(() async {
399+
final thisInfo = await isar.walletInfo
400+
.where()
401+
.walletIdEqualTo(walletId)
402+
.findFirst();
403+
if (thisInfo != null && thisInfo.restoreHeight != newRestoreHeight) {
404404
await isar.walletInfo.delete(thisInfo.id);
405405
await isar.walletInfo.put(
406406
thisInfo.copyWith(restoreHeight: newRestoreHeight),
407407
);
408-
});
409-
}
408+
}
409+
});
410410
}
411411

412412
/// copies this with a new name and updates the db
@@ -442,7 +442,8 @@ class WalletInfo implements IsarId {
442442
}) async {
443443
await updateOtherData(
444444
newEntries: {
445-
WalletInfoKeys.solanaCustomTokenMintAddresses: newMintAddresses.toList(),
445+
WalletInfoKeys.solanaCustomTokenMintAddresses: newMintAddresses
446+
.toList(),
446447
},
447448
isar: isar,
448449
);

0 commit comments

Comments
 (0)