Skip to content

Commit

Permalink
fix: tx pagination (#3530)
Browse files Browse the repository at this point in the history
- remove pending txs from transactions query
- properly paginate transactions
  • Loading branch information
UncleSamtoshi authored Nov 30, 2023
1 parent 11fbcf0 commit eecbbd3
Show file tree
Hide file tree
Showing 34 changed files with 512 additions and 613 deletions.
18 changes: 9 additions & 9 deletions bats/core/api/onchain-receive.bats
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,15 @@ setup_file() {
[[ "${on_chain_address_created_1}" != "null" ]] || exit 1

bitcoin_cli sendtoaddress "$on_chain_address_created_1" "$amount"
retry 15 1 check_for_broadcast 'alice' "$on_chain_address_created_1" 1
retry 15 1 check_for_incoming_broadcast 'alice' "$on_chain_address_created_1"

# Create address and broadcast transaction 2
exec_graphql 'alice' 'on-chain-address-create' "$variables"
on_chain_address_created_2="$(graphql_output '.data.onChainAddressCreate.address')"
[[ "${on_chain_address_created_2}" != "null" ]] || exit 1

bitcoin_cli sendtoaddress "$on_chain_address_created_2" "$amount"
retry 15 1 check_for_broadcast 'alice' "$on_chain_address_created_2" 1
retry 15 1 check_for_incoming_broadcast 'alice' "$on_chain_address_created_2"

# Check pending transactions for address 1
address_1_pending_txns_variables=$(
Expand Down Expand Up @@ -224,7 +224,7 @@ setup_file() {

# Execute onchain send and check for transaction
bitcoin_cli sendtoaddress "$on_chain_address_created" "$amount"
retry 15 1 check_for_broadcast 'alice' "$on_chain_address_created" 1
retry 15 1 check_for_incoming_broadcast 'alice' "$on_chain_address_created"

# Check pending transactions for address
address_pending_txns_variables=$(
Expand Down Expand Up @@ -315,9 +315,9 @@ setup_file() {
tx_hex=$(bitcoin_cli finalizepsbt "$signed_psbt" | jq -r '.hex')
txid=$(bitcoin_cli sendrawtransaction "$tx_hex")

retry 15 1 check_for_broadcast 'alice' "$alice_address_1" 2
retry 3 1 check_for_broadcast 'alice' "$alice_address_2" 2
retry 3 1 check_for_broadcast 'bob' "$bob_address_1" 1
retry 15 1 check_for_incoming_broadcast 'alice' "$alice_address_1"
retry 3 1 check_for_incoming_broadcast 'alice' "$alice_address_2"
retry 3 1 check_for_incoming_broadcast 'bob' "$bob_address_1"

# Check 'pendingIncomingBalance' query
exec_graphql 'alice' 'wallets-for-account'
Expand Down Expand Up @@ -406,9 +406,9 @@ setup_file() {
tx_hex=$(bitcoin_cli finalizepsbt "$signed_psbt" | jq -r '.hex')
txid=$(bitcoin_cli sendrawtransaction "$tx_hex")

retry 45 1 check_for_broadcast 'alice' "$alice_btc_address" 10
retry 3 1 check_for_broadcast 'alice' "$alice_usd_address" 10
retry 3 1 check_for_broadcast 'bob' "$bob_btc_address" 10
retry 45 1 check_for_incoming_broadcast 'alice' "$alice_btc_address"
retry 3 1 check_for_incoming_broadcast 'alice' "$alice_usd_address"
retry 3 1 check_for_incoming_broadcast 'bob' "$bob_btc_address"

# Mine transactions
# Note: subscription event operates in a delayed way from lnd1 state
Expand Down
8 changes: 4 additions & 4 deletions bats/core/api/onchain-send.bats
Original file line number Diff line number Diff line change
Expand Up @@ -319,10 +319,10 @@ grep_in_trigger_logs() {
# ----------

# Check for broadcast of last send
retry 15 1 check_for_broadcast 'alice' "$on_chain_payment_send_all_address" 4
retry 3 1 check_for_broadcast 'alice' "$on_chain_usd_payment_send_as_btc_denominated_address" 4
retry 3 1 check_for_broadcast 'alice' "$on_chain_usd_payment_send_address" 4
retry 3 1 check_for_broadcast 'alice' "$on_chain_payment_send_address" 4
retry 15 1 check_for_outgoing_broadcast 'alice' "$on_chain_payment_send_all_address" 4
retry 3 1 check_for_outgoing_broadcast 'alice' "$on_chain_usd_payment_send_as_btc_denominated_address" 4
retry 3 1 check_for_outgoing_broadcast 'alice' "$on_chain_usd_payment_send_address" 4
retry 3 1 check_for_outgoing_broadcast 'alice' "$on_chain_payment_send_address" 4

# Mine all
bitcoin_cli -generate 2
Expand Down
27 changes: 26 additions & 1 deletion bats/helpers/onchain.bash
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,32 @@ get_from_transaction_by_address() {
| jq -r "$property_query"
}

check_for_broadcast() {
get_from_pending_transaction_by_address() {
property_query=$2

jq_query='.data.me.defaultAccount.pendingIncomingTransactions[] | select(.initiationVia.address == $address)'
echo $output \
| jq -r --arg address "$1" "$jq_query" \
| jq -r "$property_query"
}

check_for_incoming_broadcast() {
local token_name=$1
local address=$2

exec_graphql "$token_name" 'pending-incoming-transactions'

tx="$(get_from_pending_transaction_by_address "$address" '.')"
[[ -n "${tx}" && "${tx}" != "null" ]] || exit 1
txid="$(echo $tx | jq -r '.settlementVia.transactionHash')"
[[ "${txid}" != "null" ]] || exit 1
status="$(echo $tx | jq -r '.status')"
[[ "${status}" == "PENDING" ]] || exit 1

bitcoin_cli gettransaction "$txid" || exit 1
}

check_for_outgoing_broadcast() {
local token_name=$1
local address=$2
local first=${3:-"1"}
Expand Down
39 changes: 28 additions & 11 deletions core/api/src/app/accounts/get-account-transactions-for-contact.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { memoSharingConfig } from "@/config"
import { MAX_PAGINATION_PAGE_SIZE, memoSharingConfig } from "@/config"
import { LedgerError } from "@/domain/ledger"
import { checkedToPaginatedQueryArgs } from "@/domain/primitives"
import { WalletTransactionHistory } from "@/domain/wallets"

import { getNonEndUserWalletIds, LedgerService } from "@/services/ledger"
Expand All @@ -8,29 +9,45 @@ import { WalletsRepository } from "@/services/mongoose"
export const getAccountTransactionsForContact = async ({
account,
contactUsername,
paginationArgs,
rawPaginationArgs,
}: {
account: Account
contactUsername: Username
paginationArgs?: PaginationArgs
}): Promise<PaginatedArray<WalletTransaction> | ApplicationError> => {
rawPaginationArgs: RawPaginationArgs
}): Promise<PaginatedQueryResult<WalletTransaction> | ApplicationError> => {
const paginationArgs = checkedToPaginatedQueryArgs({
paginationArgs: rawPaginationArgs,
maxPageSize: MAX_PAGINATION_PAGE_SIZE,
})

if (paginationArgs instanceof Error) return paginationArgs

const ledger = LedgerService()

const wallets = await WalletsRepository().listByAccountId(account.id)
if (wallets instanceof Error) return wallets

const resp = await ledger.getTransactionsByWalletIdAndContactUsername({
const ledgerTxs = await ledger.getTransactionsByWalletIdAndContactUsername({
walletIds: wallets.map((wallet) => wallet.id),
contactUsername,
paginationArgs,
})
if (resp instanceof LedgerError) return resp
if (ledgerTxs instanceof LedgerError) return ledgerTxs

const nonEndUserWalletIds = Object.values(await getNonEndUserWalletIds())

const txEdges = ledgerTxs.edges.map((edge) => {
const transaction = WalletTransactionHistory.fromLedger({
txn: edge.node,
nonEndUserWalletIds,
memoSharingConfig,
})

const confirmedHistory = WalletTransactionHistory.fromLedger({
ledgerTransactions: resp.slice,
nonEndUserWalletIds: Object.values(await getNonEndUserWalletIds()),
memoSharingConfig,
return {
cursor: edge.cursor,
node: transaction,
}
})

return { slice: confirmedHistory.transactions, total: resp.total }
return { ...ledgerTxs, edges: txEdges }
}
7 changes: 1 addition & 6 deletions core/api/src/app/accounts/get-invoices-for-account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,7 @@ export const getInvoicesForAccountByWalletIds = async ({
}: {
account: Account
walletIds?: WalletId[]
rawPaginationArgs: {
first?: number | null
last?: number | null
before?: string | null
after?: string | null
}
rawPaginationArgs: RawPaginationArgs
}): Promise<PaginatedQueryResult<WalletInvoice> | ApplicationError> => {
const walletsRepo = WalletsRepository()

Expand Down
35 changes: 20 additions & 15 deletions core/api/src/app/accounts/get-transactions-for-account.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,39 @@
import { getTransactionsForWallets } from "../wallets"

import { PartialResult } from "../partial-result"

import { AccountValidator } from "@/domain/accounts"
import { RepositoryError } from "@/domain/errors"
import { WalletsRepository } from "@/services/mongoose"

export const getTransactionsForAccountByWalletIds = async ({
account,
walletIds,
paginationArgs,
rawPaginationArgs,
}: {
account: Account
walletIds: WalletId[]
paginationArgs?: PaginationArgs
}): Promise<PartialResult<PaginatedArray<WalletTransaction>>> => {
walletIds?: WalletId[]
rawPaginationArgs: RawPaginationArgs
}): Promise<PaginatedQueryResult<WalletTransaction> | ApplicationError> => {
const walletsRepo = WalletsRepository()

const wallets: Wallet[] = []
for (const walletId of walletIds) {
const wallet = await walletsRepo.findById(walletId)
if (wallet instanceof RepositoryError) return PartialResult.err(wallet)

const accountValidator = AccountValidator(account)
if (accountValidator instanceof Error) return PartialResult.err(accountValidator)
const validateWallet = accountValidator.validateWalletForAccount(wallet)
if (validateWallet instanceof Error) return PartialResult.err(validateWallet)
if (walletIds) {
for (const walletId of walletIds) {
const wallet = await walletsRepo.findById(walletId)
if (wallet instanceof RepositoryError) return wallet

const accountValidator = AccountValidator(account)
if (accountValidator instanceof Error) return accountValidator
const validateWallet = accountValidator.validateWalletForAccount(wallet)
if (validateWallet instanceof Error) return validateWallet

wallets.push(wallet)
wallets.push(wallet)
}
} else {
const accountWallets = await walletsRepo.listByAccountId(account.id)
if (accountWallets instanceof RepositoryError) return accountWallets
wallets.push(...accountWallets)
}

return getTransactionsForWallets({ wallets, paginationArgs })
return getTransactionsForWallets({ wallets, rawPaginationArgs })
}
7 changes: 7 additions & 0 deletions core/api/src/app/index.types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ type PartialResult<T> = {
partialResult: true
}

type RawPaginationArgs = {
first?: number
last?: number
before?: string
after?: string
}

type ValueOf<T> = T[keyof T]

type ApplicationErrors = typeof import("./errors").ApplicationErrors
Expand Down
7 changes: 1 addition & 6 deletions core/api/src/app/wallets/get-invoices-for-wallets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,7 @@ export const getInvoicesForWallets = async ({
rawPaginationArgs,
}: {
wallets: Wallet[]
rawPaginationArgs: {
first?: number | null
last?: number | null
before?: string | null
after?: string | null
}
rawPaginationArgs: RawPaginationArgs
}): Promise<PaginatedQueryResult<WalletInvoice> | ApplicationError> => {
const walletIds = wallets.map((wallet) => wallet.id)

Expand Down
8 changes: 4 additions & 4 deletions core/api/src/app/wallets/get-transaction-by-id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ export const getTransactionForWalletById = async ({
if (ledgerTransaction instanceof Error) return ledgerTransaction

return WalletTransactionHistory.fromLedger({
ledgerTransactions: [ledgerTransaction],
txn: ledgerTransaction,
nonEndUserWalletIds: Object.values(await getNonEndUserWalletIds()),
memoSharingConfig,
}).transactions[0]
})
}

export const getTransactionById = async (
Expand All @@ -41,8 +41,8 @@ export const getTransactionById = async (
if (ledgerTransaction instanceof Error) return ledgerTransaction

return WalletTransactionHistory.fromLedger({
ledgerTransactions: [ledgerTransaction],
txn: ledgerTransaction,
nonEndUserWalletIds: Object.values(await getNonEndUserWalletIds()),
memoSharingConfig,
}).transactions[0]
})
}
4 changes: 2 additions & 2 deletions core/api/src/app/wallets/get-transaction-by-journal-id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export const getTransactionForWalletByJournalId = async ({
if (ledgerTransaction instanceof Error) return ledgerTransaction

return WalletTransactionHistory.fromLedger({
ledgerTransactions: [ledgerTransaction],
txn: ledgerTransaction,
nonEndUserWalletIds: Object.values(await getNonEndUserWalletIds()),
memoSharingConfig,
}).transactions[0]
})
}
70 changes: 32 additions & 38 deletions core/api/src/app/wallets/get-transactions-by-addresses.ts
Original file line number Diff line number Diff line change
@@ -1,61 +1,55 @@
import { memoSharingConfig } from "@/config"
import { PartialResult } from "@/app/partial-result"
import { MAX_PAGINATION_PAGE_SIZE, memoSharingConfig } from "@/config"

import { LedgerError } from "@/domain/ledger"
import { WalletTransactionHistory } from "@/domain/wallets"
import { CouldNotFindError } from "@/domain/errors"

import { getNonEndUserWalletIds, LedgerService } from "@/services/ledger"
import { WalletOnChainPendingReceiveRepository } from "@/services/mongoose"
import { checkedToPaginatedQueryArgs } from "@/domain/primitives"

export const getTransactionsForWalletsByAddresses = async ({
wallets,
addresses,
paginationArgs,
rawPaginationArgs,
}: {
wallets: Wallet[]
addresses: OnChainAddress[]
paginationArgs?: PaginationArgs
}): Promise<PartialResult<PaginatedArray<WalletTransaction>>> => {
const walletIds = wallets.map((wallet) => wallet.id)
rawPaginationArgs: RawPaginationArgs
}): Promise<PaginatedQueryResult<WalletTransaction> | ApplicationError> => {
const paginationArgs = checkedToPaginatedQueryArgs({
paginationArgs: rawPaginationArgs,
maxPageSize: MAX_PAGINATION_PAGE_SIZE,
})

let pendingHistory =
await WalletOnChainPendingReceiveRepository().listByWalletIdsAndAddresses({
walletIds,
addresses,
})
if (pendingHistory instanceof Error) {
if (pendingHistory instanceof CouldNotFindError) {
pendingHistory = []
} else {
return PartialResult.err(pendingHistory)
}
if (paginationArgs instanceof Error) {
return paginationArgs
}

const confirmedLedgerTxns = await LedgerService().getTransactionsByWalletIds({
const walletIds = wallets.map((wallet) => wallet.id)

const ledgerTxs = await LedgerService().getTransactionsByWalletIdsAndAddresses({
walletIds,
paginationArgs,
addresses,
})
if (confirmedLedgerTxns instanceof LedgerError) {
return PartialResult.partial(
{ slice: pendingHistory, total: pendingHistory.length },
confirmedLedgerTxns,
)

if (ledgerTxs instanceof LedgerError) {
return ledgerTxs
}
const ledgerTransactions = confirmedLedgerTxns.slice.filter(
(tx) => tx.address && addresses.includes(tx.address),
)

const confirmedHistory = WalletTransactionHistory.fromLedger({
ledgerTransactions,
nonEndUserWalletIds: Object.values(await getNonEndUserWalletIds()),
memoSharingConfig,
})

const transactions = [...pendingHistory, ...confirmedHistory.transactions]
const nonEndUserWalletIds = Object.values(await getNonEndUserWalletIds())

const txEdges = ledgerTxs.edges.map((edge) => {
const transaction = WalletTransactionHistory.fromLedger({
txn: edge.node,
nonEndUserWalletIds,
memoSharingConfig,
})

return PartialResult.ok({
slice: transactions,
total: transactions.length,
return {
cursor: edge.cursor,
node: transaction,
}
})

return { ...ledgerTxs, edges: txEdges }
}
Loading

0 comments on commit eecbbd3

Please sign in to comment.