diff --git a/backend/src/api/rbf-cache.ts b/backend/src/api/rbf-cache.ts
index 79d5ff2d1..a3714406f 100644
--- a/backend/src/api/rbf-cache.ts
+++ b/backend/src/api/rbf-cache.ts
@@ -55,7 +55,7 @@ class RbfCache {
if (tree) {
tree.interval = newTime - tree?.time;
replacedTrees.push(tree);
- fullRbf = fullRbf || tree.fullRbf;
+ fullRbf = fullRbf || tree.fullRbf || !tree.tx.rbf;
}
}
} else {
diff --git a/frontend/src/app/components/rbf-timeline/rbf-timeline-tooltip.component.html b/frontend/src/app/components/rbf-timeline/rbf-timeline-tooltip.component.html
index 68f8a1caf..540da7480 100644
--- a/frontend/src/app/components/rbf-timeline/rbf-timeline-tooltip.component.html
+++ b/frontend/src/app/components/rbf-timeline/rbf-timeline-tooltip.component.html
@@ -32,6 +32,7 @@
Status |
+ Full RBF
RBF
RBF
Mined
diff --git a/frontend/src/app/components/rbf-timeline/rbf-timeline-tooltip.component.ts b/frontend/src/app/components/rbf-timeline/rbf-timeline-tooltip.component.ts
index b9da63c86..fc3748f32 100644
--- a/frontend/src/app/components/rbf-timeline/rbf-timeline-tooltip.component.ts
+++ b/frontend/src/app/components/rbf-timeline/rbf-timeline-tooltip.component.ts
@@ -1,5 +1,5 @@
import { Component, ElementRef, ViewChild, Input, OnChanges } from '@angular/core';
-import { RbfInfo } from '../../interfaces/node-api.interface';
+import { RbfTree } from '../../interfaces/node-api.interface';
@Component({
selector: 'app-rbf-timeline-tooltip',
@@ -7,7 +7,7 @@ import { RbfInfo } from '../../interfaces/node-api.interface';
styleUrls: ['./rbf-timeline-tooltip.component.scss'],
})
export class RbfTimelineTooltipComponent implements OnChanges {
- @Input() rbfInfo: RbfInfo | void;
+ @Input() rbfInfo: RbfTree | null;
@Input() cursorPosition: { x: number, y: number };
tooltipPosition = null;
diff --git a/frontend/src/app/components/rbf-timeline/rbf-timeline.component.html b/frontend/src/app/components/rbf-timeline/rbf-timeline.component.html
index ce5a9678f..a2012d45f 100644
--- a/frontend/src/app/components/rbf-timeline/rbf-timeline.component.html
+++ b/frontend/src/app/components/rbf-timeline/rbf-timeline.component.html
@@ -15,14 +15,15 @@
-
+
-
+
+
-
-
+
+
diff --git a/frontend/src/app/components/rbf-timeline/rbf-timeline.component.scss b/frontend/src/app/components/rbf-timeline/rbf-timeline.component.scss
index 3745360a5..be7aef2d6 100644
--- a/frontend/src/app/components/rbf-timeline/rbf-timeline.component.scss
+++ b/frontend/src/app/components/rbf-timeline/rbf-timeline.component.scss
@@ -83,15 +83,26 @@
transform: translateY(-50%);
background: #105fb0;
border-radius: 5px;
+
+ &.left {
+ right: 50%;
+ }
+ &.right {
+ left: 50%;
+ }
+
+ &.fullrbf {
+ background: #1bd8f4;
+ }
}
&.first-node {
- .track {
- left: 50%;
+ .track.left {
+ display: none;
}
}
&:last-child {
- .track {
- right: 50%;
+ .track.right {
+ display: none;
}
}
}
@@ -177,11 +188,17 @@
height: 108px;
bottom: 50%;
border-right: solid 10px #105fb0;
+ &.fullrbf {
+ border-right: solid 10px #1bd8f4;
+ }
}
.corner {
border-bottom: solid 10px #105fb0;
border-bottom-right-radius: 10px;
+ &.fullrbf {
+ border-bottom: solid 10px #1bd8f4;
+ }
}
}
}
diff --git a/frontend/src/app/components/rbf-timeline/rbf-timeline.component.ts b/frontend/src/app/components/rbf-timeline/rbf-timeline.component.ts
index f02e8ca35..474da7326 100644
--- a/frontend/src/app/components/rbf-timeline/rbf-timeline.component.ts
+++ b/frontend/src/app/components/rbf-timeline/rbf-timeline.component.ts
@@ -1,15 +1,20 @@
import { Component, Input, OnInit, OnChanges, Inject, LOCALE_ID, HostListener } from '@angular/core';
import { Router } from '@angular/router';
-import { RbfInfo, RbfTree } from '../../interfaces/node-api.interface';
+import { RbfTree, RbfTransaction } from '../../interfaces/node-api.interface';
import { StateService } from '../../services/state.service';
import { ApiService } from '../../services/api.service';
type Connector = 'pipe' | 'corner';
interface TimelineCell {
- replacement?: RbfInfo,
+ replacement?: RbfTree,
connector?: Connector,
first?: boolean,
+ fullRbf?: boolean,
+}
+
+function isTimelineCell(val: RbfTree | TimelineCell): boolean {
+ return !val || !('tx' in val);
}
@Component({
@@ -22,7 +27,7 @@ export class RbfTimelineComponent implements OnInit, OnChanges {
@Input() txid: string;
rows: TimelineCell[][] = [];
- hoverInfo: RbfInfo | void = null;
+ hoverInfo: RbfTree | null = null;
tooltipPosition = null;
dir: 'rtl' | 'ltr' = 'ltr';
@@ -53,13 +58,27 @@ export class RbfTimelineComponent implements OnInit, OnChanges {
buildTimelines(tree: RbfTree): TimelineCell[][] {
if (!tree) return [];
+ this.flagFullRbf(tree);
const split = this.splitTimelines(tree);
const timelines = this.prepareTimelines(split);
return this.connectTimelines(timelines);
}
+ // sets the fullRbf flag on each transaction in the tree
+ flagFullRbf(tree: RbfTree): void {
+ let fullRbf = false;
+ for (const replaced of tree.replaces) {
+ if (!replaced.tx.rbf) {
+ fullRbf = true;
+ }
+ replaced.replacedBy = tree.tx;
+ this.flagFullRbf(replaced);
+ }
+ tree.tx.fullRbf = fullRbf;
+ }
+
// splits a tree into N leaf-to-root paths
- splitTimelines(tree: RbfTree, tail: RbfInfo[] = []): RbfInfo[][] {
+ splitTimelines(tree: RbfTree, tail: RbfTree[] = []): RbfTree[][] {
const replacements = [...tail, tree];
if (tree.replaces.length) {
return [].concat(...tree.replaces.map(subtree => this.splitTimelines(subtree, replacements)));
@@ -70,7 +89,7 @@ export class RbfTimelineComponent implements OnInit, OnChanges {
// merges separate leaf-to-root paths into a coherent forking timeline
// represented as a 2D array of Rbf events
- prepareTimelines(lines: RbfInfo[][]): RbfInfo[][] {
+ prepareTimelines(lines: RbfTree[][]): (RbfTree | TimelineCell)[][] {
lines.sort((a, b) => b.length - a.length);
const rows = lines.map(() => []);
@@ -85,7 +104,7 @@ export class RbfTimelineComponent implements OnInit, OnChanges {
let emptyCount = 0;
const nextGroups = [];
for (const group of lineGroups) {
- const toMerge: { [txid: string]: RbfInfo[][] } = {};
+ const toMerge: { [txid: string]: RbfTree[][] } = {};
let emptyInGroup = 0;
let first = true;
for (const line of group) {
@@ -97,7 +116,7 @@ export class RbfTimelineComponent implements OnInit, OnChanges {
} else {
// substitute duplicates with empty cells
// (we'll fill these in with connecting lines later)
- rows[index].unshift(null);
+ rows[index].unshift({ connector: true, replacement: head });
}
// group the tails of the remaining lines for the next iteration
if (line.length) {
@@ -127,7 +146,7 @@ export class RbfTimelineComponent implements OnInit, OnChanges {
}
// annotates a 2D timeline array with info needed to draw connecting lines for multi-replacements
- connectTimelines(timelines: RbfInfo[][]): TimelineCell[][] {
+ connectTimelines(timelines: (RbfTree | TimelineCell)[][]): TimelineCell[][] {
const rows: TimelineCell[][] = [];
timelines.forEach((lines, row) => {
rows.push([]);
@@ -135,11 +154,12 @@ export class RbfTimelineComponent implements OnInit, OnChanges {
let finished = false;
lines.forEach((replacement, column) => {
const cell: TimelineCell = {};
- if (replacement) {
- cell.replacement = replacement;
+ if (!isTimelineCell(replacement)) {
+ cell.replacement = replacement as RbfTree;
+ cell.fullRbf = (replacement as RbfTree).replacedBy?.fullRbf;
}
rows[row].push(cell);
- if (replacement) {
+ if (!isTimelineCell(replacement)) {
if (!started) {
cell.first = true;
started = true;
@@ -153,11 +173,13 @@ export class RbfTimelineComponent implements OnInit, OnChanges {
matched = true;
} else if (i === row) {
rows[i][column] = {
- connector: 'corner'
+ connector: 'corner',
+ fullRbf: (replacement as TimelineCell).replacement.tx.fullRbf,
};
} else if (nextCell.connector !== 'corner') {
rows[i][column] = {
- connector: 'pipe'
+ connector: 'pipe',
+ fullRbf: (replacement as TimelineCell).replacement.tx.fullRbf,
};
}
}
diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts
index 82e1ae50d..7a8ab3f06 100644
--- a/frontend/src/app/interfaces/node-api.interface.ts
+++ b/frontend/src/app/interfaces/node-api.interface.ts
@@ -39,6 +39,7 @@ export interface RbfTree extends RbfInfo {
mined?: boolean;
fullRbf: boolean;
replaces: RbfTree[];
+ replacedBy?: RbfTransaction;
}
export interface DifficultyAdjustment {
@@ -176,9 +177,10 @@ export interface TransactionStripped {
context?: 'projected' | 'actual';
}
-interface RbfTransaction extends TransactionStripped {
+export interface RbfTransaction extends TransactionStripped {
rbf?: boolean;
mined?: boolean,
+ fullRbf?: boolean,
}
export interface MempoolPosition {
block: number,
|