Skip to content

Commit

Permalink
refactor(node): stronger types for account paths
Browse files Browse the repository at this point in the history
  • Loading branch information
justmoon committed Nov 21, 2023
1 parent 12976cb commit f01f042
Show file tree
Hide file tree
Showing 24 changed files with 180 additions and 78 deletions.
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
import { Reactor, createComputed } from "@dassie/lib-reactive"

import { OwnerLedgerIdSignal } from "../signals/owner-ledger-id"
import { LedgerStore } from "../stores/ledger"
import { PostedTransfersTopic } from "../topics/posted-transfers"

export const TotalOwnerBalanceSignal = (reactor: Reactor) =>
createComputed(reactor, () => {
const ledger = reactor.use(LedgerStore)
const ownerLedgerIdSignal = reactor.use(OwnerLedgerIdSignal)

let balance = 0n
for (const account of ledger.getAccounts("builtin/owner/")) {
for (const account of ledger.getAccounts(
`${ownerLedgerIdSignal.read()}:owner/`,
)) {
balance += account.creditsPosted - account.debitsPosted
}

reactor.use(PostedTransfersTopic).on(reactor, (transfer) => {
const ownerAccountPrefix = `${ownerLedgerIdSignal.read()}:owner/`

let newBalance = balance
if (transfer.creditAccount.startsWith("builtin/owner/")) {
if (transfer.creditAccount.startsWith(ownerAccountPrefix)) {
newBalance += transfer.amount
}

if (transfer.debitAccount.startsWith("builtin/owner/")) {
if (transfer.debitAccount.startsWith(ownerAccountPrefix)) {
newBalance -= transfer.amount
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { CreateTransferParameters } from "../stores/ledger"
import { AccountPath, ConnectorAccount } from "../types/accounts"
import { getLedgerIdFromPath } from "./get-ledger-id-from-path"

export const applyPacketPrepareToLedger = (
sourceAccountPath: string,
destinationAccountPath: string,
sourceAccountPath: AccountPath,
destinationAccountPath: AccountPath,
amount: bigint,
) => {
const transfers = []

{
const ledgerId = getLedgerIdFromPath(sourceAccountPath)
const connectorPath = `${ledgerId}/internal/connector`
const connectorPath: ConnectorAccount = `${ledgerId}:internal/connector`

const transfer: CreateTransferParameters = {
debitAccountPath: sourceAccountPath,
Expand All @@ -23,7 +24,7 @@ export const applyPacketPrepareToLedger = (
}
{
const ledgerId = getLedgerIdFromPath(destinationAccountPath)
const connectorPath = `${ledgerId}/internal/connector`
const connectorPath: ConnectorAccount = `${ledgerId}:internal/connector`

const transfer: CreateTransferParameters = {
debitAccountPath: connectorPath,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import assert from "node:assert"

export const getLedgerIdFromPath = (path: string) => {
const firstSlash = path.indexOf("/")
import { AccountPath } from "../types/accounts"
import { LedgerId } from "../types/ledger-id"

assert(firstSlash !== -1, "account paths must contain at least one slash")
export const getLedgerIdFromPath = (path: AccountPath): LedgerId => {
const colonPosition = path.indexOf(":")

return path.slice(0, firstSlash)
assert(colonPosition !== -1, "account paths must contain a colon")

return path.slice(0, colonPosition) as LedgerId
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { SettlementSchemeId } from "../../peer-protocol/types/settlement-scheme-id"
import { Ledger } from "../stores/ledger"
import { LedgerId } from "../types/ledger-id"

export const initializeCommonAccounts = (
ledger: Ledger,
settlementSchemeId: SettlementSchemeId,
ledgerId: LedgerId,
) => {
ledger.createAccount(`${settlementSchemeId}/internal/connector`)
ledger.createAccount(`${ledgerId}:internal/connector`)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,28 @@ import assert from "node:assert"
import { isFailure } from "@dassie/lib-type-utils"

import { NodeId } from "../../peer-protocol/types/node-id"
import { SettlementSchemeId } from "../../peer-protocol/types/settlement-scheme-id"
import { Ledger } from "../stores/ledger"
import { LedgerId } from "../types/ledger-id"

const DEFAULT_CREDIT = 1_100_000_000n

export const initializePeer = (
ledger: Ledger,
settlementSchemeId: SettlementSchemeId,
ledgerId: LedgerId,
peerId: NodeId,
) => {
ledger.createAccount(`${settlementSchemeId}/peer/${peerId}/interledger`, {
ledger.createAccount(`${ledgerId}:peer/${peerId}/interledger`, {
limit: "debits_must_not_exceed_credits",
})
ledger.createAccount(`${settlementSchemeId}/peer/${peerId}/settlement`)
ledger.createAccount(`${settlementSchemeId}/peer/${peerId}/trust`, {
ledger.createAccount(`${ledgerId}:peer/${peerId}/settlement`)
ledger.createAccount(`${ledgerId}:peer/${peerId}/trust`, {
limit: "credits_must_not_exceed_debits",
})

// Extend initial trust limit
const result = ledger.createTransfer({
debitAccountPath: `${settlementSchemeId}/peer/${peerId}/trust`,
creditAccountPath: `${settlementSchemeId}/peer/${peerId}/interledger`,
debitAccountPath: `${ledgerId}:peer/${peerId}/trust`,
creditAccountPath: `${ledgerId}:peer/${peerId}/interledger`,
amount: DEFAULT_CREDIT,
})

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import { NodeId } from "../../peer-protocol/types/node-id"
import { CreateTransferParameters, Ledger } from "../stores/ledger"
import {
PeerInterledgerAccount,
PeerSettlementAccount,
} from "../types/accounts"
import { LedgerId } from "../types/ledger-id"

export const processSettlementPrepare = (
ledger: Ledger,
settlementSchemeId: string,
ledgerId: LedgerId,
peerId: NodeId,
amount: bigint,
direction: "incoming" | "outgoing",
) => {
const peerPath = `${settlementSchemeId}/peer/${peerId}/interledger`
const settlementPath = `${settlementSchemeId}/peer/${peerId}/settlement`
const peerPath: PeerInterledgerAccount = `${ledgerId}:peer/${peerId}/interledger`
const settlementPath: PeerSettlementAccount = `${ledgerId}:peer/${peerId}/settlement`

const transfer: CreateTransferParameters = {
debitAccountPath: direction === "incoming" ? settlementPath : peerPath,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { createActor } from "@dassie/lib-reactive"

import { SettlementSchemeId } from "../peer-protocol/types/settlement-scheme-id"
import { initializeCommonAccounts } from "./functions/manage-common-accounts"
import { OwnerLedgerIdSignal } from "./signals/owner-ledger-id"
import { LedgerStore } from "./stores/ledger"

export const ManageBuiltinAccountsActor = () =>
createActor((sig) => {
const ledger = sig.reactor.use(LedgerStore)
const ledgerId = sig.read(OwnerLedgerIdSignal)

initializeCommonAccounts(ledger, "builtin" as SettlementSchemeId)
ledger.createAccount(`builtin/owner/spsp`)
ledger.createAccount(`builtin/owner/btp`)
ledger.createAccount(`builtin/owner/http`)
initializeCommonAccounts(ledger, ledgerId)
ledger.createAccount(`${ledgerId}:owner/spsp`)
ledger.createAccount(`${ledgerId}:owner/btp`)
ledger.createAccount(`${ledgerId}:owner/http`)
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { createSignal } from "@dassie/lib-reactive"

import { LedgerId } from "../types/ledger-id"

/**
* Determines which internal ledger the owner accounts are on.
*
* @remarks
*
* Dassie aims for multi-currency support but in this current version, the owner accounts are only on one ledger. This
* signal determines which ledger that is.
*
* Owner accounts are the accounts that are used when the node's owner sends or receives money or connects a client via
* BTP etc.
*/
export const OwnerLedgerIdSignal = () => createSignal("stub" as LedgerId)
28 changes: 7 additions & 21 deletions packages/app-node/src/backend/accounting/stores/ledger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,7 @@ import { EXCEEDS_DEBITS_FAILURE } from "../failures/exceeds-debits"
import InvalidAccountFailure from "../failures/invalid-account"
import { getLedgerIdFromPath } from "../functions/get-ledger-id-from-path"
import { PostedTransfersTopic } from "../topics/posted-transfers"

// Ledger Account Structure
// ------------------------
//
// internal/… -> Own accounts
// internal/connector -> Connector account
// peer/… -> Peer accounts
// peer/<nodeId> -> Accounts for a specific peer
// peer/<nodeId>/interledger -> Interledger packets
// peer/<nodeId>/settlement -> Underlying settlement account
// peer/<nodeId>/trust -> Trust extended or revoked
// owner/… -> Owner accounts
// owner/spsp/<spspAccountId> -> SPSP accounts
// owner/btp/<btpAccountId> -> BTP accounts
// owner/http/<httpAccountId> -> ILP-HTTP accounts
import { AccountPath } from "../types/accounts"

export interface LedgerAccount {
path: string
Expand All @@ -41,14 +27,14 @@ export interface LedgerAccount {

export interface Transfer {
state: "pending" | "posted" | "voided"
debitAccount: string
creditAccount: string
debitAccount: AccountPath
creditAccount: AccountPath
amount: bigint
}

export interface CreateTransferParameters {
debitAccountPath: string
creditAccountPath: string
debitAccountPath: AccountPath
creditAccountPath: AccountPath
amount: bigint
pending?: boolean
}
Expand All @@ -61,7 +47,7 @@ export const LedgerStore = (reactor: Reactor) => {

return {
createAccount: (
path: string,
path: AccountPath,
options: Simplify<
Pick<SetOptional<LedgerAccount, keyof LedgerAccount>, "limit">
> = {},
Expand All @@ -82,7 +68,7 @@ export const LedgerStore = (reactor: Reactor) => {
ledger.set(path, account)
},

getAccount: (path: string) => ledger.get(path),
getAccount: (path: AccountPath) => ledger.get(path),

getAccounts: (filterPrefix: string) => ledger.filterPrefix(filterPrefix),

Expand Down
36 changes: 36 additions & 0 deletions packages/app-node/src/backend/accounting/types/accounts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { NodeId } from "../../peer-protocol/types/node-id"
import { LedgerId } from "./ledger-id"

// Ledger Account Structure
// ------------------------
//
// internal/… -> Own accounts
// internal/connector -> Connector account
// peer/… -> Peer accounts
// peer/<nodeId> -> Accounts for a specific peer
// peer/<nodeId>/interledger -> Interledger packets
// peer/<nodeId>/settlement -> Underlying settlement account
// peer/<nodeId>/trust -> Trust extended or revoked
// owner/… -> Owner accounts
// owner/spsp/<spspAccountId> -> SPSP accounts
// owner/btp/<btpAccountId> -> BTP accounts
// owner/http/<httpAccountId> -> ILP-HTTP accounts

export type ConnectorAccount = `${LedgerId}:internal/connector`

export type PeerInterledgerAccount = `${LedgerId}:peer/${NodeId}/interledger`
export type PeerSettlementAccount = `${LedgerId}:peer/${NodeId}/settlement`
export type PeerTrustAccount = `${LedgerId}:peer/${NodeId}/trust`

export type OwnerSpspAccount = `${LedgerId}:owner/spsp`
export type OwnerBtpAccount = `${LedgerId}:owner/btp`
export type OwnerHttpAccount = `${LedgerId}:owner/http`

export type AccountPath =
| ConnectorAccount
| PeerInterledgerAccount
| PeerSettlementAccount
| PeerTrustAccount
| OwnerSpspAccount
| OwnerBtpAccount
| OwnerHttpAccount
3 changes: 3 additions & 0 deletions packages/app-node/src/backend/accounting/types/ledger-id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Opaque } from "type-fest"

export type LedgerId = Opaque<string, "LedgerId">
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import { Reactor, createActor } from "@dassie/lib-reactive"
import { isFailure } from "@dassie/lib-type-utils"

import { OwnerLedgerIdSignal } from "../accounting/signals/owner-ledger-id"
import { BtpTokensStore } from "../api-keys/database-stores/btp-tokens"
import { BtpToken } from "../api-keys/types/btp-token"
import { WebsocketRoutesSignal } from "../http-server/serve-https"
Expand All @@ -27,6 +28,7 @@ export const RegisterBtpHttpUpgradeActor = (reactor: Reactor) => {
const processIncomingPacketActor = reactor.use(ProcessPacketActor)
const btpTokensStore = reactor.use(BtpTokensStore)
const routingTableSignal = reactor.use(RoutingTableSignal)
const ownerLedgerIdSignal = reactor.use(OwnerLedgerIdSignal)

return createActor((sig) => {
const nodeIlpAddress = sig.readAndTrack(NodeIlpAddressSignal)
Expand All @@ -52,7 +54,7 @@ export const RegisterBtpHttpUpgradeActor = (reactor: Reactor) => {
type: "btp",
connectionId,
ilpAddress: `${nodeIlpAddress}.${localIlpAddressPart}`,
accountPath: "builtin/owner/btp",
accountPath: `${ownerLedgerIdSignal.read()}:owner/btp`,
}

logger.debug("handle BTP websocket connection", { connectionId })
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createActor } from "@dassie/lib-reactive"
import { isFailure } from "@dassie/lib-type-utils"

import { AccountPath } from "../accounting/types/accounts"
import { ProcessPacketActor } from "../ilp-connector/process-packet"
import {
IlpType,
Expand All @@ -18,20 +19,21 @@ export interface IldcpRequestParameters {
requestId: number
}

const ILDCP_DESTINATION_INFO: IldcpEndpointInfo = {
type: "ildcp",
ilpAddress: ILDCP_ADDRESS,
accountPath: `internal/ildcp`,
}

export const HandleIldcpRequestsActor = () =>
createActor((sig) => {
const ilpRoutingTable = sig.readAndTrack(RoutingTableSignal)
const processIncomingPacketActor = sig.reactor.use(ProcessPacketActor)

const ildcpDestinationInfo: IldcpEndpointInfo = {
type: "ildcp",
ilpAddress: ILDCP_ADDRESS,
// ILDCP cannot send or receive money so we set the account path to an impossible value
accountPath: "unreachable" as AccountPath,
}

ilpRoutingTable.set(ILDCP_ADDRESS, {
type: "fixed",
destination: ILDCP_DESTINATION_INFO,
destination: ildcpDestinationInfo,
})

sig.onCleanup(() => {
Expand Down Expand Up @@ -65,7 +67,7 @@ export const HandleIldcpRequestsActor = () =>
})

processIncomingPacketActor.api.handle.tell({
sourceEndpointInfo: ILDCP_DESTINATION_INFO,
sourceEndpointInfo: ildcpDestinationInfo,
serializedPacket: serializeIlpPacket(responsePacket),
parsedPacket: responsePacket,
requestId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Reactor } from "@dassie/lib-reactive"

import { AccountPath } from "../../accounting/types/accounts"
import { BtpEndpointInfo, SendBtpPackets } from "../senders/send-btp-packets"
import {
IldcpEndpointInfo,
Expand All @@ -18,7 +19,7 @@ import { PreparedIlpPacketEvent } from "../topics/prepared-ilp-packet"
import { ResolvedIlpPacketEvent } from "../topics/resolved-ilp-packet"

export interface CommonEndpointInfo {
readonly accountPath: string
readonly accountPath: AccountPath
readonly ilpAddress: string
}

Expand Down
Loading

0 comments on commit f01f042

Please sign in to comment.