@pioneer-platform/pioneer-cache 1.28.14 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +22 -0
- package/dist/core/base-cache.js +36 -4
- package/dist/stores/price-cache.js +20 -0
- package/package.json +5 -5
- package/src/core/base-cache.ts +41 -5
- package/src/stores/price-cache.ts +21 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
> @pioneer-platform/pioneer-cache@
|
|
3
|
+
> @pioneer-platform/pioneer-cache@2.0.0 build /Users/highlander/WebstormProjects/keepkey-stack/projects/pioneer/modules/pioneer/pioneer-cache
|
|
4
4
|
> tsc
|
|
5
5
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @pioneer-platform/pioneer-cache
|
|
2
2
|
|
|
3
|
+
## 2.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- fix: prevent $0 price death spiral for major cryptocurrencies
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies
|
|
12
|
+
- @pioneer-platform/pioneer-discovery@9.0.0
|
|
13
|
+
|
|
14
|
+
## 1.28.15
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- chore: chore: fix: Redis gzip binary corruption in tx history + SDK transaction loading from vault cache
|
|
19
|
+
- Updated dependencies
|
|
20
|
+
- @pioneer-platform/pioneer-discovery@8.51.15
|
|
21
|
+
- @pioneer-platform/pioneer-caip@9.27.10
|
|
22
|
+
- @pioneer-platform/default-redis@8.11.11
|
|
23
|
+
- @pioneer-platform/redis-queue@8.12.21
|
|
24
|
+
|
|
3
25
|
## 1.28.14
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
package/dist/core/base-cache.js
CHANGED
|
@@ -103,14 +103,37 @@ class BaseCache {
|
|
|
103
103
|
normalizedCaip &&
|
|
104
104
|
Array.from(MAJOR_CRYPTO_WHITELIST).some(wl => wl.toLowerCase() === normalizedCaip));
|
|
105
105
|
if (isInvalidMajorCryptoPrice) {
|
|
106
|
-
log.warn(tag, `🚨 Zero price cached for major cryptocurrency: ${params.caip} -
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
log.warn(tag, `🚨 Zero price cached for major cryptocurrency: ${params.caip} - BLOCKING to fetch fresh price`);
|
|
107
|
+
// DELETE the $0 entry so it can't poison future requests
|
|
108
|
+
try {
|
|
109
|
+
await this.redis.del(key);
|
|
110
|
+
log.warn(tag, `🗑️ Deleted $0 cache entry for major crypto: ${key}`);
|
|
111
|
+
}
|
|
112
|
+
catch (delErr) {
|
|
113
|
+
log.error(tag, `Failed to delete $0 entry:`, delErr);
|
|
114
|
+
}
|
|
115
|
+
// BLOCK and wait for a real price — $0 BTC is never acceptable
|
|
116
|
+
try {
|
|
117
|
+
const freshValue = await this.fetchFresh(params);
|
|
118
|
+
if (freshValue.price > 0) {
|
|
119
|
+
return {
|
|
120
|
+
success: true,
|
|
121
|
+
value: freshValue,
|
|
122
|
+
cached: false,
|
|
123
|
+
fresh: true,
|
|
124
|
+
age: 0
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch (fetchErr) {
|
|
129
|
+
log.error(tag, `Fresh fetch failed for major crypto:`, fetchErr);
|
|
130
|
+
}
|
|
131
|
+
// Last resort: return $0 but mark as invalid
|
|
109
132
|
return {
|
|
110
133
|
success: true,
|
|
111
134
|
value: cachedValue.value,
|
|
112
135
|
cached: true,
|
|
113
|
-
fresh: false,
|
|
136
|
+
fresh: false,
|
|
114
137
|
age,
|
|
115
138
|
invalidPrice: true
|
|
116
139
|
};
|
|
@@ -237,6 +260,15 @@ class BaseCache {
|
|
|
237
260
|
// CRITICAL: Never overwrite existing non-zero price with $0
|
|
238
261
|
// This protects against API failures/rate limits overwriting good cached prices
|
|
239
262
|
if (this.config.name === 'price' && value.price === 0) {
|
|
263
|
+
// Extract CAIP from key (format: price_v2:<caip>)
|
|
264
|
+
const caipFromKey = key.replace(this.config.keyPrefix, '');
|
|
265
|
+
const isMajorCrypto = Array.from(MAJOR_CRYPTO_WHITELIST).some(wl => wl.toLowerCase() === caipFromKey.toLowerCase());
|
|
266
|
+
if (isMajorCrypto) {
|
|
267
|
+
// NEVER cache $0 for major crypto — this is always an API failure
|
|
268
|
+
log.error(tag, `🛡️ BLOCKING $0 cache write for major crypto: ${key}`);
|
|
269
|
+
log.error(tag, ` $0 for BTC/ETH/etc is ALWAYS an API failure, never caching it`);
|
|
270
|
+
return; // Do NOT write — $0 for major crypto is NEVER valid
|
|
271
|
+
}
|
|
240
272
|
const existingCache = await this.getCached(key);
|
|
241
273
|
if (existingCache && existingCache.value.price > 0) {
|
|
242
274
|
log.warn(tag, `🛡️ Refusing to overwrite $${existingCache.value.price} with $0 for ${key}`);
|
|
@@ -87,6 +87,26 @@ class PriceCache extends base_cache_1.BaseCache {
|
|
|
87
87
|
throw new Error(`No valid price available for ${caip}`);
|
|
88
88
|
}
|
|
89
89
|
log.debug(tag, `Fetched price for ${caip}: $${price}`);
|
|
90
|
+
// CRITICAL: $0 for major crypto = API failure, NOT a valid price
|
|
91
|
+
// Throw so fetchFresh() falls back to stale cache instead of caching $0
|
|
92
|
+
if (price === 0) {
|
|
93
|
+
const normalizedCaip = caip.toLowerCase();
|
|
94
|
+
const MAJOR_CAIPS = [
|
|
95
|
+
'bip122:000000000019d6689c085ae165831e93/slip44:0', // BTC
|
|
96
|
+
'eip155:1/slip44:60', // ETH
|
|
97
|
+
'eip155:56/slip44:60', // BNB
|
|
98
|
+
'cosmos:cosmoshub-4/slip44:118', // ATOM
|
|
99
|
+
'cosmos:thorchain-mainnet-v1/slip44:931', // RUNE
|
|
100
|
+
'ripple:4109c6f2045fc7eff4cde8f9905d19c2/slip44:144', // XRP
|
|
101
|
+
'bip122:12a765e31ffd4059bada1e25190f6e98/slip44:2', // LTC
|
|
102
|
+
'bip122:00000000001a91e3dace36e2be3bf030/slip44:3', // DOGE
|
|
103
|
+
];
|
|
104
|
+
const isMajorCrypto = MAJOR_CAIPS.some(wl => wl.toLowerCase() === normalizedCaip);
|
|
105
|
+
if (isMajorCrypto) {
|
|
106
|
+
log.error(tag, `🚨 All APIs returned $0 for major crypto ${caip} — treating as API failure`);
|
|
107
|
+
throw new Error(`API failure: $0 price for major crypto ${caip}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
90
110
|
// FIX #8: Mark zero prices with special source to indicate they are unpriceable tokens
|
|
91
111
|
// This allows us to track and monitor unpriceable token caching
|
|
92
112
|
const source = price === 0 ? 'unpriceable' : 'markets-caip';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pioneer-platform/pioneer-cache",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Unified caching system for Pioneer platform with Redis backend",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -15,10 +15,10 @@
|
|
|
15
15
|
"license": "MIT",
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@pioneer-platform/loggerdog": "8.11.0",
|
|
18
|
-
"@pioneer-platform/
|
|
19
|
-
"@pioneer-platform/
|
|
20
|
-
"@pioneer-platform/redis-queue": "8.12.
|
|
21
|
-
"@pioneer-platform/pioneer-caip": "9.27.
|
|
18
|
+
"@pioneer-platform/default-redis": "8.11.11",
|
|
19
|
+
"@pioneer-platform/pioneer-discovery": "9.0.0",
|
|
20
|
+
"@pioneer-platform/redis-queue": "8.12.21",
|
|
21
|
+
"@pioneer-platform/pioneer-caip": "9.27.10"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@types/jest": "^29.5.0",
|
package/src/core/base-cache.ts
CHANGED
|
@@ -130,15 +130,38 @@ export abstract class BaseCache<T> {
|
|
|
130
130
|
);
|
|
131
131
|
|
|
132
132
|
if (isInvalidMajorCryptoPrice) {
|
|
133
|
-
log.warn(tag, `🚨 Zero price cached for major cryptocurrency: ${params.caip} -
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
133
|
+
log.warn(tag, `🚨 Zero price cached for major cryptocurrency: ${params.caip} - BLOCKING to fetch fresh price`);
|
|
134
|
+
|
|
135
|
+
// DELETE the $0 entry so it can't poison future requests
|
|
136
|
+
try {
|
|
137
|
+
await this.redis.del(key);
|
|
138
|
+
log.warn(tag, `🗑️ Deleted $0 cache entry for major crypto: ${key}`);
|
|
139
|
+
} catch (delErr) {
|
|
140
|
+
log.error(tag, `Failed to delete $0 entry:`, delErr);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// BLOCK and wait for a real price — $0 BTC is never acceptable
|
|
144
|
+
try {
|
|
145
|
+
const freshValue = await this.fetchFresh(params);
|
|
146
|
+
if ((freshValue as any).price > 0) {
|
|
147
|
+
return {
|
|
148
|
+
success: true,
|
|
149
|
+
value: freshValue,
|
|
150
|
+
cached: false,
|
|
151
|
+
fresh: true,
|
|
152
|
+
age: 0
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
} catch (fetchErr) {
|
|
156
|
+
log.error(tag, `Fresh fetch failed for major crypto:`, fetchErr);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Last resort: return $0 but mark as invalid
|
|
137
160
|
return {
|
|
138
161
|
success: true,
|
|
139
162
|
value: cachedValue.value,
|
|
140
163
|
cached: true,
|
|
141
|
-
fresh: false,
|
|
164
|
+
fresh: false,
|
|
142
165
|
age,
|
|
143
166
|
invalidPrice: true
|
|
144
167
|
};
|
|
@@ -281,6 +304,19 @@ export abstract class BaseCache<T> {
|
|
|
281
304
|
// CRITICAL: Never overwrite existing non-zero price with $0
|
|
282
305
|
// This protects against API failures/rate limits overwriting good cached prices
|
|
283
306
|
if (this.config.name === 'price' && (value as any).price === 0) {
|
|
307
|
+
// Extract CAIP from key (format: price_v2:<caip>)
|
|
308
|
+
const caipFromKey = key.replace(this.config.keyPrefix, '');
|
|
309
|
+
const isMajorCrypto = Array.from(MAJOR_CRYPTO_WHITELIST).some(
|
|
310
|
+
wl => wl.toLowerCase() === caipFromKey.toLowerCase()
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
if (isMajorCrypto) {
|
|
314
|
+
// NEVER cache $0 for major crypto — this is always an API failure
|
|
315
|
+
log.error(tag, `🛡️ BLOCKING $0 cache write for major crypto: ${key}`);
|
|
316
|
+
log.error(tag, ` $0 for BTC/ETH/etc is ALWAYS an API failure, never caching it`);
|
|
317
|
+
return; // Do NOT write — $0 for major crypto is NEVER valid
|
|
318
|
+
}
|
|
319
|
+
|
|
284
320
|
const existingCache = await this.getCached(key);
|
|
285
321
|
if (existingCache && (existingCache.value as any).price > 0) {
|
|
286
322
|
log.warn(tag, `🛡️ Refusing to overwrite $${(existingCache.value as any).price} with $0 for ${key}`);
|
|
@@ -112,6 +112,27 @@ export class PriceCache extends BaseCache<PriceData> {
|
|
|
112
112
|
|
|
113
113
|
log.debug(tag, `Fetched price for ${caip}: $${price}`);
|
|
114
114
|
|
|
115
|
+
// CRITICAL: $0 for major crypto = API failure, NOT a valid price
|
|
116
|
+
// Throw so fetchFresh() falls back to stale cache instead of caching $0
|
|
117
|
+
if (price === 0) {
|
|
118
|
+
const normalizedCaip = caip.toLowerCase();
|
|
119
|
+
const MAJOR_CAIPS = [
|
|
120
|
+
'bip122:000000000019d6689c085ae165831e93/slip44:0', // BTC
|
|
121
|
+
'eip155:1/slip44:60', // ETH
|
|
122
|
+
'eip155:56/slip44:60', // BNB
|
|
123
|
+
'cosmos:cosmoshub-4/slip44:118', // ATOM
|
|
124
|
+
'cosmos:thorchain-mainnet-v1/slip44:931', // RUNE
|
|
125
|
+
'ripple:4109c6f2045fc7eff4cde8f9905d19c2/slip44:144', // XRP
|
|
126
|
+
'bip122:12a765e31ffd4059bada1e25190f6e98/slip44:2', // LTC
|
|
127
|
+
'bip122:00000000001a91e3dace36e2be3bf030/slip44:3', // DOGE
|
|
128
|
+
];
|
|
129
|
+
const isMajorCrypto = MAJOR_CAIPS.some(wl => wl.toLowerCase() === normalizedCaip);
|
|
130
|
+
if (isMajorCrypto) {
|
|
131
|
+
log.error(tag, `🚨 All APIs returned $0 for major crypto ${caip} — treating as API failure`);
|
|
132
|
+
throw new Error(`API failure: $0 price for major crypto ${caip}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
115
136
|
// FIX #8: Mark zero prices with special source to indicate they are unpriceable tokens
|
|
116
137
|
// This allows us to track and monitor unpriceable token caching
|
|
117
138
|
const source = price === 0 ? 'unpriceable' : 'markets-caip';
|