await wallet address saving (#3393)
What changed, and why it matters
This commit fixes a timing bug in how the wallet saves address records. Previously, several save operations were started without waiting for each to finish, which could cause them to overlap or complete out of order. The change makes the code wait for each save step before starting the next one, and also tweaks how address lists are copied to avoid a subtle race where the list could change mid-save. A new network error message is also added to the ignore list, and a CI workflow step is adjusted.
Treat as a reliability and potential data-integrity hardening patch. Review whether any user wallets could have been saved with incomplete or inconsistent address state before this fix, and consider whether a migration or re-sync of address metadata is needed. No immediate exploit mitigation is indicated.
Security signals we found
Race condition in asynchronous persistence of wallet address metadata
Potential data inconsistency between wallet address, address map, and derived address lists due to unawaited futures
Map iteration mutation race mitigated by snapshotting entries list
No explicit security claim or CVE in commit message
Evidence from the diff
The core change is in WalletAddresses.saveAddressesInBox(): the calls to walletInfo.setAddresses, setAddressInfos, setUsedAddresses, setHiddenAddresses, and setManualAddresses are now awaited. In WalletInfo.setAddresses, the previous pattern of copying keys and values into separate lists has been replaced by copying entries into a new list, which avoids a potential mutation race if the source map is modified during iteration. The exception handler now ignores ‘Connection closed while receiving data’, and the lint CI workflow has a HOME fix and checkout step reordering.
Changed components
cw_core/lib/wallet_addresses.dartcw_core/lib/wallet_info.dartlib/utils/exception_handler.dart.github/workflows/lint.ymlInspect captured patch +16 / −10
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index bdcd36a4..e496201a 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -32,6 +32,12 @@ jobs:
api-level: [ 29 ]
steps:
+ - name: Fix github actions messing up $HOME...
+ run: "echo HOME=/root | sudo tee -a $GITHUB_ENV"
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ inputs.ref }}
+
- name: configure git
run: |
git config --global --add safe.directory '*'
diff --git a/cw_core/lib/wallet_addresses.dart b/cw_core/lib/wallet_addresses.dart
index ccbb8aa3..765afaa5 100644
--- a/cw_core/lib/wallet_addresses.dart
+++ b/cw_core/lib/wallet_addresses.dart
@@ -71,11 +71,12 @@ abstract class WalletAddresses {
Future<void> saveAddressesInBox() async {
try {
walletInfo.address = address;
- walletInfo.setAddresses(addressesMap);
- walletInfo.setAddressInfos(addressInfos);
- walletInfo.setUsedAddresses(usedAddresses.toList());
- walletInfo.setHiddenAddresses(hiddenAddresses.toList());
- walletInfo.setManualAddresses(manualAddresses.toList());
+ // TODO: check if it will affect the performance of each wallet
+ await walletInfo.setAddresses(addressesMap);
+ await walletInfo.setAddressInfos(addressInfos);
+ await walletInfo.setUsedAddresses(usedAddresses.toList());
+ await walletInfo.setHiddenAddresses(hiddenAddresses.toList());
+ await walletInfo.setManualAddresses(manualAddresses.toList());
await walletInfo.save();
} catch (e) {
diff --git a/cw_core/lib/wallet_info.dart b/cw_core/lib/wallet_info.dart
index 5877ad2d..a43bfa15 100644
--- a/cw_core/lib/wallet_info.dart
+++ b/cw_core/lib/wallet_info.dart
@@ -428,11 +428,9 @@ class WalletInfo {
Future<void> setAddresses(Map<String, String> addresses) async {
await WalletInfoAddressMap.deleteByWalletInfoId(internalId);
- final keys = addresses.keys.toList();
- final values = addresses.values.toList();
- // ToDo: check why the addresses list gets changed half way through
- for (int i = 0; i < keys.length; i++) {
- await WalletInfoAddressMap.insert(internalId, keys[i], values[i]);
+ final entries = addresses.entries.toList();
+ for (final entry in entries) {
+ await WalletInfoAddressMap.insert(internalId, entry.key, entry.value);
}
}
diff --git a/lib/utils/exception_handler.dart b/lib/utils/exception_handler.dart
index abbef80c..7a048fed 100644
--- a/lib/utils/exception_handler.dart
+++ b/lib/utils/exception_handler.dart
@@ -258,6 +258,7 @@ class ExceptionHandler {
"Connection timed out",
"Connection reset by peer",
"Connection closed before full header was received",
+ "Connection closed while receiving data",
"Connection terminated during handshake",
"OS Error: Connection refused, errno = 61",
"PERMISSION_NOT_GRANTED",
Why this scored 29/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.