Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ transactions also refresh on new blocks rather than on a timer. A newly observed
tip resets pending block template polling and delays the next request by 15
seconds so the electrs cache can refresh.

While an unconfirmed transaction page is focused, mempool summary and fee
estimates refresh on the standard cadence to keep its ETA, mempool depth, and
fee analysis current. The transaction confirmation status does not use another
fixed-rate poll: each newly observed tip requests `/tx/:txid/status`. Once the
transaction is confirmed, those unconfirmed-transaction refreshes stop and the
containing block metadata is loaded for the block details shown below the
transaction.

The dashboard requests both `/mempool/recent` and `/mempool` because they serve
different UI contracts. `/mempool/recent` supplies the recent transaction list;
`/mempool` supplies aggregate backlog fields such as transaction count, virtual
Expand Down
18 changes: 17 additions & 1 deletion client/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,16 @@ export default function main(
).startWith(null).scan((S, mod) => mod(S))

// Single TX
, tx$ = reply('tx').merge(goTx$.mapTo(null)).startWith(null)
, tx$ = O.merge(
reply('tx').map(tx => _ => tx),
reply('tx-status', true).map(r => tx =>
tx && tx.txid == r.request.txid && !tx.status.confirmed && r.body.confirmed
? { ...tx, status: r.body }
: tx),
page$.mapTo(_ => null)
).startWith(_ => null)
.scan((tx, update) => update(tx), null)
.distinctUntilChanged()
, txBlock$ = reply('tx-block').merge(goTx$.mapTo(null)).startWith(null)

// Predecessor metadata for confirmed block interval calculations
Expand Down Expand Up @@ -633,6 +642,13 @@ export default function main(
// fetch single tx (including confirmation status)
, goTx$.map(txid => ({ category: 'tx', method: 'GET', path: `/tx/${txid}` }))

// Refresh unconfirmed transactions on observed tip-height changes,
// reusing the existing tip poll.
, !pollingEnabled ? O.empty() : subsequentTipHeight$
.withLatestFrom(view$, tx$, (_, view, tx) => ({ view, tx }))
.filter(({ view, tx }) => view == 'tx' && tx && tx.status && !tx.status.confirmed && hasFocus())
.map(({ tx }) => ({ category: 'tx-status', method: 'GET', path: `/tx/${tx.txid}/status`, txid: tx.txid, bg: true }))

// fetch the block containing a confirmed tx
, tx$.filter(tx => tx && tx.status && tx.status.confirmed && tx.status.block_hash)
.map(tx => ({ category: 'tx-block', method: 'GET', path: `/block/${tx.status.block_hash}` }))
Expand Down
4 changes: 2 additions & 2 deletions client/src/components/status-badge.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ export const StatusBadge = (
</span>
);

export const StatusDot = () => (
<span className="confirmation-status-dot" aria-hidden="true">
export const StatusDot = ({ key } = {}) => (
<span key={key} className="confirmation-status-dot" aria-hidden="true">
<span className="confirmation-status-dot-back"></span>
<span className="confirmation-status-dot-middle"></span>
<span className="confirmation-status-dot-front"></span>
Expand Down
19 changes: 12 additions & 7 deletions client/src/views/tx.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export default ({
return layout(
[
<div className="tx-page">
{txHeader(tx, { t, tipHeight, ...S })}
{txHeader(tx, { ...S, block, t, tipHeight })}
{unblinded && unblinded.error && (
<div className="transaction-warning text-danger mt-3">
{t`Warning:`} {unblinded.error.toString()}
Expand Down Expand Up @@ -231,6 +231,7 @@ const btnDetailsContent = (isOpen, t) => (
const txHeader = (
tx,
{
block,
tipHeight,
feeEst,
t,
Expand All @@ -251,10 +252,12 @@ const txHeader = (
: confEstimate == -1
? t`Unknown`
: `~${Math.ceil(confEstimate * targetBlockIntervalSeconds / 60)} min`;
const confirmationTime =
isConfirmed && Number.isFinite(tx.status.block_time)
? formatTime(tx.status.block_time)
: "N/A";
const blockTime = isConfirmed && Number.isFinite(tx.status.block_time)
? tx.status.block_time
: block && block.timestamp;
const confirmationTime = Number.isFinite(blockTime)
? formatTime(blockTime)
: "N/A";
const segwitSavings = segwitGainsView(segwitGains, t);

return (
Expand All @@ -279,9 +282,11 @@ const txHeader = (
</button>
<StatusBadge variant={isConfirmed ? "success" : "warning"}>
{!isConfirmed ? (
<StatusDot />
<StatusDot key="transaction-confirmation-dot" />
) : null}
<span>{confirmationText(tx.status, tipHeight, t)}</span>
<span key="transaction-confirmation-label">
{confirmationText(tx.status, tipHeight, t)}
</span>
</StatusBadge>
</div>
<div className="info-stats-row">
Expand Down
Loading
Loading