You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Commit 6f9bd2c converted the transaction-history readers (Show_Transfers, Get_Payments_DestinationPort, Get_Payments_TXID, GetTXKey) from w.Lock() to w.RLock(). But the writer of w.account.EntriesNative — SyncHistory — never takes w.Lock() at all; it holds only the package-level sync_multilock, which readers don't touch. The RWMutex therefore provides no exclusion against the one goroutine that actually mutates the map.
Additionally, Save_Wallet serializes the whole account with json.Marshal(w.account) — which iterates EntriesNative (map + entry slices) — without any lock shared with SyncHistory:
Wallet RPC server calls (GetTransfers, etc.) reading entries while the sync loop's SyncHistory prunes/appends them: concurrent map read/write → runtime fatal throw (not a recoverable panic), or torn slice reads.
Save_Wallet marshaling during a history scan: same fatal class, or a corrupted account snapshot encrypted to disk.
Have SyncHistory (and synchistory_block's entry appends) perform all w.account.EntriesNative mutations under w.Lock() — the readers' RLocks then actually exclude the writer — and take w.RLock() around the json.Marshal(w.account) in Save_Wallet. Run go test -race ./walletapi/... with a simulated concurrent sync to confirm.
Summary
Commit 6f9bd2c converted the transaction-history readers (
Show_Transfers,Get_Payments_DestinationPort,Get_Payments_TXID,GetTXKey) fromw.Lock()tow.RLock(). But the writer ofw.account.EntriesNative—SyncHistory— never takesw.Lock()at all; it holds only the package-levelsync_multilock, which readers don't touch. The RWMutex therefore provides no exclusion against the one goroutine that actually mutates the map.Writer (holds only
sync_multilock):derohe/walletapi/daemon_communication.go
Lines 596 to 604 in 6f9bd2c
Readers (RLock only — no shared lock with the writer):
derohe/walletapi/wallet.go
Lines 266 to 268 in 6f9bd2c
Additionally,
Save_Walletserializes the whole account withjson.Marshal(w.account)— which iteratesEntriesNative(map + entry slices) — without any lock shared with SyncHistory:derohe/walletapi/wallet_memory.go
Line 283 in 6f9bd2c
Impact
GetTransfers, etc.) reading entries while the sync loop'sSyncHistoryprunes/appends them: concurrent map read/write → runtime fatal throw (not a recoverable panic), or torn slice reads.Save_Walletmarshaling during a history scan: same fatal class, or a corrupted account snapshot encrypted to disk.go w.SyncHistory(scid)) unsafe as written — see the review discussion there. Fixing locking here first would unblock that PR.Fix
Have
SyncHistory(andsynchistory_block's entry appends) perform allw.account.EntriesNativemutations underw.Lock()— the readers'RLocks then actually exclude the writer — and takew.RLock()around thejson.Marshal(w.account)inSave_Wallet. Rungo test -race ./walletapi/...with a simulated concurrent sync to confirm.