From 169a23796b82da2fc3f891ae25a8aace645d0068 Mon Sep 17 00:00:00 2001 From: jeronimoalbi <894299+jeronimoalbi@users.noreply.github.com> Date: Fri, 28 Nov 2025 20:04:48 +0100 Subject: [PATCH 1/5] feat: add support for versioning Dither protocol --- docker-compose.yml | 2 + packages/reader-main/docker-compose.yml | 8 +++- packages/reader-main/src/config/index.ts | 7 ++++ packages/reader-main/src/index.ts | 17 ++++---- packages/reader-main/src/messages/index.ts | 41 +++++++++++-------- packages/reader-main/src/messages/messages.ts | 28 +++++++++++++ .../src/messages/{ => v1}/dislike.ts | 4 +- .../reader-main/src/messages/{ => v1}/flag.ts | 4 +- .../src/messages/{ => v1}/follow.ts | 4 +- packages/reader-main/src/messages/v1/index.ts | 19 +++++++++ .../reader-main/src/messages/{ => v1}/like.ts | 4 +- .../reader-main/src/messages/{ => v1}/post.ts | 4 +- .../src/messages/{ => v1}/remove.ts | 4 +- .../src/messages/{ => v1}/reply.ts | 4 +- .../src/messages/{ => v1}/unfollow.ts | 4 +- 15 files changed, 110 insertions(+), 44 deletions(-) create mode 100644 packages/reader-main/src/messages/messages.ts rename packages/reader-main/src/messages/{ => v1}/dislike.ts (94%) rename packages/reader-main/src/messages/{ => v1}/flag.ts (94%) rename packages/reader-main/src/messages/{ => v1}/follow.ts (94%) create mode 100644 packages/reader-main/src/messages/v1/index.ts rename packages/reader-main/src/messages/{ => v1}/like.ts (94%) rename packages/reader-main/src/messages/{ => v1}/post.ts (94%) rename packages/reader-main/src/messages/{ => v1}/remove.ts (93%) rename packages/reader-main/src/messages/{ => v1}/reply.ts (94%) rename packages/reader-main/src/messages/{ => v1}/unfollow.ts (94%) diff --git a/docker-compose.yml b/docker-compose.yml index 846fda29..f8aea9fa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -72,6 +72,8 @@ services: RECEIVER: atone1uq6zjslvsa29cy6uu75y8txnl52mw06j6fzlep API_ROOT: 'http://api-main:3000/v1' AUTH: dev + COMMUNITY_ACCOUNT_V1: atone1uq6zjslvsa29cy6uu75y8txnl52mw06j6fzlep + # Uncomment to enable fast sync # ECLESIA_GRAPHQL_ENDPOINT: "https://graphql-atomone-testnet-1.allinbits.services/v1/graphql" # ECLESIA_GRAPHQL_SECRET: "" diff --git a/packages/reader-main/docker-compose.yml b/packages/reader-main/docker-compose.yml index a16b2424..7a0c96fb 100644 --- a/packages/reader-main/docker-compose.yml +++ b/packages/reader-main/docker-compose.yml @@ -6,11 +6,17 @@ services: restart: always container_name: chronosync environment: - API_URLS: 'https://atomone-api.allinbits.com,https://atomone-rest.publicnode.com' + # Mainnet + # API_URLS: 'https://atomone-rest.publicnode.com' + # + # Testnet + API_URLS: 'https://atomone-testnet-1-api.allinbits.services' + START_BLOCK: '2605764' BATCH_SIZE: 50 MEMO_PREFIX: dither. RECEIVER: atone1uq6zjslvsa29cy6uu75y8txnl52mw06j6fzlep AUTH: dev + COMMUNITY_ACCOUNT_V1: atone1uq6zjslvsa29cy6uu75y8txnl52mw06j6fzlep # LOG: process.env.LOG === 'true' ? true : false, command: [bun, run, start] diff --git a/packages/reader-main/src/config/index.ts b/packages/reader-main/src/config/index.ts index 0a1c72eb..d88cdd04 100644 --- a/packages/reader-main/src/config/index.ts +++ b/packages/reader-main/src/config/index.ts @@ -6,6 +6,7 @@ let config: Config & { AUTH: string; ECLESIA_GRAPHQL_ENDPOINT?: string; ECLESIA_GRAPHQL_SECRET?: string; + COMMUNITY_ACCOUNT_V1: string; }; export function useConfig(): typeof config { @@ -18,6 +19,10 @@ export function useConfig(): typeof config { throw new Error(`AUTH must be set to a strong secret`); } + if (!process.env.COMMUNITY_ACCOUNT_V1) { + throw new Error(`COMMUNITY_ACCOUNT_V1 value is required`); + } + config = { API_URLS: process.env.API_URLS ? process.env.API_URLS.split(',') : [], MEMO_PREFIX: process.env.MEMO_PREFIX, @@ -30,6 +35,8 @@ export function useConfig(): typeof config { ECLESIA_GRAPHQL_ENDPOINT: process.env.ECLESIA_GRAPHQL_ENDPOINT, ECLESIA_GRAPHQL_SECRET: process.env.ECLESIA_GRAPHQL_SECRET, + + COMMUNITY_ACCOUNT_V1: process.env.COMMUNITY_ACCOUNT_V1, }; return config; diff --git a/packages/reader-main/src/index.ts b/packages/reader-main/src/index.ts index c160c124..40376c9c 100644 --- a/packages/reader-main/src/index.ts +++ b/packages/reader-main/src/index.ts @@ -9,7 +9,7 @@ import { ChronoState } from '@atomone/chronostate'; import { useConfig } from './config/index'; import { EclesiaClient } from './eclesia/client'; -import { MessageHandlers } from './messages/index'; +import { messages } from './messages'; import { useQueue } from './queue'; const config = useConfig(); @@ -97,13 +97,6 @@ async function processAction(action: Action): Promise { } const actionType = match[1]; - - const actionTypeKey = actionType as keyof typeof MessageHandlers; - if (!MessageHandlers[actionTypeKey]) { - console.warn(`Skipped ${action.hash}, unknown action type: ${actionType}`); - return 'SKIP'; - } - const transfer = getTransferMessage(action.messages as unknown as Array); const quantity = getTransferQuantities(action.messages as unknown as Array); if (!transfer) { @@ -111,7 +104,13 @@ async function processAction(action: Action): Promise { return 'SKIP'; } - return await MessageHandlers[actionTypeKey]({ ...action, sender: transfer.from_address, quantity }); + const handler = messages.get(transfer.to_address, actionType); + if (!handler) { + console.warn(`Skipped ${action.hash}, unknown action type: ${actionType}`); + return 'SKIP'; + } + + return await handler({ ...action, sender: transfer.from_address, quantity }); } async function updateLastBlock(height: string, attempt = 0) { diff --git a/packages/reader-main/src/messages/index.ts b/packages/reader-main/src/messages/index.ts index 74d530d4..274c7505 100644 --- a/packages/reader-main/src/messages/index.ts +++ b/packages/reader-main/src/messages/index.ts @@ -1,19 +1,24 @@ -import { Dislike } from './dislike'; -import { Flag } from './flag'; -import { Follow } from './follow'; -import { Like } from './like'; -import { Post } from './post'; -import { Remove } from './remove'; -import { Reply } from './reply'; -import { Unfollow } from './unfollow'; +import { useConfig } from '../config'; +import { Messages } from './messages'; +import v1 from './v1'; -export const MessageHandlers = { - Dislike, - Flag, - Follow, - Like, - Post, - Remove, - Reply, - Unfollow, -}; +const config = useConfig(); + +// Messages contains message handlers for different Dither protocol versions. +// +// Versions are defined by the address of the destination account used in the +// send messages. +// +// New versions must be created when the protocol changes because new functions +// are added, or because existing function parameters change. A new version means +// that the destination account for the send transactions must be changed to a +// different one. +// +// Dither protocol is open, it uses a memo field and a send message, so anyone +// can "polute" Dither namespace sending multiple transactions using different +// non existent function names, which might be expected to be supported in the +// future. Apart from poluting the namespace they could also generate issues +// when replaying historical transactions to restore the data. +export const messages = new Messages({ + [config.COMMUNITY_ACCOUNT_V1]: v1, +}); diff --git a/packages/reader-main/src/messages/messages.ts b/packages/reader-main/src/messages/messages.ts new file mode 100644 index 00000000..be41fd79 --- /dev/null +++ b/packages/reader-main/src/messages/messages.ts @@ -0,0 +1,28 @@ +import type { ActionWithData, ResponseStatus } from '../types'; + +export type MessageHandler = (action: ActionWithData) => Promise; +export type Handlers = Record>; + +// Messages keeps track of message handlers for different versions. +export class Messages { + private handlers: Handlers; + + constructor(handlers: Handlers) { + this.handlers = handlers; + } + + // Registers messages for a specific version. + register(version: string, handlers: Record) { + this.handlers[version] = handlers; + } + + // Gets a message handler by name for a specific version. + get(version: string, name: string): MessageHandler | undefined { + return this.handlers[version]?.[name]; + } + + // Lists registered versions. + versions(): string[] { + return Object.keys(this.handlers); + } +} diff --git a/packages/reader-main/src/messages/dislike.ts b/packages/reader-main/src/messages/v1/dislike.ts similarity index 94% rename from packages/reader-main/src/messages/dislike.ts rename to packages/reader-main/src/messages/v1/dislike.ts index 15a7b78c..57eda6f8 100644 --- a/packages/reader-main/src/messages/dislike.ts +++ b/packages/reader-main/src/messages/v1/dislike.ts @@ -1,13 +1,13 @@ /* eslint-disable ts/no-namespace */ import type { Posts } from '@atomone/dither-api-types'; -import type { ActionWithData, ResponseStatus } from '../types/index'; +import type { ActionWithData, ResponseStatus } from '../../types/index'; import process from 'node:process'; import { extractMemoContent } from '@atomone/chronostate'; -import { useConfig } from '../config'; +import { useConfig } from '../../config'; declare module '@atomone/chronostate' { export namespace MemoExtractor { diff --git a/packages/reader-main/src/messages/flag.ts b/packages/reader-main/src/messages/v1/flag.ts similarity index 94% rename from packages/reader-main/src/messages/flag.ts rename to packages/reader-main/src/messages/v1/flag.ts index c29220a9..e95482fc 100644 --- a/packages/reader-main/src/messages/flag.ts +++ b/packages/reader-main/src/messages/v1/flag.ts @@ -1,13 +1,13 @@ /* eslint-disable ts/no-namespace */ import type { Posts } from '@atomone/dither-api-types'; -import type { ActionWithData, ResponseStatus } from '../types/index'; +import type { ActionWithData, ResponseStatus } from '../../types/index'; import process from 'node:process'; import { extractMemoContent } from '@atomone/chronostate'; -import { useConfig } from '../config'; +import { useConfig } from '../../config'; declare module '@atomone/chronostate' { export namespace MemoExtractor { diff --git a/packages/reader-main/src/messages/follow.ts b/packages/reader-main/src/messages/v1/follow.ts similarity index 94% rename from packages/reader-main/src/messages/follow.ts rename to packages/reader-main/src/messages/v1/follow.ts index 279bbc1d..dd866e4c 100644 --- a/packages/reader-main/src/messages/follow.ts +++ b/packages/reader-main/src/messages/v1/follow.ts @@ -1,13 +1,13 @@ /* eslint-disable ts/no-namespace */ import type { Posts } from '@atomone/dither-api-types'; -import type { ActionWithData, ResponseStatus } from '../types/index'; +import type { ActionWithData, ResponseStatus } from '../../types/index'; import process from 'node:process'; import { extractMemoContent } from '@atomone/chronostate'; -import { useConfig } from '../config/index'; +import { useConfig } from '../../config/index'; declare module '@atomone/chronostate' { export namespace MemoExtractor { diff --git a/packages/reader-main/src/messages/v1/index.ts b/packages/reader-main/src/messages/v1/index.ts new file mode 100644 index 00000000..c6f38ede --- /dev/null +++ b/packages/reader-main/src/messages/v1/index.ts @@ -0,0 +1,19 @@ +import { Dislike } from './dislike'; +import { Flag } from './flag'; +import { Follow } from './follow'; +import { Like } from './like'; +import { Post } from './post'; +import { Remove } from './remove'; +import { Reply } from './reply'; +import { Unfollow } from './unfollow'; + +export default { + Dislike, + Flag, + Follow, + Like, + Post, + Remove, + Reply, + Unfollow, +}; diff --git a/packages/reader-main/src/messages/like.ts b/packages/reader-main/src/messages/v1/like.ts similarity index 94% rename from packages/reader-main/src/messages/like.ts rename to packages/reader-main/src/messages/v1/like.ts index e83ceba2..50b6ecfa 100644 --- a/packages/reader-main/src/messages/like.ts +++ b/packages/reader-main/src/messages/v1/like.ts @@ -1,13 +1,13 @@ /* eslint-disable ts/no-namespace */ import type { Posts } from '@atomone/dither-api-types'; -import type { ActionWithData, ResponseStatus } from '../types/index'; +import type { ActionWithData, ResponseStatus } from '../../types/index'; import process from 'node:process'; import { extractMemoContent } from '@atomone/chronostate'; -import { useConfig } from '../config/index'; +import { useConfig } from '../../config/index'; declare module '@atomone/chronostate' { export namespace MemoExtractor { diff --git a/packages/reader-main/src/messages/post.ts b/packages/reader-main/src/messages/v1/post.ts similarity index 94% rename from packages/reader-main/src/messages/post.ts rename to packages/reader-main/src/messages/v1/post.ts index a9b7beb7..bd06c7dd 100644 --- a/packages/reader-main/src/messages/post.ts +++ b/packages/reader-main/src/messages/v1/post.ts @@ -1,13 +1,13 @@ /* eslint-disable ts/no-namespace */ import type { Posts } from '@atomone/dither-api-types'; -import type { ActionWithData, ResponseStatus } from '../types/index'; +import type { ActionWithData, ResponseStatus } from '../../types/index'; import process from 'node:process'; import { extractMemoContent } from '@atomone/chronostate'; -import { useConfig } from '../config/index'; +import { useConfig } from '../../config/index'; declare module '@atomone/chronostate' { export namespace MemoExtractor { diff --git a/packages/reader-main/src/messages/remove.ts b/packages/reader-main/src/messages/v1/remove.ts similarity index 93% rename from packages/reader-main/src/messages/remove.ts rename to packages/reader-main/src/messages/v1/remove.ts index c9180e00..bb806028 100644 --- a/packages/reader-main/src/messages/remove.ts +++ b/packages/reader-main/src/messages/v1/remove.ts @@ -1,13 +1,13 @@ /* eslint-disable ts/no-namespace */ import type { Posts } from '@atomone/dither-api-types'; -import type { ActionWithData, ResponseStatus } from '../types/index'; +import type { ActionWithData, ResponseStatus } from '../../types/index'; import process from 'node:process'; import { extractMemoContent } from '@atomone/chronostate'; -import { useConfig } from '../config/index'; +import { useConfig } from '../../config/index'; declare module '@atomone/chronostate' { export namespace MemoExtractor { diff --git a/packages/reader-main/src/messages/reply.ts b/packages/reader-main/src/messages/v1/reply.ts similarity index 94% rename from packages/reader-main/src/messages/reply.ts rename to packages/reader-main/src/messages/v1/reply.ts index 2ad40ab8..0cca92f7 100644 --- a/packages/reader-main/src/messages/reply.ts +++ b/packages/reader-main/src/messages/v1/reply.ts @@ -1,13 +1,13 @@ /* eslint-disable ts/no-namespace */ import type { Posts } from '@atomone/dither-api-types'; -import type { ActionWithData, ResponseStatus } from '../types/index'; +import type { ActionWithData, ResponseStatus } from '../../types/index'; import process from 'node:process'; import { extractMemoContent } from '@atomone/chronostate'; -import { useConfig } from '../config/index'; +import { useConfig } from '../../config/index'; declare module '@atomone/chronostate' { export namespace MemoExtractor { diff --git a/packages/reader-main/src/messages/unfollow.ts b/packages/reader-main/src/messages/v1/unfollow.ts similarity index 94% rename from packages/reader-main/src/messages/unfollow.ts rename to packages/reader-main/src/messages/v1/unfollow.ts index dccfa570..5a9e9381 100644 --- a/packages/reader-main/src/messages/unfollow.ts +++ b/packages/reader-main/src/messages/v1/unfollow.ts @@ -1,13 +1,13 @@ /* eslint-disable ts/no-namespace */ import type { Posts } from '@atomone/dither-api-types'; -import type { ActionWithData, ResponseStatus } from '../types/index'; +import type { ActionWithData, ResponseStatus } from '../../types/index'; import process from 'node:process'; import { extractMemoContent } from '@atomone/chronostate'; -import { useConfig } from '../config/index'; +import { useConfig } from '../../config/index'; declare module '@atomone/chronostate' { export namespace MemoExtractor { From 464f4c190572817fb3a84d11626a34dd2859bac9 Mon Sep 17 00:00:00 2001 From: jeronimoalbi <894299+jeronimoalbi@users.noreply.github.com> Date: Fri, 28 Nov 2025 20:06:07 +0100 Subject: [PATCH 2/5] chore: allow frontend requests to `allinbits.services` domain --- packages/frontend-main/public/csp.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/frontend-main/public/csp.json b/packages/frontend-main/public/csp.json index 98044a80..bedd6c64 100644 --- a/packages/frontend-main/public/csp.json +++ b/packages/frontend-main/public/csp.json @@ -26,6 +26,7 @@ "https://cloud.umami.is", "https://api-gateway.umami.dev", "https://*.dither.chat", - "https://*.allinbits.com" + "https://*.allinbits.com", + "https://*.allinbits.services" ] } From 66e5cf78ffc3480aec6b1b2d3b146d3cc4ee8dcf Mon Sep 17 00:00:00 2001 From: jeronimoalbi <894299+jeronimoalbi@users.noreply.github.com> Date: Fri, 28 Nov 2025 20:06:44 +0100 Subject: [PATCH 3/5] fix: correct issue in reader that led to always start from 1st block Explicit start block value was ignored before. --- packages/reader-main/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/reader-main/src/index.ts b/packages/reader-main/src/index.ts index 40376c9c..377c3801 100644 --- a/packages/reader-main/src/index.ts +++ b/packages/reader-main/src/index.ts @@ -215,7 +215,7 @@ export async function start() { if (Number.parseInt(config.START_BLOCK) > lastBlockStored) { console.info(`START_BLOCK is higher than last block stored, starting from START_BLOCK=${config.START_BLOCK}`); - config.START_BLOCK = lastBlockStored.toString(); + startBlock = Number.parseInt(config.START_BLOCK); } else { startBlock = lastBlockStored; } From 3723e856bc5acf914b2e8c0d89fd05f11a6b4140 Mon Sep 17 00:00:00 2001 From: jeronimoalbi <894299+jeronimoalbi@users.noreply.github.com> Date: Fri, 28 Nov 2025 20:12:57 +0100 Subject: [PATCH 4/5] chore: fix typo --- packages/reader-main/src/messages/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/reader-main/src/messages/index.ts b/packages/reader-main/src/messages/index.ts index 274c7505..a5ec2aa0 100644 --- a/packages/reader-main/src/messages/index.ts +++ b/packages/reader-main/src/messages/index.ts @@ -15,9 +15,9 @@ const config = useConfig(); // different one. // // Dither protocol is open, it uses a memo field and a send message, so anyone -// can "polute" Dither namespace sending multiple transactions using different +// can "pollute" Dither namespace sending multiple transactions using different // non existent function names, which might be expected to be supported in the -// future. Apart from poluting the namespace they could also generate issues +// future. Apart from polluting the namespace they could also generate issues // when replaying historical transactions to restore the data. export const messages = new Messages({ [config.COMMUNITY_ACCOUNT_V1]: v1, From 0e8c6725e41c1fc4c3d4d862996d17c2d9ba1fa7 Mon Sep 17 00:00:00 2001 From: jeronimoalbi <894299+jeronimoalbi@users.noreply.github.com> Date: Sat, 29 Nov 2025 12:57:20 +0100 Subject: [PATCH 5/5] chore: use named export instead of default --- packages/reader-main/src/messages/index.ts | 2 +- packages/reader-main/src/messages/v1/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/reader-main/src/messages/index.ts b/packages/reader-main/src/messages/index.ts index a5ec2aa0..4323bcb5 100644 --- a/packages/reader-main/src/messages/index.ts +++ b/packages/reader-main/src/messages/index.ts @@ -1,6 +1,6 @@ import { useConfig } from '../config'; import { Messages } from './messages'; -import v1 from './v1'; +import { v1 } from './v1'; const config = useConfig(); diff --git a/packages/reader-main/src/messages/v1/index.ts b/packages/reader-main/src/messages/v1/index.ts index c6f38ede..c0cf0978 100644 --- a/packages/reader-main/src/messages/v1/index.ts +++ b/packages/reader-main/src/messages/v1/index.ts @@ -7,7 +7,7 @@ import { Remove } from './remove'; import { Reply } from './reply'; import { Unfollow } from './unfollow'; -export default { +export const v1 = { Dislike, Flag, Follow,