Skip to content

Conversation

@darioAnongba
Copy link
Contributor

This PR adds RFQ forward history tracking, allowing edge nodes to monitor and query their asset forwarding activity and fee revenue. Edge nodes need visibility into their RFQ forwarding activity to:

  • Track fee revenue earned from routing asset payments
  • Monitor forwarding volume
  • Audit historical forwarding events
  • Generate reports on routing performance

Changes

  • New rfq_forwards table to store forward events
  • QueryRfqForwards: New RPC endpoint
    • Filters: min/max timestamp, peer pubkey, asset ID, group key
    • Pagination: limit and offset support
    • Returns: forward records with joined policy data (peer, asset info)
  • tapcli rfqrpc forwards: Query forward history from CLI with filters --min-timestamp, --max-timestamp, --peer, --asset-id, --group-key, --limit, --offset

Fixes #1005

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a valuable feature for tracking RFQ forward history, complete with a new database table, RPC endpoint, and CLI command. The implementation is well-structured, spanning the database, business logic, and presentation layers.

My review includes a suggestion to improve the CLI's user experience by providing explicit error handling for mutually exclusive flags. I've also pointed out a minor style guide violation in the new test files regarding function comments.

Overall, the changes are solid and the new functionality is well-tested.

Comment on lines +127 to +152
assetIDStr := ctx.String(assetIDName)
groupKeyStr := ctx.String(groupKeyName)

