Example Remote Nonce Cache Provider
This test code utilises Redis for remote caching.
danger
This is test code, use it at your own risk.
const { NonceType } = require("avn-api");
const Redis = require("ioredis");
const Redlock = require("redlock");
class TestRedisNonceCacheProvider {
SLOT_PREFIX = "{avnApi}:";
constructor() {
this.nonceMap = {};
this.redisClient;
}
async connect() {
this.redisClient = new Redis();
this.lockTtlMs = 5000;
this.redlock = new Redlock([this.redisClient], {
driftFactor: 0.01,
retryCount: 10,
retryDelay: 500,
retryJitter: 200,
});
console.info(
"Connected to Redis database:\n",
(await this.redisClient.hello())
.map((e, i) => (i % 2 == 0 ? e + ":" : e + ", "))
.join("")
);
return this;
}
async initUserNonceCache(signerAddress) {
const signerKey = this.getSignerKey(signerAddress);
const isUserSetup = await this.redisClient.get(signerKey);
if (isUserSetup !== "true" && isUserSetup !== true) {
// Set the signer
await this.redisClient.set(this.getSignerKey(signerAddress), true);
// Set the nonces
for (const nonceType of Object.values(NonceType)) {
await this.saveNonceDataToRedis(signerAddress, nonceType, {
locked: false,
});
}
}
}
async getNonceData(signerAddress, nonceType) {
const nonceData = await this.readNonceDatafromRedis(
signerAddress,
nonceType
);
return nonceData;
}
async getNonceAndLock(signerAddress, nonceType) {
const lock = await this.redlock.acquire(
[`redlock-${signerAddress}${nonceType}`],
this.lockTtlMs
);
try {
let nonceData = await this.readNonceDatafromRedis(
signerAddress,
nonceType
);
if (nonceData.locked === false) {
const lockId = this.getLockId(
signerAddress,
nonceType,
nonceData.nonce
);
nonceData.locked = true;
nonceData.lockId = lockId;
await this.saveNonceDataToRedis(
signerAddress,
nonceType,
nonceData
);
return { lockAquired: true, data: nonceData };
}
return { lockAquired: false, data: nonceData };
} finally {
await lock.unlock();
}
}
async incrementNonce(lockId, signerAddress, nonceType, updateLastUpdate) {
let nonceData = await this.readNonceDatafromRedis(
signerAddress,
nonceType
);
if (nonceData.locked !== true || nonceData.lockId !== lockId) {
throw new Error(
`Invalid attempt to increment nonce. Current lock: ${nonceData.lockId} LockId: ${lockId}, signerAddress: ${signerAddress}, nonceType: ${nonceType}`
);
}
nonceData.nonce = parseInt(nonceData.nonce) + 1;
if (updateLastUpdate === true) {
nonceData.lastUpdated = Date.now();
}
await this.saveNonceDataToRedis(signerAddress, nonceType, nonceData);
return nonceData;
}
async unlockNonce(lockId, signerAddress, nonceType) {
let nonceData = await this.readNonceDatafromRedis(
signerAddress,
nonceType
);
if (nonceData.locked !== true || nonceData.lockId !== lockId) {
throw new Error(
`Invalid attempt to unlock nonce. Current lock: ${nonceData.lockId}. LockId: ${lockId}, signerAddress: ${signerAddress}, nonceType: ${nonceType}`
);
}
nonceData.locked = false;
nonceData.lockId = undefined;
await this.saveNonceDataToRedis(signerAddress, nonceType, nonceData);
}
async setNonce(lockId, signerAddress, nonceType, nonce) {
let nonceData = await this.readNonceDatafromRedis(
signerAddress,
nonceType
);
if (nonceData.locked !== true || nonceData.lockId !== lockId) {
throw new Error(
`Invalid attempt to set nonce. Current lock: ${nonceData.lockId}. LockId: ${lockId}, signerAddress: ${signerAddress}, nonceType: ${nonceType}`
);
}
nonceData = { nonce: nonce, lastUpdated: Date.now() };
await this.saveNonceDataToRedis(signerAddress, nonceType, nonceData);
}
getLockId(signerAddress, nonceType, nonce) {
return `${Date.now()}-${nonce}-${signerAddress}-${nonceType}`;
}
getNonceKey(signerAddress, nonceType) {
return `${this.getSignerKey(signerAddress)}-${nonceType}`;
}
getSignerKey(signerAddress) {
return `${this.SLOT_PREFIX}-${signerAddress}`;
}
async readNonceDatafromRedis(signerAddress, nonceType) {
const nonceKey = this.getNonceKey(signerAddress, nonceType);
const nonceData = await this.redisClient.hgetall(nonceKey);
// deal with boolean values
nonceData.locked = nonceData.locked === "true";
return nonceData;
}
async saveNonceDataToRedis(signerAddress, nonceType, nonceData) {
const nonceKey = this.getNonceKey(signerAddress, nonceType);
await this.redisClient.hset(nonceKey, nonceData);
}
}
module.exports = TestRedisNonceCacheProvider;