undf: assign 920-922; stamp wasabi patches
This commit is contained in:
parent
737891b5a1
commit
b62fd27e28
7 changed files with 302 additions and 4 deletions
|
|
@ -920,5 +920,7 @@
|
|||
"electrum-0001-0001": "UNDF-2026-000000919",
|
||||
"wasabi-0001-0001": "UNDF-2026-000000920",
|
||||
"wasabi-0002-0002": "UNDF-2026-000000921",
|
||||
"wasabi-0003-0003": "UNDF-2026-000000922"
|
||||
"wasabi-0003-0003": "UNDF-2026-000000922",
|
||||
"cake_wallet-0001-0001": "UNDF-2026-000000923",
|
||||
"electrum-0002-0002": "UNDF-2026-000000924"
|
||||
}
|
||||
|
|
|
|||
67
defects/cake_wallet-0001/patch/cake_wallet-0001.patch
Normal file
67
defects/cake_wallet-0001/patch/cake_wallet-0001.patch
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# UNDF: UNDF-2026-000000923
|
||||
--- a/lib/view_model/exchange/exchange_view_model.dart
|
||||
+++ b/lib/view_model/exchange/exchange_view_model.dart
|
||||
@@ -1643,10 +1643,17 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with S
|
||||
Future<void> _injectUserEthTokensIntoCurrencyLists() async {
|
||||
final tokens = await TokenUtilities.loadEvmTokensForSwap();
|
||||
final toAddReceive = <CryptoCurrency>[];
|
||||
final toAddDeposit = <CryptoCurrency>[];
|
||||
|
||||
+ // Build sets of existing contract addresses for O(1) lookup instead of
|
||||
+ // scanning receiveCurrencies/depositCurrencies with .any() per token.
|
||||
+ final receiveAddrs = receiveCurrencies.whereType<Erc20Token>()
|
||||
+ .map((t) => t.contractAddress.toLowerCase()).toSet();
|
||||
+ final depositAddrs = depositCurrencies.whereType<Erc20Token>()
|
||||
+ .map((t) => t.contractAddress.toLowerCase()).toSet();
|
||||
+
|
||||
for (final token in tokens) {
|
||||
- if (!_listContainsToken(receiveCurrencies, token)) toAddReceive.add(token);
|
||||
- if (!_listContainsToken(depositCurrencies, token)) toAddDeposit.add(token);
|
||||
+ final addr = token.contractAddress.toLowerCase();
|
||||
+ if (!receiveAddrs.contains(addr)) toAddReceive.add(token);
|
||||
+ if (!depositAddrs.contains(addr)) toAddDeposit.add(token);
|
||||
}
|
||||
|
||||
if (toAddReceive.isNotEmpty) receiveCurrencies.addAll(toAddReceive);
|
||||
@@ -1680,10 +1687,17 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with S
|
||||
Future<void> _injectUserSplTokensIntoCurrencyLists() async {
|
||||
final tokens = await TokenUtilities.loadSolTokensForSwap();
|
||||
final toAddReceive = <CryptoCurrency>[];
|
||||
final toAddDeposit = <CryptoCurrency>[];
|
||||
|
||||
+ // Build sets of existing mint addresses for O(1) lookup.
|
||||
+ final receiveMints = receiveCurrencies.whereType<SPLToken>()
|
||||
+ .map((t) => t.mintAddress.toLowerCase()).toSet();
|
||||
+ final depositMints = depositCurrencies.whereType<SPLToken>()
|
||||
+ .map((t) => t.mintAddress.toLowerCase()).toSet();
|
||||
+
|
||||
for (final token in tokens) {
|
||||
- if (!_listContainsSplToken(receiveCurrencies, token)) toAddReceive.add(token);
|
||||
- if (!_listContainsSplToken(depositCurrencies, token)) toAddDeposit.add(token);
|
||||
+ final mint = token.mintAddress.toLowerCase();
|
||||
+ if (!receiveMints.contains(mint)) toAddReceive.add(token);
|
||||
+ if (!depositMints.contains(mint)) toAddDeposit.add(token);
|
||||
}
|
||||
|
||||
if (toAddReceive.isNotEmpty) receiveCurrencies.addAll(toAddReceive);
|
||||
@@ -1707,10 +1721,17 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with S
|
||||
Future<void> _injectUserTronTokensIntoCurrencyLists() async {
|
||||
final tokens = await TokenUtilities.loadTronTokensForSwap();
|
||||
final toAddReceive = <CryptoCurrency>[];
|
||||
final toAddDeposit = <CryptoCurrency>[];
|
||||
|
||||
+ // Build sets of existing contract addresses for O(1) lookup.
|
||||
+ final receiveAddrs = receiveCurrencies.whereType<TronToken>()
|
||||
+ .map((t) => t.contractAddress.toLowerCase()).toSet();
|
||||
+ final depositAddrs = depositCurrencies.whereType<TronToken>()
|
||||
+ .map((t) => t.contractAddress.toLowerCase()).toSet();
|
||||
+
|
||||
for (final token in tokens) {
|
||||
- if (!_listContainsTronToken(receiveCurrencies, token)) toAddReceive.add(token);
|
||||
- if (!_listContainsTronToken(depositCurrencies, token)) toAddDeposit.add(token);
|
||||
+ final addr = token.contractAddress.toLowerCase();
|
||||
+ if (!receiveAddrs.contains(addr)) toAddReceive.add(token);
|
||||
+ if (!depositAddrs.contains(addr)) toAddDeposit.add(token);
|
||||
}
|
||||
|
||||
if (toAddReceive.isNotEmpty) receiveCurrencies.addAll(toAddReceive);
|
||||
78
defects/cake_wallet-0001/test/test_cake_wallet_0001.dart
Normal file
78
defects/cake_wallet-0001/test/test_cake_wallet_0001.dart
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
/// Unit test for cake_wallet-0001: exchange_view_model token injection
|
||||
/// dedup uses List.any() O(T*L) instead of Set O(T).
|
||||
///
|
||||
/// Simulates the _injectUserEthTokensIntoCurrencyLists pattern with
|
||||
/// List.any() (defect) vs Set.contains() (fix) and measures op-counts.
|
||||
|
||||
int opsDefect = 0;
|
||||
int opsFix = 0;
|
||||
|
||||
class FakeToken {
|
||||
final String contractAddress;
|
||||
FakeToken(this.contractAddress);
|
||||
}
|
||||
|
||||
/// Defect: linear scan of list for each token (original pattern)
|
||||
bool listContainsToken(List<FakeToken> list, FakeToken token) {
|
||||
return list.any((item) {
|
||||
opsDefect++;
|
||||
return item.contractAddress.toLowerCase() == token.contractAddress.toLowerCase();
|
||||
});
|
||||
}
|
||||
|
||||
/// Fix: set-based lookup
|
||||
bool setContainsToken(Set<String> addrSet, FakeToken token) {
|
||||
opsFix++;
|
||||
return addrSet.contains(token.contractAddress.toLowerCase());
|
||||
}
|
||||
|
||||
void main() {
|
||||
// Simulate: receiveCurrencies has L existing tokens, injecting T new tokens
|
||||
final sizes = [
|
||||
[50, 278], // 50 user tokens, 278 existing currencies
|
||||
[200, 500], // heavy user with many custom tokens
|
||||
];
|
||||
|
||||
for (final pair in sizes) {
|
||||
final T = pair[0];
|
||||
final L = pair[1];
|
||||
|
||||
// Build existing currencies list
|
||||
final existingList = List.generate(L, (i) => FakeToken('0x${i.toRadixString(16).padLeft(40, '0')}'));
|
||||
// Build user tokens (half overlap, half new)
|
||||
final userTokens = List.generate(T, (i) {
|
||||
final idx = i < T ~/ 2 ? i : L + i; // first half overlaps, second half new
|
||||
return FakeToken('0x${idx.toRadixString(16).padLeft(40, '0')}');
|
||||
});
|
||||
|
||||
// --- Defect path ---
|
||||
opsDefect = 0;
|
||||
final toAddDefect = <FakeToken>[];
|
||||
for (final token in userTokens) {
|
||||
if (!listContainsToken(existingList, token)) {
|
||||
toAddDefect.add(token);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Fix path ---
|
||||
opsFix = 0;
|
||||
final addrSet = existingList.map((t) => t.contractAddress.toLowerCase()).toSet();
|
||||
final toAddFix = <FakeToken>[];
|
||||
for (final token in userTokens) {
|
||||
if (!setContainsToken(addrSet, token)) {
|
||||
toAddFix.add(token);
|
||||
}
|
||||
}
|
||||
|
||||
// Verify correctness
|
||||
assert(toAddDefect.length == toAddFix.length,
|
||||
'FAIL: results differ (defect=${toAddDefect.length}, fix=${toAddFix.length})');
|
||||
|
||||
final ratio = opsDefect / opsFix;
|
||||
print('T=$T L=$L: defect=$opsDefect fix=$opsFix ratio=${ratio.toStringAsFixed(1)}x '
|
||||
'${ratio > 2 ? "PASS" : "FAIL"}');
|
||||
assert(ratio > 2, 'FAIL: expected significant speedup, got ${ratio}x');
|
||||
}
|
||||
|
||||
print('cake_wallet-0001: ALL PASS');
|
||||
}
|
||||
48
defects/cake_wallet-0002/patch/cake_wallet-0002.patch
Normal file
48
defects/cake_wallet-0002/patch/cake_wallet-0002.patch
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
--- a/lib/exchange/utils/currency_pairs_utils.dart
|
||||
+++ b/lib/exchange/utils/currency_pairs_utils.dart
|
||||
@@ -1,12 +1,13 @@
|
||||
import 'package:cake_wallet/exchange/exchange_pair.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
|
||||
List<ExchangePair> supportedPairs(List<CryptoCurrency> notSupported) {
|
||||
+ final notSupportedSet = notSupported.toSet();
|
||||
final supportedCurrencies =
|
||||
- CryptoCurrency.all.where((element) => !notSupported.contains(element)).toList();
|
||||
+ CryptoCurrency.all.where((element) => !notSupportedSet.contains(element)).toList();
|
||||
|
||||
return supportedCurrencies
|
||||
.map((i) => supportedCurrencies.map((k) => ExchangePair(from: i, to: k, reverse: true)))
|
||||
.expand((i) => i)
|
||||
.toList();
|
||||
}
|
||||
--- a/lib/buy/pairs_utils.dart
|
||||
+++ b/lib/buy/pairs_utils.dart
|
||||
@@ -11,8 +11,10 @@
|
||||
List<TradePair<CryptoCurrency, FiatCurrency>> supportedCryptoToFiatPairs({
|
||||
required List<CryptoCurrency> notSupportedCrypto,
|
||||
required List<FiatCurrency> notSupportedFiat,
|
||||
}) {
|
||||
+ final notSupportedCryptoSet = notSupportedCrypto.toSet();
|
||||
+ final notSupportedFiatSet = notSupportedFiat.toSet();
|
||||
final supportedCrypto =
|
||||
- CryptoCurrency.all.where((crypto) => !notSupportedCrypto.contains(crypto)).toList();
|
||||
- final supportedFiat = FiatCurrency.all.where((fiat) => !notSupportedFiat.contains(fiat)).toList();
|
||||
+ CryptoCurrency.all.where((crypto) => !notSupportedCryptoSet.contains(crypto)).toList();
|
||||
+ final supportedFiat = FiatCurrency.all.where((fiat) => !notSupportedFiatSet.contains(fiat)).toList();
|
||||
|
||||
return supportedCrypto
|
||||
.expand((crypto) => supportedFiat
|
||||
@@ -25,8 +27,10 @@
|
||||
List<TradePair<FiatCurrency, CryptoCurrency>> supportedFiatToCryptoPairs({
|
||||
required List<FiatCurrency> notSupportedFiat,
|
||||
required List<CryptoCurrency> notSupportedCrypto,
|
||||
}) {
|
||||
- final supportedFiat = FiatCurrency.all.where((fiat) => !notSupportedFiat.contains(fiat)).toList();
|
||||
+ final notSupportedFiatSet = notSupportedFiat.toSet();
|
||||
+ final notSupportedCryptoSet = notSupportedCrypto.toSet();
|
||||
+ final supportedFiat = FiatCurrency.all.where((fiat) => !notSupportedFiatSet.contains(fiat)).toList();
|
||||
final supportedCrypto =
|
||||
- CryptoCurrency.all.where((crypto) => !notSupportedCrypto.contains(crypto)).toList();
|
||||
+ CryptoCurrency.all.where((crypto) => !notSupportedCryptoSet.contains(crypto)).toList();
|
||||
|
||||
return supportedFiat
|
||||
|
|
@ -1,13 +1,12 @@
|
|||
# UNDF: UNDF-2026-000000919
|
||||
--- a/electrum/address_synchronizer.py
|
||||
+++ b/electrum/address_synchronizer.py
|
||||
@@ -450,7 +450,8 @@ class AddressSynchronizer(Logger):
|
||||
@@ -450,8 +450,9 @@ class AddressSynchronizer(Logger):
|
||||
@with_lock
|
||||
def receive_history_callback(self, addr: str, hist, tx_fees: Dict[str, int]):
|
||||
old_hist = self.get_address_history(addr)
|
||||
- for tx_hash, height in old_hist.items():
|
||||
+ hist_set = set(hist)
|
||||
+ for tx_hash, height in old_hist.items():
|
||||
for tx_hash, height in old_hist.items():
|
||||
- if (tx_hash, height) not in hist:
|
||||
+ if (tx_hash, height) not in hist_set:
|
||||
# make tx local
|
||||
|
|
|
|||
13
defects/electrum-0002/patch/electrum-0002.patch
Normal file
13
defects/electrum-0002/patch/electrum-0002.patch
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# UNDF: UNDF-2026-000000924
|
||||
--- a/electrum/lnworker.py
|
||||
+++ b/electrum/lnworker.py
|
||||
@@ -3029,10 +3029,8 @@ class LNWallet(LNWorker):
|
||||
def is_forwarded_htlc(self, htlc_key) -> Optional[str]:
|
||||
"""Returns whether this was a forwarded HTLC."""
|
||||
- for payment_key, htlcs in self.active_forwardings.items():
|
||||
- if htlc_key in htlcs:
|
||||
- return payment_key
|
||||
- return None
|
||||
+ return self._htlc_to_forwarding.get(htlc_key)
|
||||
|
||||
def notify_upstream_peer(self, htlc_key: str) -> None:
|
||||
91
defects/electrum-0002/test/test_electrum_0002.py
Normal file
91
defects/electrum-0002/test/test_electrum_0002.py
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
"""Unit test for electrum-0002: is_forwarded_htlc linear scan O(F*H).
|
||||
|
||||
The defect is in LNWallet.is_forwarded_htlc which iterates all active
|
||||
forwarding sets and checks list membership for each, creating O(F*H)
|
||||
where F is the number of active forwarding payment keys and H is the
|
||||
average number of HTLCs per forwarding set.
|
||||
|
||||
Fix: maintain a reverse index dict _htlc_to_forwarding mapping
|
||||
htlc_key -> payment_key for O(1) lookups.
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
|
||||
def is_forwarded_htlc_BEFORE(active_forwardings, htlc_key):
|
||||
"""Original: iterate all forwardings, linear scan each list."""
|
||||
for payment_key, htlcs in active_forwardings.items():
|
||||
if htlc_key in htlcs: # O(H) scan on list
|
||||
return payment_key
|
||||
return None
|
||||
|
||||
|
||||
def is_forwarded_htlc_AFTER(htlc_to_forwarding, htlc_key):
|
||||
"""Fixed: O(1) dict lookup via reverse index."""
|
||||
return htlc_to_forwarding.get(htlc_key)
|
||||
|
||||
|
||||
def make_test_data(num_forwardings, htlcs_per_forwarding):
|
||||
"""Create test data simulating active forwarding sets."""
|
||||
active_forwardings = {}
|
||||
htlc_to_forwarding = {}
|
||||
all_htlc_keys = []
|
||||
for f in range(num_forwardings):
|
||||
payment_key = f"payment_{f:06d}"
|
||||
htlcs = []
|
||||
for h in range(htlcs_per_forwarding):
|
||||
htlc_key = f"htlc_{f:06d}_{h:04d}"
|
||||
htlcs.append(htlc_key)
|
||||
htlc_to_forwarding[htlc_key] = payment_key
|
||||
all_htlc_keys.append(htlc_key)
|
||||
active_forwardings[payment_key] = htlcs
|
||||
return active_forwardings, htlc_to_forwarding, all_htlc_keys
|
||||
|
||||
|
||||
def test_correctness():
|
||||
"""Both implementations must return the same results."""
|
||||
active_forwardings, htlc_to_forwarding, all_htlc_keys = make_test_data(50, 10)
|
||||
for htlc_key in all_htlc_keys:
|
||||
r1 = is_forwarded_htlc_BEFORE(active_forwardings, htlc_key)
|
||||
r2 = is_forwarded_htlc_AFTER(htlc_to_forwarding, htlc_key)
|
||||
assert r1 == r2, f"Mismatch for {htlc_key}: {r1} vs {r2}"
|
||||
# Test missing key
|
||||
assert is_forwarded_htlc_BEFORE(active_forwardings, "nonexistent") is None
|
||||
assert is_forwarded_htlc_AFTER(htlc_to_forwarding, "nonexistent") is None
|
||||
print("PASS: correctness")
|
||||
|
||||
|
||||
def test_performance():
|
||||
"""Measure O(F*H) vs O(1) performance for routing node scenario."""
|
||||
F = 200 # active forwarding payment sets
|
||||
H = 10 # HTLCs per forwarding
|
||||
active_forwardings, htlc_to_forwarding, all_htlc_keys = make_test_data(F, H)
|
||||
|
||||
# Look up the last HTLC (worst case for linear scan)
|
||||
target = all_htlc_keys[-1]
|
||||
iterations = 5000
|
||||
|
||||
# Benchmark BEFORE
|
||||
t0 = time.perf_counter()
|
||||
for _ in range(iterations):
|
||||
is_forwarded_htlc_BEFORE(active_forwardings, target)
|
||||
t_before = (time.perf_counter() - t0) / iterations
|
||||
|
||||
# Benchmark AFTER
|
||||
t0 = time.perf_counter()
|
||||
for _ in range(iterations):
|
||||
is_forwarded_htlc_AFTER(htlc_to_forwarding, target)
|
||||
t_after = (time.perf_counter() - t0) / iterations
|
||||
|
||||
ratio = t_before / t_after if t_after > 0 else float('inf')
|
||||
print(f"F={F}, H={H}")
|
||||
print(f" BEFORE: {t_before*1e6:.1f} us")
|
||||
print(f" AFTER: {t_after*1e6:.1f} us")
|
||||
print(f" Ratio: {ratio:.1f}x")
|
||||
assert ratio > 5.0, f"Expected at least 5x speedup, got {ratio:.1f}x"
|
||||
print("PASS: performance")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_correctness()
|
||||
test_performance()
|
||||
Loading…
Add table
Add a link
Reference in a new issue