if assetIDStr != "" || groupKeyStr != "" {
req.AssetSpecifier = &rfqrpc.AssetSpecifier{}

if assetIDStr != "" {
assetID, err := hex.DecodeString(assetIDStr)
if err != nil {
return fmt.Errorf("invalid asset ID hex: %w",
err)
}
req.AssetSpecifier.Id = &rfqrpc.AssetSpecifier_AssetId{
AssetId: assetID,
}
} else if groupKeyStr != "" {
groupKey, err := hex.DecodeString(groupKeyStr)
if err != nil {
return fmt.Errorf("invalid group key hex: %w",
err)
}
req.AssetSpecifier.Id = &rfqrpc.AssetSpecifier_GroupKey{
GroupKey: groupKey,
}
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current logic for handling --asset-id and --group-key flags can lead to unexpected behavior. If a user provides both flags, the --group-key is silently ignored due to the else if structure. It would be better to return an error to the user, informing them that these flags are mutually exclusive.

I suggest adding a check to handle this case explicitly.

	assetIDStr := ctx.String(assetIDName)
	groupKeyStr := ctx.String(groupKeyName)

	if assetIDStr != "" && groupKeyStr != "" {
		return fmt.Errorf("cannot specify both --%s and --%s",
			assetIDName, groupKeyName)
	}

	if assetIDStr != "" || groupKeyStr != "" {
		req.AssetSpecifier = &rfqrpc.AssetSpecifier{}

		if assetIDStr != "" {
			assetID, err := hex.DecodeString(assetIDStr)
			if err != nil {
				return fmt.Errorf("invalid asset ID hex: %w",
					err)
			}
			req.AssetSpecifier.Id = &rfqrpc.AssetSpecifier_AssetId{
				AssetId: assetID,
			}
		} else if groupKeyStr != "" {
			groupKey, err := hex.DecodeString(groupKeyStr)
			if err != nil {
				return fmt.Errorf("invalid group key hex: %w",
					err)
			}
			req.AssetSpecifier.Id = &rfqrpc.AssetSpecifier_GroupKey{
				GroupKey: groupKey,
			}
		}
	}

@darioAnongba darioAnongba moved this from 🆕 New to 👀 In review in Taproot-Assets Project Board Dec 22, 2025
@darioAnongba darioAnongba force-pushed the feat/forward-history branch 2 times, most recently from 26178d0 to 5e71beb Compare December 22, 2025 13:45
@coveralls
Copy link

coveralls commented Dec 22, 2025

Pull Request Test Coverage Report for Build 20461350246

Details

  • 429 of 823 (52.13%) changed or added relevant lines in 13 files are covered.
  • 30 unchanged lines in 11 files lost coverage.
  • Overall coverage decreased (-0.002%) to 56.96%

Changes Missing Coverage Covered Lines Changed/Added Lines %
tapdb/rfq_policies.go 4 5 80.0%
rfq/manager.go 9 13 69.23%
tapdb/sqlc/rfq.sql.go 59 67 88.06%
taprpc/rfqrpc/rfq_grpc.pb.go 17 26 65.38%
rpcserver.go 70 85 82.35%
tapdb/rfq_forwards.go 143 160 89.38%
taprpc/rfqrpc/rfq.pb.json.go 0 21 0.0%
rfq/order.go 80 107 74.77%
cmd/commands/rfq.go 0 50 0.0%
taprpc/rfqrpc/rfq.pb.gw.go 1 63 1.59%
Files with Coverage Reduction New Missed Lines %
taprpc/rfqrpc/rfq_grpc.pb.go 1 67.01%
taprpc/rfqrpc/rfq.pb.gw.go 1 2.95%
address/mock.go 2 95.11%
tapdb/universe.go 2 81.27%
asset/asset.go 3 80.94%
asset/mock.go 3 73.21%
tapchannel/aux_leaf_signer.go 3 43.53%
tapdb/assets_store.go 3 78.99%
tapdb/interfaces.go 3 80.0%
universe/archive.go 3 79.22%
Totals Coverage Status
Change from base Build 20350951079: -0.002%
Covered Lines: 65973
Relevant Lines: 115823

💛 - Coveralls

Adds a new rfq_forwards table migration with its associated
models and queries:
- Query forwards with filters
- Count forwards
- Sum forward fees
- Adds the forward store in the DB layer.
- Moves business entities from the DB layer to the RFQ layer.
- Defines Forward Store interface and entities.
On settle event from LND, log the
RFQ forward event into the DB.
with pagination and filters.
Adds a new rfq_forwards table migration with its associated
models and queries:
- Query forwards with filters
- Count forwards
- Adds the forward store in the DB layer.
- Moves business entities from the DB layer to the RFQ layer.
- Defines Forward Store interface and entities.
On settle event from LND, log the
RFQ forward event into the DB.
with pagination and filters.
Copy link
Member

@GeorgeTsagk GeorgeTsagk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Direction looking good

id INTEGER PRIMARY KEY,

-- settled_at is the unix timestamp when the forward settled.
settled_at BIGINT NOT NULL,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be going a bit too far here, but what about also storing an "opened at" field?

This way we're exposing information to the portfolio pilot that can be of essence:

  • Fast HTLCs are better than long-held HTLCs (especially for tap, where you don't want to have an open position for too long)
  • We could also even store forward events that were never settled. So if someone is locking liquidity or performing a jam attack over the channel it can be detected by observing the rfq_forwards, which potentially may never have their settled_at field set (a failed_at field would be complementary here)

asset_amt BIGINT NOT NULL,

UNIQUE(chan_id_in, htlc_id)
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like you also removed the sats in/out fields that were previously here?

even though the fee calculation was off, I think it would be nice to keep those field for sanity

Right now you can derive the msat amount that was in theory forwarded by combining asset units with asset rate, but the actual recorded amount would be nice to have (these 2 can also be off by design)

CREATE TABLE IF NOT EXISTS rfq_forwards (
id INTEGER PRIMARY KEY,

-- settled_at is the unix timestamp when the forward settled.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the following commits meant to be fixups? If so some context on the fixup commit itself is needed, as many of the changes are not self explanatory (much of the previous diff is reverted)

@lightninglabs-deploy
Copy link

@jtobin: review reminder
@ffranr: review reminder
@darioAnongba, remember to re-request review from reviewers when ready

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: 👀 In review

Development

Successfully merging this pull request may close these issues.

[feature]: Provide "forwarding history" for Edge Nodes

5 participants