forked from FuelLabs/fuels-ts
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: optional UTXO caching (FuelLabs#930)
* use cache here * add coverage * add coverage * cs * actually test * lower ttl * fix test * pr review tweaks * rewrite using safeExec * refactor --------- Co-authored-by: Anderson Arboleya <[email protected]>
- Loading branch information
Showing
8 changed files
with
595 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@fuel-ts/providers": patch | ||
--- | ||
|
||
Added optional caching |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import type { BytesLike } from '@ethersproject/bytes'; | ||
import { hexlify } from '@ethersproject/bytes'; | ||
import { randomBytes } from '@fuel-ts/keystore'; | ||
|
||
import { MemoryCache } from './memory-cache'; | ||
|
||
const CACHE_ITEMS = [hexlify(randomBytes(8)), randomBytes(8), randomBytes(8)]; | ||
|
||
describe('Memory Cache', () => { | ||
it('can construct [valid numerical ttl]', () => { | ||
const memCache = new MemoryCache(1000); | ||
|
||
expect(memCache.ttl).toEqual(1000); | ||
}); | ||
|
||
it('can construct [invalid numerical ttl]', () => { | ||
expect(() => new MemoryCache(-1)).toThrow(/Invalid TTL: -1. Use a value greater than zero./); | ||
}); | ||
|
||
it('can construct [invalid mistyped ttl]', () => { | ||
// @ts-expect-error intentional invalid input | ||
expect(() => new MemoryCache('bogus')).toThrow( | ||
/Invalid TTL: bogus. Use a value greater than zero./ | ||
); | ||
}); | ||
|
||
it('can construct [missing ttl]', () => { | ||
const memCache = new MemoryCache(); | ||
|
||
expect(memCache.ttl).toEqual(30_000); | ||
}); | ||
|
||
it('can get [unknown key]', () => { | ||
const memCache = new MemoryCache(1000); | ||
|
||
expect( | ||
memCache.get('0xda5d131c490db33333333333333333334444444444444444444455555555556666') | ||
).toEqual(undefined); | ||
}); | ||
|
||
it('can get active [no data]', () => { | ||
const EXPECTED: BytesLike[] = []; | ||
const memCache = new MemoryCache(100); | ||
|
||
expect(memCache.getActiveData()).toStrictEqual(EXPECTED); | ||
}); | ||
|
||
it('can set', () => { | ||
const ttl = 1000; | ||
const expiresAt = Date.now() + ttl; | ||
const memCache = new MemoryCache(ttl); | ||
|
||
expect(memCache.set(CACHE_ITEMS[0])).toBeGreaterThanOrEqual(expiresAt); | ||
}); | ||
|
||
it('can get [valid key]', () => { | ||
const KEY = CACHE_ITEMS[1]; | ||
const memCache = new MemoryCache(100); | ||
|
||
memCache.set(KEY); | ||
|
||
expect(memCache.get(KEY)).toEqual(KEY); | ||
}); | ||
|
||
it('can get [valid key bytes like]', () => { | ||
const KEY = CACHE_ITEMS[2]; | ||
const memCache = new MemoryCache(100); | ||
|
||
memCache.set(KEY); | ||
|
||
expect(memCache.get(KEY)).toEqual(KEY); | ||
}); | ||
|
||
it('can get [valid key, expired content]', async () => { | ||
const KEY = randomBytes(8); | ||
const memCache = new MemoryCache(1); | ||
|
||
memCache.set(KEY); | ||
|
||
await new Promise((resolve) => { | ||
setTimeout(resolve, 10); | ||
}); | ||
|
||
expect(memCache.get(KEY)).toEqual(undefined); | ||
}); | ||
|
||
it('can get, disabling auto deletion [valid key, expired content]', async () => { | ||
const KEY = randomBytes(8); | ||
const memCache = new MemoryCache(1); | ||
|
||
memCache.set(KEY); | ||
|
||
await new Promise((resolve) => { | ||
setTimeout(resolve, 10); | ||
}); | ||
|
||
expect(memCache.get(KEY, false)).toEqual(KEY); | ||
}); | ||
|
||
it('can delete', () => { | ||
const KEY = randomBytes(8); | ||
const memCache = new MemoryCache(100); | ||
|
||
memCache.set(KEY); | ||
memCache.del(KEY); | ||
|
||
expect(memCache.get(KEY)).toEqual(undefined); | ||
}); | ||
|
||
it('can get active [with data]', () => { | ||
const EXPECTED: BytesLike[] = [CACHE_ITEMS[0], CACHE_ITEMS[1], CACHE_ITEMS[2]]; | ||
const memCache = new MemoryCache(100); | ||
|
||
expect(memCache.getActiveData()).toStrictEqual(EXPECTED); | ||
}); | ||
|
||
it('can get all [with data + expired data]', async () => { | ||
const KEY = randomBytes(8); | ||
const EXPECTED: BytesLike[] = [CACHE_ITEMS[0], CACHE_ITEMS[1], CACHE_ITEMS[2], KEY]; | ||
const memCache = new MemoryCache(1); | ||
memCache.set(KEY); | ||
|
||
await new Promise((resolve) => { | ||
setTimeout(resolve, 10); | ||
}); | ||
|
||
expect(memCache.getAllData()).toStrictEqual(EXPECTED); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import type { BytesLike } from '@ethersproject/bytes'; | ||
import { hexlify } from '@ethersproject/bytes'; | ||
|
||
type Cache = { | ||
[key: string]: { | ||
expires: number; | ||
value: BytesLike; | ||
}; | ||
}; | ||
const cache: Cache = {}; // it's a cache hash ~~> cash? | ||
|
||
const DEFAULT_TTL_IN_MS = 30 * 1000; // 30seconds | ||
|
||
export class MemoryCache { | ||
ttl: number; | ||
constructor(ttlInMs: number = DEFAULT_TTL_IN_MS) { | ||
this.ttl = ttlInMs; | ||
|
||
if (typeof ttlInMs !== 'number' || this.ttl <= 0) { | ||
throw new Error(`Invalid TTL: ${this.ttl}. Use a value greater than zero.`); | ||
} | ||
} | ||
|
||
get(value: BytesLike, isAutoExpiring = true): BytesLike | undefined { | ||
const key = hexlify(value); | ||
if (cache[key]) { | ||
if (!isAutoExpiring || cache[key].expires > Date.now()) { | ||
return cache[key].value; | ||
} | ||
|
||
this.del(value); | ||
} | ||
|
||
return undefined; | ||
} | ||
|
||
set(value: BytesLike): number { | ||
const expiresAt = Date.now() + this.ttl; | ||
const key = hexlify(value); | ||
cache[key] = { | ||
expires: expiresAt, | ||
value, | ||
}; | ||
|
||
return expiresAt; | ||
} | ||
|
||
getAllData(): BytesLike[] { | ||
return Object.keys(cache).reduce((list, key) => { | ||
const data = this.get(key, false); | ||
if (data) { | ||
list.push(data); | ||
} | ||
|
||
return list; | ||
}, [] as BytesLike[]); | ||
} | ||
|
||
getActiveData(): BytesLike[] { | ||
return Object.keys(cache).reduce((list, key) => { | ||
const data = this.get(key); | ||
if (data) { | ||
list.push(data); | ||
} | ||
|
||
return list; | ||
}, [] as BytesLike[]); | ||
} | ||
|
||
del(value: BytesLike) { | ||
const key = hexlify(value); | ||
delete cache[key]; | ||
} | ||
} |
Oops, something went wrong.