From 9b1202782b123fb138ae91eadd6f9aa7fdeb8d5a Mon Sep 17 00:00:00 2001 From: dumbledope <84159538+dumbledope@users.noreply.github.com> Date: Thu, 15 Sep 2022 13:19:43 -0700 Subject: [PATCH 1/9] fix: better backoff strat for balances job --- src/worker/queues.ts | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/worker/queues.ts b/src/worker/queues.ts index 6883f67..253b1c2 100644 --- a/src/worker/queues.ts +++ b/src/worker/queues.ts @@ -39,35 +39,37 @@ const opts: QueueOptions = { }, }; +function fixedBackoff(attempts: number, delay: number) { + return { + ...opts, + defaultJobOptions: { + attempts: attempts, + backoff: { + type: 'fixed', + delay: delay, + }, + }, + }; +} + +function fixedBackoffMins(attempts: number, delayMinutes: number) { + return fixedBackoff(attempts, delayMinutes * 60 * 1000); +} + export const eventQueue = new Queue('events', opts); -export const finalizeInboundQueue = new Queue('finalize-inbound', { - ...opts, - defaultJobOptions: { - attempts: 12, - backoff: { - type: 'fixed', - delay: 10 * 60 * 1000, // attempt every 10 minutes - }, - }, -}); +export const finalizeInboundQueue = new Queue( + 'finalize-inbound', + fixedBackoffMins(12, 10) +); export const sendOutboundQueue = new Queue('send-outbound', opts); -export const finalizeOutboundQueue = new Queue('finalize-outbound', { - ...opts, - defaultJobOptions: { - attempts: 24, - backoff: { - type: 'fixed', - delay: 10 * 60 * 1000, // attempt every 10 minutes - }, - }, -}); +export const finalizeOutboundQueue = new Queue('finalize-outbound', fixedBackoffMins(24, 10)); export const eventCronQueue = new Queue('events-cron', opts); -export const balancesQueue = new Queue('balance-check', opts); +export const balancesQueue = new Queue('balance-check', fixedBackoffMins(12, 5)); export const allQueues = [ eventQueue, From fb05ccba42e467a64b57a73d1d118f5edab0e321 Mon Sep 17 00:00:00 2001 From: dumbledope <84159538+dumbledope@users.noreply.github.com> Date: Thu, 15 Sep 2022 13:20:22 -0700 Subject: [PATCH 2/9] feat: simple analytics script --- .gitignore | 4 +- scripts/analytics.ts | 155 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 scripts/analytics.ts diff --git a/.gitignore b/.gitignore index e70d938..0ed5883 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,6 @@ yarn-error.log* .yalc settings/Testnet.toml -scripts/migrate-redis.ts \ No newline at end of file +scripts/migrate-redis.ts + +tmpdata \ No newline at end of file diff --git a/scripts/analytics.ts b/scripts/analytics.ts new file mode 100644 index 0000000..37481ef --- /dev/null +++ b/scripts/analytics.ts @@ -0,0 +1,155 @@ +import 'cross-fetch/polyfill'; +import { getContractEventsUntil } from '../src/stacks-api'; +import { getQueryApi } from '../src/metrics'; +import { writeFile, readFile } from 'fs/promises'; +import { + isFinalizeInboundEvent, + Prints, + Event, + isEscrowEvent, + isInitiateOutboundEvent, + isFinalizeOutboundEvent, + isRevokeInboundEvent, + isRevokeOutboundEvent, +} from '../src/events'; +import { resolve } from 'path'; +import { satsToBtc } from '../src/utils'; +import { bytesToHex, hexToBytes } from 'micro-stacks/common'; + +const [command] = process.argv.slice(2); + +function replacer(key: string, value: any): any { + if (typeof value === 'bigint') { + return { __bigintval__: value.toString() }; + } else if (value instanceof Uint8Array) { + console.log('is buff'); + return { __buff__: bytesToHex(value) }; + } + return value; +} + +function reviver(key: string, value: any): any { + if (value != null && typeof value === 'object' && '__bigintval__' in value) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + return BigInt(value['__bigintval__']); + } + if (value != null && typeof value === 'object' && '__buff__' in value) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + return hexToBytes(value['__buff__']); + } + return value; +} + +async function run() { + const dataFile = resolve(__dirname, '../tmpdata/events.json'); + // const totalInbound = + if (command === 'write') { + const events = await getContractEventsUntil(null); + console.log(`Found ${events.length} events`); + const json = JSON.stringify(events, replacer); + await writeFile(dataFile, json, { + encoding: 'utf-8', + }); + return; + } else if (command === 'pending') { + const eventsJSON = await readFile(dataFile, { encoding: 'utf-8' }); + const events = JSON.parse(eventsJSON, reviver) as Event[]; + events.reverse(); + + // const ins: string[] = []; + const ins = new Set(); + const outs = new Set(); + events.forEach(event => { + if (isEscrowEvent(event)) { + console.log('start', event.print.swapper, bytesToHex(event.print.txid)); + // console.log(event.print); + ins.add(bytesToHex(event.print.txid)); + } else if (isInitiateOutboundEvent(event)) { + console.log('start', event.print.swapId); + outs.add(event.print.swapId); + } else if (isFinalizeInboundEvent(event)) { + // console.log('end', bytesToHex(event.print.txid)); + ins.delete(bytesToHex(event.print.txid)); + } else if (isFinalizeOutboundEvent(event)) { + // console.log('end', event.print.swapId); + outs.delete(event.print.swapId); + console.log('finalized', event.print.swapId); + } else if (isRevokeInboundEvent(event)) { + ins.delete(bytesToHex(event.print.txid)); + } else if (isRevokeOutboundEvent(event)) { + outs.delete(event.print.swapId); + } + }); + + ins.forEach(txid => { + console.log(txid); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + // const event = events.find(e => (e as any).print.txid === hexToBytes(txid)); + const event = events.find(e => { + if (isEscrowEvent(e)) { + console.log(bytesToHex(e.print.txid)); + return bytesToHex(e.print.txid) === txid; + } + return false; + }); + console.log(event); + }); + + console.log(ins); + console.log(outs); + + return; + } + const eventsJSON = await readFile(dataFile, { encoding: 'utf-8' }); + const events = JSON.parse(eventsJSON, reviver) as Event[]; + console.log('Found events', events.length); + let totalInbound = 0n; + let totalOutbound = 0n; + let totalFees = 0n; + let swapsCount = 0; + + events.forEach(event => { + if (isEscrowEvent(event)) { + totalInbound += event.print.sats; + totalFees += event.print.sats - event.print.xbtc; + swapsCount += 1; + } else if (isInitiateOutboundEvent(event)) { + totalOutbound += event.print.xbtc; + totalFees += event.print.xbtc - event.print.sats; + swapsCount += 1; + } + }); + + const total = totalInbound + totalOutbound; + + console.log('Total volume:', satsToBtc(total)); + console.log('Total inbound:', satsToBtc(totalInbound)); + console.log('Total outbound:', satsToBtc(totalOutbound)); + console.log('Total fees:', satsToBtc(totalFees)); + const avgFee = Number(totalFees) / swapsCount; + console.log('Average fee:', satsToBtc(avgFee)); + console.log('Average fee %:', ((Number(totalFees) / Number(total)) * 100).toFixed(3)); + console.log('Total swaps:', swapsCount); + // console.log('Avg fee:', ((swapsCount / Number(totalFees)) * 100).toFixed(3)); + + // // const api = getQueryApi(); + + // // if (api) { + // // const lines = await api.collectLines('SELECT "ping" FROM "testPing" limit 1'); + // // console.log(lines.length, 'lines'); + // // } + + // // events.forEach(event => { + // // if (isFinalizeInboundEvent(event)) { + // // event. + // // } + // // // event. + // // }); + // } +} + +run() + .catch(console.error) + .finally(() => { + process.exit(); + }); From d064d84d75aabfe2d29a0cb3e32657696ff24137 Mon Sep 17 00:00:00 2001 From: dumbledope <84159538+dumbledope@users.noreply.github.com> Date: Thu, 15 Sep 2022 13:21:22 -0700 Subject: [PATCH 3/9] chore: repeat balances job every 5 mins --- src/worker/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/worker/index.ts b/src/worker/index.ts index c436369..86e99da 100644 --- a/src/worker/index.ts +++ b/src/worker/index.ts @@ -111,5 +111,5 @@ export function initWorkerThread() { void finalizeOutboundQueue.add({}, { repeat: { every: 120_000 } }); void eventCronQueue.add({}, { repeat: { every: 120_000 } }); - void balancesQueue.add({}, { repeat: { every: 60_000 } }); + void balancesQueue.add({}, { repeat: { every: 300_000 } }); } From d6c43ef92b9ef3f227e24fd0d3fe695f9975127e Mon Sep 17 00:00:00 2001 From: dumbledope <84159538+dumbledope@users.noreply.github.com> Date: Fri, 7 Oct 2022 10:13:02 -0700 Subject: [PATCH 4/9] chore: move helper fns for serializing events --- .gitignore | 1 + scripts/analytics.ts | 30 ++++-------------------------- scripts/helpers.ts | 43 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 47 insertions(+), 27 deletions(-) diff --git a/.gitignore b/.gitignore index 0ed5883..1bb5e8e 100644 --- a/.gitignore +++ b/.gitignore @@ -40,5 +40,6 @@ yarn-error.log* settings/Testnet.toml scripts/migrate-redis.ts +scripts/accounting.ts tmpdata \ No newline at end of file diff --git a/scripts/analytics.ts b/scripts/analytics.ts index 37481ef..cb9a2a3 100644 --- a/scripts/analytics.ts +++ b/scripts/analytics.ts @@ -1,6 +1,5 @@ import 'cross-fetch/polyfill'; import { getContractEventsUntil } from '../src/stacks-api'; -import { getQueryApi } from '../src/metrics'; import { writeFile, readFile } from 'fs/promises'; import { isFinalizeInboundEvent, @@ -15,45 +14,24 @@ import { import { resolve } from 'path'; import { satsToBtc } from '../src/utils'; import { bytesToHex, hexToBytes } from 'micro-stacks/common'; +import { parseEventsJSON, stringifyEvents } from './helpers'; const [command] = process.argv.slice(2); -function replacer(key: string, value: any): any { - if (typeof value === 'bigint') { - return { __bigintval__: value.toString() }; - } else if (value instanceof Uint8Array) { - console.log('is buff'); - return { __buff__: bytesToHex(value) }; - } - return value; -} - -function reviver(key: string, value: any): any { - if (value != null && typeof value === 'object' && '__bigintval__' in value) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - return BigInt(value['__bigintval__']); - } - if (value != null && typeof value === 'object' && '__buff__' in value) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - return hexToBytes(value['__buff__']); - } - return value; -} - async function run() { const dataFile = resolve(__dirname, '../tmpdata/events.json'); // const totalInbound = if (command === 'write') { const events = await getContractEventsUntil(null); console.log(`Found ${events.length} events`); - const json = JSON.stringify(events, replacer); + const json = stringifyEvents(events); await writeFile(dataFile, json, { encoding: 'utf-8', }); return; } else if (command === 'pending') { const eventsJSON = await readFile(dataFile, { encoding: 'utf-8' }); - const events = JSON.parse(eventsJSON, reviver) as Event[]; + const events = parseEventsJSON(eventsJSON); events.reverse(); // const ins: string[] = []; @@ -101,7 +79,7 @@ async function run() { return; } const eventsJSON = await readFile(dataFile, { encoding: 'utf-8' }); - const events = JSON.parse(eventsJSON, reviver) as Event[]; + const events = parseEventsJSON(eventsJSON); console.log('Found events', events.length); let totalInbound = 0n; let totalOutbound = 0n; diff --git a/scripts/helpers.ts b/scripts/helpers.ts index 251202d..a787325 100644 --- a/scripts/helpers.ts +++ b/scripts/helpers.ts @@ -7,7 +7,18 @@ import { stacksProvider } from '../src/stacks'; import { getTxUrl, stxToUstx, ustxToStx } from '../src/utils'; import { getStxBalance } from '../src/wallet'; import { getStxNetwork, getStxPrivateKey } from '../src/config'; -import { IntegerType } from 'micro-stacks/common'; +import { bytesToHex, hexToBytes, IntegerType } from 'micro-stacks/common'; +import { + isFinalizeInboundEvent, + Prints, + Event, + isEscrowEvent, + isInitiateOutboundEvent, + isFinalizeOutboundEvent, + isRevokeInboundEvent, + isRevokeOutboundEvent, +} from '../src/events'; +import { resolve } from 'path'; type UnknownTx = ContractCallTyped[], unknown>; @@ -81,3 +92,33 @@ export async function getFeeEstimate(tx: UnknownTx, options: Partial[]) { + return JSON.stringify(events, replacer); +} + +export function parseEventsJSON(json: string) { + const events = JSON.parse(json, reviver) as Event[]; + return events; +} From 701c579fe6bcced02ee96753059961daa093222d Mon Sep 17 00:00:00 2001 From: dumbledope <84159538+dumbledope@users.noreply.github.com> Date: Fri, 7 Oct 2022 10:14:47 -0700 Subject: [PATCH 5/9] feat: isNullish & isNotNullish helper fn --- src/events.ts | 3 ++- src/stacks-api.ts | 4 ++-- src/store.ts | 3 ++- src/utils.ts | 10 +++++++++- src/wallet.ts | 4 ++-- test/utils.test.ts | 15 +++++++++++++++ 6 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 test/utils.test.ts diff --git a/src/events.ts b/src/events.ts index 00c7c11..c916b30 100644 --- a/src/events.ts +++ b/src/events.ts @@ -1,6 +1,7 @@ import { contracts } from './clarigen/next'; import { hexToCvValue, TypedAbiArg, TypedAbiFunction } from '@clarigen/core'; import { ApiEvent, getTransactionEvent } from './stacks-api'; +import { isNullish } from './utils'; type ResponseType = T extends TypedAbiFunction[], infer R> ? R @@ -91,7 +92,7 @@ export const isRevokeOutboundEvent = (val: Event): val is Event(prints: Prints[], topic: T['topic']): T { const [found] = prints.filter(p => p.topic === topic); - if (typeof found === 'undefined') { + if (isNullish(found)) { throw new Error(`No print with topic '${topic}'`); } return found as T; diff --git a/src/stacks-api.ts b/src/stacks-api.ts index 61caf4e..f77b691 100644 --- a/src/stacks-api.ts +++ b/src/stacks-api.ts @@ -19,7 +19,7 @@ import { } from 'micro-stacks/api'; import ElectrumClient from 'electrum-client-sl'; import { logger } from './logger'; -import { getTxUrl } from './utils'; +import { getTxUrl, isNotNullish } from './utils'; import { bridgeContract } from './stacks'; import { CoreNodeEventType, filterEvents, hexToCvValue, SmartContractEvent } from '@clarigen/core'; import { Prints, Event, getPrintFromRawEvent } from './events'; @@ -79,7 +79,7 @@ export async function findStacksBlockAtHeight( electrumClient.blockchain_block_header(height), getStacksHeight(height), ]); - if (typeof stacksHeight !== 'undefined') { + if (isNotNullish(stacksHeight)) { return { header, prevBlocks, diff --git a/src/store.ts b/src/store.ts index 7c0b236..c142c6f 100644 --- a/src/store.ts +++ b/src/store.ts @@ -1,5 +1,6 @@ import Redis, { Redis as RedisClient } from 'ioredis'; import { getNetworkKey } from './config'; +import { isNullish } from './utils'; export type { Redis as RedisClient } from 'ioredis'; export enum RedisKeys { @@ -22,7 +23,7 @@ export function workerKeyPrefix() { export function getRedisUrl() { const url = process.env.REDIS_URL || process.env.REDISTOGO_URL; - if (typeof url === 'undefined') { + if (isNullish(url)) { return 'redis://127.0.0.1:6379'; } return url; diff --git a/src/utils.ts b/src/utils.ts index 2e12acc..edf6b37 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -51,7 +51,7 @@ export function bpsToPercent(bps: IntegerType) { export function satsToBtc(sats: IntegerType, minDecimals?: number) { const n = new BigNumber(intToString(sats)).shiftedBy(-8).decimalPlaces(8); - if (typeof minDecimals === 'undefined') return n.toFormat(); + if (isNullish(minDecimals)) return n.toFormat(); const rounded = n.toFormat(minDecimals); const normal = n.toFormat(); return rounded.length > normal.length ? rounded : normal; @@ -102,3 +102,11 @@ export function isRevokedTxid(txId: string | Uint8Array) { const txidString = typeof txId === 'string' ? txId : bytesToHex(txId); return txidString === '00'; } + +export function isNotNullish(value?: T): value is NonNullable { + return typeof value !== 'undefined' && value !== null; +} + +export function isNullish(value?: T | null): value is null | undefined { + return typeof value === 'undefined' || value === null; +} diff --git a/src/wallet.ts b/src/wallet.ts index 09e428f..d1420cb 100644 --- a/src/wallet.ts +++ b/src/wallet.ts @@ -1,5 +1,5 @@ import ElectrumClient, { Unspent } from 'electrum-client-sl'; -import { btcToSats, getBtcTxUrl, getScriptHash, satsToBtc, shiftInt } from './utils'; +import { btcToSats, getBtcTxUrl, getScriptHash, isNotNullish, shiftInt } from './utils'; import { getBtcPayment, getBtcNetwork, @@ -136,7 +136,7 @@ export async function sendBtc(opts: SendBtc) { const final = psbt.extractTransaction(); const hex = hexToBytes(final.toHex()); - if (typeof opts.maxSize !== 'undefined' && hex.length > opts.maxSize) { + if (isNotNullish(opts.maxSize) && hex.length > opts.maxSize) { logger.error( { topic: 'btcTxSize', diff --git a/test/utils.test.ts b/test/utils.test.ts new file mode 100644 index 0000000..9deb8da --- /dev/null +++ b/test/utils.test.ts @@ -0,0 +1,15 @@ +import { isNotNullish, isNullish } from '../src/utils'; + +test('isNullish', () => { + expect(isNullish(undefined)).toEqual(true); + expect(isNullish(null)).toEqual(true); + expect(isNullish(false)).toEqual(false); + expect(isNullish(0)).toEqual(false); +}); + +test('isNullish', () => { + expect(isNotNullish(undefined)).toEqual(false); + expect(isNotNullish(null)).toEqual(false); + expect(isNotNullish(false)).toEqual(true); + expect(isNotNullish(0)).toEqual(true); +}); From be1f44e911f02a8dd1e08f50fc18da08b1049965 Mon Sep 17 00:00:00 2001 From: dumbledope <84159538+dumbledope@users.noreply.github.com> Date: Fri, 7 Oct 2022 10:26:30 -0700 Subject: [PATCH 6/9] fix: remove all unused imports --- .eslintrc.js | 20 +++++++++++++------- package.json | 1 + scripts/analytics.ts | 4 +--- scripts/consolidate.ts | 2 +- scripts/finalize-inbound.ts | 5 +---- scripts/helpers.ts | 12 +----------- scripts/register-supplier.ts | 4 +--- scripts/remove-funds.ts | 2 +- src/config.ts | 1 - src/events.ts | 2 +- src/processors/outbound.ts | 2 +- src/processors/redeem-htlc.ts | 2 +- src/stacks-api.ts | 5 +---- src/wallet.ts | 4 ++-- src/worker/jobs.ts | 11 +---------- src/worker/queues.ts | 4 ++-- yarn.lock | 12 ++++++++++++ 17 files changed, 41 insertions(+), 52 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 0f79a01..bd0c541 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,23 +1,29 @@ module.exports = { - extends: ["@stacks/eslint-config"], + extends: ['@stacks/eslint-config'], settings: { react: { - version: "999.999.999", + version: '999.999.999', }, }, + plugins: ['unused-imports'], parserOptions: { - project: "tsconfig.json", + project: 'tsconfig.json', }, rules: { - "@typescript-eslint/no-unused-vars": [0], - "@typescript-eslint/explicit-module-boundary-types": [0], - "@typescript-eslint/no-non-null-assertion": [0], - "@typescript-eslint/strict-boolean-expressions": [ + '@typescript-eslint/no-unused-vars': [0], + '@typescript-eslint/explicit-module-boundary-types': [0], + '@typescript-eslint/no-non-null-assertion': [0], + '@typescript-eslint/strict-boolean-expressions': [ 2, { allowNullableString: true, allowNullableBoolean: true, }, ], + 'unused-imports/no-unused-imports': 'error', + 'unused-imports/no-unused-vars': [ + 'warn', + { vars: 'all', varsIgnorePattern: '^_', args: 'after-used', argsIgnorePattern: '^_' }, + ], }, }; diff --git a/package.json b/package.json index 3b8c042..b6cbf4c 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "app.json": "^1.3.0", "eslint": "7", "eslint-plugin-prettier": "^3.1.4", + "eslint-plugin-unused-imports": "2.0.0", "jest": "^27.0.3", "jest-fetch-mock": "3.0.3", "nodemon": "^2.0.15", diff --git a/scripts/analytics.ts b/scripts/analytics.ts index cb9a2a3..b6a633e 100644 --- a/scripts/analytics.ts +++ b/scripts/analytics.ts @@ -3,8 +3,6 @@ import { getContractEventsUntil } from '../src/stacks-api'; import { writeFile, readFile } from 'fs/promises'; import { isFinalizeInboundEvent, - Prints, - Event, isEscrowEvent, isInitiateOutboundEvent, isFinalizeOutboundEvent, @@ -13,7 +11,7 @@ import { } from '../src/events'; import { resolve } from 'path'; import { satsToBtc } from '../src/utils'; -import { bytesToHex, hexToBytes } from 'micro-stacks/common'; +import { bytesToHex } from 'micro-stacks/common'; import { parseEventsJSON, stringifyEvents } from './helpers'; const [command] = process.argv.slice(2); diff --git a/scripts/consolidate.ts b/scripts/consolidate.ts index 9bf385b..4361083 100644 --- a/scripts/consolidate.ts +++ b/scripts/consolidate.ts @@ -1,7 +1,7 @@ import { getFeeRate, listUnspent, txWeight, withElectrumClient } from '../src/wallet'; import { Psbt } from 'bitcoinjs-lib'; import { getBtcPayment, getBtcNetwork, getBtcSigner } from '../src/config'; -import { btcToSats, getBtcTxUrl, satsToBtc } from '../src/utils'; +import { getBtcTxUrl, satsToBtc } from '../src/utils'; import { prompt } from 'inquirer'; async function run() { diff --git a/scripts/finalize-inbound.ts b/scripts/finalize-inbound.ts index d2c08d5..5db4818 100644 --- a/scripts/finalize-inbound.ts +++ b/scripts/finalize-inbound.ts @@ -1,11 +1,8 @@ import 'cross-fetch/polyfill'; import { hexToBytes } from 'micro-stacks/common'; -import { FinalizeInboundPrint } from '../src/events'; -import { processFinalizedInbound, redeem } from '../src/processors/redeem-htlc'; +import { redeem } from '../src/processors/redeem-htlc'; import { bridgeContract, stacksProvider } from '../src/stacks'; -import { createRedisClient } from '../src/store'; import { getBtcTxUrl } from '../src/utils'; -import { deserializeJob } from '../src/worker/jobs'; const [txidHex] = process.argv.slice(2); diff --git a/scripts/helpers.ts b/scripts/helpers.ts index a787325..49db2fd 100644 --- a/scripts/helpers.ts +++ b/scripts/helpers.ts @@ -8,17 +8,7 @@ import { getTxUrl, stxToUstx, ustxToStx } from '../src/utils'; import { getStxBalance } from '../src/wallet'; import { getStxNetwork, getStxPrivateKey } from '../src/config'; import { bytesToHex, hexToBytes, IntegerType } from 'micro-stacks/common'; -import { - isFinalizeInboundEvent, - Prints, - Event, - isEscrowEvent, - isInitiateOutboundEvent, - isFinalizeOutboundEvent, - isRevokeInboundEvent, - isRevokeOutboundEvent, -} from '../src/events'; -import { resolve } from 'path'; +import { Prints, Event } from '../src/events'; type UnknownTx = ContractCallTyped[], unknown>; diff --git a/scripts/register-supplier.ts b/scripts/register-supplier.ts index 714d60e..663891f 100644 --- a/scripts/register-supplier.ts +++ b/scripts/register-supplier.ts @@ -1,7 +1,7 @@ import { prompt } from 'inquirer'; import 'cross-fetch/polyfill'; import { stacksProvider, bridgeContract } from '../src/stacks'; -import { bpsToPercent, btcToSats, satsToBtc, shiftInt, stxToUstx } from '../src/utils'; +import { bpsToPercent, btcToSats, satsToBtc } from '../src/utils'; import { getBtcAddress, getNetworkKey, @@ -13,8 +13,6 @@ import { import { PostConditionMode } from 'micro-stacks/transactions'; import BigNumber from 'bignumber.js'; import { getBalances } from '../src/wallet'; -import { AnchorMode } from 'micro-stacks/transactions'; -import { bytesToHex } from 'micro-stacks/common'; import { askStxFee, broadcastAndLog } from './helpers'; interface Answers { diff --git a/scripts/remove-funds.ts b/scripts/remove-funds.ts index 0875ae4..dc5af2c 100644 --- a/scripts/remove-funds.ts +++ b/scripts/remove-funds.ts @@ -2,7 +2,7 @@ import { prompt } from 'inquirer'; import { PostConditionMode } from 'micro-stacks/transactions'; import { getSupplierId } from '../src/config'; import { stacksProvider, bridgeContract } from '../src/stacks'; -import { btcToSats, satsToBtc, shiftInt } from '../src/utils'; +import { btcToSats, satsToBtc } from '../src/utils'; import { getStxBalance } from '../src/wallet'; import { askStxFee, broadcastAndLog } from './helpers'; diff --git a/src/config.ts b/src/config.ts index ed24d00..7cd05c3 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,7 +1,6 @@ import { ECPair, networks, payments } from 'bitcoinjs-lib'; import { StacksNetworkVersion } from 'micro-stacks/crypto'; import { StacksMainnet, StacksMocknet, StacksNetwork, StacksTestnet } from 'micro-stacks/network'; -import { getPublicKey as _getPublicKey } from 'noble-secp256k1'; import { logger } from './logger'; import { bridgeContract, stacksProvider } from './stacks'; import { makeStxAddress } from './utils'; diff --git a/src/events.ts b/src/events.ts index c916b30..431cef7 100644 --- a/src/events.ts +++ b/src/events.ts @@ -149,7 +149,7 @@ export function getPrintFromRawEvent(event: ApiEvent): Event | nu export async function deserializeEvent(eventData: SerializedEvent): Promise> { const { index, txid } = eventData; - const apiEvent = await getTransactionEvent(eventData.txid, eventData.index); + const apiEvent = await getTransactionEvent(txid, index); const event = getPrintFromRawEvent(apiEvent); if (event === null) throw new Error('Invalid event'); return event; diff --git a/src/processors/outbound.ts b/src/processors/outbound.ts index 8a096de..1c92732 100644 --- a/src/processors/outbound.ts +++ b/src/processors/outbound.ts @@ -1,5 +1,5 @@ import { networks, payments } from 'bitcoinjs-lib'; -import { bytesToBigInt, bytesToHex } from 'micro-stacks/common'; +import { bytesToBigInt } from 'micro-stacks/common'; import { getBtcNetwork, getSupplierId } from '../config'; import { getSentOutbound, diff --git a/src/processors/redeem-htlc.ts b/src/processors/redeem-htlc.ts index 99cebf8..47b1c96 100644 --- a/src/processors/redeem-htlc.ts +++ b/src/processors/redeem-htlc.ts @@ -1,5 +1,5 @@ import { getBtcAddress, getBtcNetwork, getBtcSigner, getSupplierId } from '../config'; -import { networks, Psbt, script as bScript, payments, opcodes } from 'bitcoinjs-lib'; +import { Psbt, script as bScript, payments, opcodes } from 'bitcoinjs-lib'; import { getRedeemedHTLC, setRedeemedHTLC, RedisClient } from '../store'; import { logger as _logger } from '../logger'; import { getFeeRate, tryBroadcast, withElectrumClient } from '../wallet'; diff --git a/src/stacks-api.ts b/src/stacks-api.ts index f77b691..fde8541 100644 --- a/src/stacks-api.ts +++ b/src/stacks-api.ts @@ -4,7 +4,6 @@ import { AddressNonces, MempoolTransaction, Transaction, - TransactionEvent, } from '@stacks/stacks-blockchain-api-types'; import { getStxNetwork } from './config'; import { @@ -21,9 +20,7 @@ import ElectrumClient from 'electrum-client-sl'; import { logger } from './logger'; import { getTxUrl, isNotNullish } from './utils'; import { bridgeContract } from './stacks'; -import { CoreNodeEventType, filterEvents, hexToCvValue, SmartContractEvent } from '@clarigen/core'; -import { Prints, Event, getPrintFromRawEvent } from './events'; -import { cvToValue } from 'micro-stacks/clarity'; +import { Event, getPrintFromRawEvent } from './events'; export async function getStacksBlock( hash: string diff --git a/src/wallet.ts b/src/wallet.ts index d1420cb..ba93aa1 100644 --- a/src/wallet.ts +++ b/src/wallet.ts @@ -9,12 +9,12 @@ import { getStxAddress, getSupplierId, } from './config'; -import { payments, Psbt, Transaction } from 'bitcoinjs-lib'; +import { Psbt, Transaction } from 'bitcoinjs-lib'; import { logger } from './logger'; import { fetchAccountBalances } from 'micro-stacks/api'; import { bridgeContract, stacksProvider, xbtcAssetId } from './stacks'; import BigNumber from 'bignumber.js'; -import { bytesToHex, hexToBytes } from 'micro-stacks/common'; +import { hexToBytes } from 'micro-stacks/common'; export const electrumClient = () => { const envConfig = getElectrumConfig(); diff --git a/src/worker/jobs.ts b/src/worker/jobs.ts index fcbb1a7..8dfc708 100644 --- a/src/worker/jobs.ts +++ b/src/worker/jobs.ts @@ -1,5 +1,3 @@ -import { Job } from 'bull'; -import { EventJob } from './queues'; import { isFinalizeInboundEvent, isInitiateOutboundEvent, @@ -8,14 +6,7 @@ import { serializeEvent, Event, } from '../events'; -import { - eventCronQueue, - eventQueue, - finalizeInboundQueue, - finalizeOutboundQueue, - sendOutboundQueue, - balancesQueue, -} from './queues'; +import { eventQueue, finalizeInboundQueue, sendOutboundQueue } from './queues'; import { getLastSeenTxid, RedisClient, setLastSeenTxid } from '../store'; import { fetchCoreInfo, getContractEventsUntil } from '../stacks-api'; import { logger } from '../logger'; diff --git a/src/worker/queues.ts b/src/worker/queues.ts index 253b1c2..1c54886 100644 --- a/src/worker/queues.ts +++ b/src/worker/queues.ts @@ -1,5 +1,5 @@ -import Queue, { Queue as QueueType, QueueOptions } from 'bull'; -import { Event, FinalizeInboundPrint, InitiateOutboundPrint, SerializedEvent } from '../events'; +import Queue, { QueueOptions } from 'bull'; +import { FinalizeInboundPrint, InitiateOutboundPrint, SerializedEvent } from '../events'; import { createWorkerRedisClient } from '../store'; import Redis from 'ioredis'; diff --git a/yarn.lock b/yarn.lock index 2e8bce5..5e56620 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2999,6 +2999,18 @@ eslint-plugin-prettier@^3.1.4, eslint-plugin-prettier@^3.4.0: dependencies: prettier-linter-helpers "^1.0.0" +eslint-plugin-unused-imports@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-2.0.0.tgz#d8db8c4d0cfa0637a8b51ce3fd7d1b6bc3f08520" + integrity sha512-3APeS/tQlTrFa167ThtP0Zm0vctjr4M44HMpeg1P4bK6wItarumq0Ma82xorMKdFsWpphQBlRPzw/pxiVELX1A== + dependencies: + eslint-rule-composer "^0.3.0" + +eslint-rule-composer@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9" + integrity sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg== + eslint-scope@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" From 8d7413b492b4b9ec93bb55f32e9418e3697a1927 Mon Sep 17 00:00:00 2001 From: dumbledope <84159538+dumbledope@users.noreply.github.com> Date: Fri, 7 Oct 2022 10:30:20 -0700 Subject: [PATCH 7/9] fix: remove unused vars --- .eslintrc.js | 2 +- scripts/register-supplier.ts | 3 +-- scripts/test-events.ts | 2 +- src/processors/redeem-htlc.ts | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index bd0c541..ce47eac 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -22,7 +22,7 @@ module.exports = { ], 'unused-imports/no-unused-imports': 'error', 'unused-imports/no-unused-vars': [ - 'warn', + 'error', { vars: 'all', varsIgnorePattern: '^_', args: 'after-used', argsIgnorePattern: '^_' }, ], }, diff --git a/scripts/register-supplier.ts b/scripts/register-supplier.ts index 663891f..19afb47 100644 --- a/scripts/register-supplier.ts +++ b/scripts/register-supplier.ts @@ -1,6 +1,6 @@ import { prompt } from 'inquirer'; import 'cross-fetch/polyfill'; -import { stacksProvider, bridgeContract } from '../src/stacks'; +import { bridgeContract } from '../src/stacks'; import { bpsToPercent, btcToSats, satsToBtc } from '../src/utils'; import { getBtcAddress, @@ -26,7 +26,6 @@ interface Answers { } async function run() { - const provider = stacksProvider(); const bridge = bridgeContract(); try { diff --git a/scripts/test-events.ts b/scripts/test-events.ts index 200c4ec..d2ca399 100644 --- a/scripts/test-events.ts +++ b/scripts/test-events.ts @@ -10,7 +10,7 @@ async function run() { if (e.tx_id !== f.tx_id || e.event_index !== f.event_index) { throw new Error('Mismatch'); } - const event = getPrintFromRawEvent(f); + getPrintFromRawEvent(f); }) ); } diff --git a/src/processors/redeem-htlc.ts b/src/processors/redeem-htlc.ts index 47b1c96..e3174d6 100644 --- a/src/processors/redeem-htlc.ts +++ b/src/processors/redeem-htlc.ts @@ -13,7 +13,7 @@ const logger = _logger.child({ topic: 'redeemHTLC' }); export async function processFinalizedInbound(event: Event, client: RedisClient) { const { print } = event; if (!isFinalizeInboundPrint(print)) return; - const { preimage, supplier } = print; + const { preimage } = print; if (print.supplier !== BigInt(getSupplierId())) return; const txidHex = bytesToHex(print.txid); const l = logger.child({ From 1af9c4ccad29c190775ed2d493c9337c30f878a6 Mon Sep 17 00:00:00 2001 From: dumbledope <84159538+dumbledope@users.noreply.github.com> Date: Fri, 7 Oct 2022 10:56:13 -0700 Subject: [PATCH 8/9] chore: changeset --- .changeset/lemon-gifts-check.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .changeset/lemon-gifts-check.md diff --git a/.changeset/lemon-gifts-check.md b/.changeset/lemon-gifts-check.md new file mode 100644 index 0000000..e9426f0 --- /dev/null +++ b/.changeset/lemon-gifts-check.md @@ -0,0 +1,13 @@ +--- +'magic-supplier': patch +--- + +Better backoff and retry logic for some worker jobs: + +- The balances job runs less often (now every 5 minutes) +- The `finalize` jobs retry every 10 minutes, and retry for more instances, to account for expected block times and confirmation delays + +Various small fixes regarding code quality: + +- Instead of checking for `undefined` with `typeof val === 'undefined'`, added `isNullish` and `isNotNullish` helpers that check against `null` and `undefined` +- Removed unused imports and variables From ee3bb6ffeb9f684d42d70abaafceb955321a4c8b Mon Sep 17 00:00:00 2001 From: dumbledope <84159538+dumbledope@users.noreply.github.com> Date: Thu, 15 Dec 2022 13:15:26 -0800 Subject: [PATCH 9/9] feat: terms of service --- tos.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 tos.md diff --git a/tos.md b/tos.md new file mode 100644 index 0000000..a868823 --- /dev/null +++ b/tos.md @@ -0,0 +1,23 @@ +The following terms (the “Terms”) apply to you (“you” or “your”) as a user of the Code that is made available by the copyright holder and its contributors (“Copyright Holder”, “our” or “we”). “Code” shall mean any software code that is distributed as “free software” or “open-source software” or is otherwise distributed publicly in source code form under terms that permit modification and redistribution of such software. By using/continuing to use our Code, you acknowledge you have read and understand and agree to be bound by the Terms, including those additional terms and conditions and policies referenced herein and/or available by hyperlink. + +Intellectual Property Rights + +You may use, distribute and modify this Code under the terms of GNU General Public License Version 2, June 1991 found here https://github.com/magicstx/magic-protocol/blob/main/LICENSE + +Disclaimers; Limitations of Liability + +YOU EXPRESSLY AGREE THAT ACCESS TO AND USE OF THE CODE IS AT YOUR SOLE RISK AND IS PROVIDED ON AN “AS IS” AND “AS AVAILABLE” BASIS WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF TITLE OR IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. WITHOUT LIMITING THE FOREGOING, NEITHER COPYRIGHT HOLDER NOR ITS AFFILIATES OR SUBSIDIARIES, OR ANY OF THEIR DIRECTORS, OFFICERS, EMPLOYEES, CONTRACTORS, AGENTS, ATTORNEYS, THIRD-PARTY PROVIDERS, DISTRIBUTORS, LICENSEES, LICENSORS, SUCCESSORS OR ASSIGNS (COLLECTIVELY, THE “COPYRIGHT HOLDER PARTIES”) WARRANT THAT THE CODE WILL BE UNINTERRUPTED, SECURE, BUG-FREE OR ERROR-FREE, AND NONE OF THE COPYRIGHT HOLDER PARTIES WARRANT THAT THE CODE IS MERCHANTABLE, FIT FOR ANY PARTICULAR PURPOSE. The entire risk as to the quality and performance of the code is with you. Should the code prove defective, you assume the cost of all necessary servicing, repair or correction. + +TO THE FULLEST EXTENT PERMITTED BY LAW, THE DISCLAIMERS OF LIABILITY CONTAINED HEREIN APPLY TO ANY AND ALL DAMAGES, LOSSES AND/OR INJURY WHATSOEVER CAUSED BY OR RELATED TO USE OF, OR INABILITY TO USE, THE CODE UNDER ANY CAUSE OR ACTION WHATSOEVER OF ANY JURISDICTION, INCLUDING, WITHOUT LIMITATION, ACTIONS FOR BREACH OF WARRANTY, BREACH OF CONTRACT AND/OR TORT (INCLUDING NEGLIGENCE). THE COPYRIGHT HOLDER PARTIES SHALL NOT BE LIABLE FOR ANY LOSS, INCLUDING, BUT NOT LIMITED TO, LOST PROFITS, REVENUES, OR FINANCIAL LOSSES OR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE AND/OR CONSEQUENTIAL DAMAGES IN ANY WAY WHATSOEVER ARISING OUT OF THE USE OF, OR INABILITY TO USE, THE CODE, OR FOR ANY DAMAGES RELATED TO THE LOSS OF REVENUE, LOSS OF PROFITS, LOSS OF BUSINESS, LOSS OF USE, LOSS OF GOODWILL OR LOSS OF DATA, AND WHETHER CAUSED BY TORT (INCLUDING NEGLIGENCE), BREACH OF CONTRACT OR OTHERWISE, EVEN IF FORESEEABLE AND EVEN IF THE COPYRIGHT HOLDER PARTIES HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. YOU FURTHER SPECIFICALLY ACKNOWLEDGE THAT THE COPYRIGHT HOLDER PARTIES ARE NOT LIABLE, AND YOU AGREE NOT TO SEEK TO HOLD THE COPYRIGHT HOLDER PARTIES LIABLE, FOR THE CONDUCT OF THIRD PARTIES, INCLUDING OTHER USERS OF THE CODE, AND THAT THE RISK OF THE USE OF THE CODE AND OF INJURY FROM THE FOREGOING RESTS ENTIRELY WITH YOU. + +IN THE EVENT THAT A COURT AND/OR ARBITRATOR(S) OF COMPETENT JURISDICTION HOLDS THAT ANY OF THE COPYRIGHT HOLDER PARTIES IS LIABLE TO YOU (FOR EXAMPLE AND WITHOUT LIMITATION, BECAUSE ANY RELEASE OR WAIVER HEREUNDER IS FOUND TO BE VOID OR OTHERWISE UNENFORCEABLE, OR BECAUSE ANY CLAIMS ARE FOUND TO BE OUTSIDE THE SCOPE OF ANY SUCH RELEASE OR WAIVER), UNDER NO CIRCUMSTANCES WILL ANY OF THE COPYRIGHT HOLDER PARTIES BE LIABLE TO YOU IN THE AGGREGATE FOR MORE THAN ONE HUNDRED DOLLARS ($100), WHETHER SUCH LIABILITY IS BASED ON BREACH OF WARRANTY, BREACH OF CONTRACT OR TORT (INCLUDING NEGLIGENCE) OR OTHERWISE. You agree that the provisions in this section (DISCLAIMERS; LIMITATIONS OF LIABILITY) will survive any termination of your use of or access to the code. + +Indemnification + +You agree to indemnify, defend and hold the Copyright Holder Parties harmless from and against any and all losses, claims, damages, judgments, demands, actions, proceedings, investigations (whether formal or informal), or expenses (including reasonable attorneys’ fees), or threats thereof, due to, arising out of or relating to (a) your violation of (i) any law or (ii) the rights of a third-party, or (b) your use of the Code. + +In the event of such a claim, suit, or action, we will attempt to provide you notice of the claim, suit, or action at the contact information we have for your account/on file (provided, that failure to deliver such notice shall not eliminate or reduce your indemnification obligations hereunder). The Copyright Holder Parties reserve the right, at their own cost, to assume the exclusive defense and control of any matter otherwise subject to indemnification by you, in which event you will fully cooperate with the Copyright Holder Parties in asserting any available defenses. You agree that the provisions in this Section (Indemnification) will survive any termination of your use of or access to the code. + +Miscellaneous + +We reserve the right, at our sole discretion, to update, change, modify, or replace any part of these Terms by posting updates and changes. It is your responsibility to check regularly for changes to these Terms. Your continued use of or access to the Code following the posting of any changes to these Terms constitutes acceptance of those changes. These Terms constitute the complete and exclusive agreement and understanding between you and us related to the Code. These Terms and all disputes arising out of or relating to the Terms shall be governed by, construed, and enforced in accordance with the laws of the State of Delaware in the United States, without regard to its conflict of laws principles. These disputes will be resolved exclusively in the federal and state courts in the State of Delaware, and you and we consent to personal jurisdiction in those courts.