Skip to content

Commit 9dd0361

Browse files
committed
graph/db+sqldb: impl PruneGraph, PruneTip, ChannelView
Which lets us run `TestGraphPruning` and `TestBatchedAddChannelEdge` against our SQL backends.
1 parent 2da701c commit 9dd0361

File tree

5 files changed

+391
-2
lines changed

5 files changed

+391
-2
lines changed

graph/db/graph_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1685,7 +1685,7 @@ func TestGraphPruning(t *testing.T) {
16851685
t.Parallel()
16861686
ctx := context.Background()
16871687

1688-
graph := MakeTestGraph(t)
1688+
graph := MakeTestGraphNew(t)
16891689

16901690
sourceNode := createTestVertex(t)
16911691
if err := graph.SetSourceNode(ctx, sourceNode); err != nil {
@@ -4006,7 +4006,7 @@ func TestBatchedAddChannelEdge(t *testing.T) {
40064006
t.Parallel()
40074007
ctx := context.Background()
40084008

4009-
graph := MakeTestGraph(t)
4009+
graph := MakeTestGraphNew(t)
40104010

40114011
sourceNode := createTestVertex(t)
40124012
require.Nil(t, graph.SetSourceNode(ctx, sourceNode))

graph/db/sql_store.go

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,14 @@ type SQLQueries interface {
9191
*/
9292
CreateChannel(ctx context.Context, arg sqlc.CreateChannelParams) (int64, error)
9393
GetChannelBySCID(ctx context.Context, arg sqlc.GetChannelBySCIDParams) (sqlc.Channel, error)
94+
GetChannelByOutpoint(ctx context.Context, outpoint string) (sqlc.GetChannelByOutpointRow, error)
9495
GetChannelBySCIDWithPolicies(ctx context.Context, arg sqlc.GetChannelBySCIDWithPoliciesParams) (sqlc.GetChannelBySCIDWithPoliciesRow, error)
9596
GetChannelAndNodesBySCID(ctx context.Context, arg sqlc.GetChannelAndNodesBySCIDParams) (sqlc.GetChannelAndNodesBySCIDRow, error)
9697
GetChannelFeaturesAndExtras(ctx context.Context, channelID int64) ([]sqlc.GetChannelFeaturesAndExtrasRow, error)
9798
HighestSCID(ctx context.Context, version int16) ([]byte, error)
9899
ListChannelsByNodeID(ctx context.Context, arg sqlc.ListChannelsByNodeIDParams) ([]sqlc.ListChannelsByNodeIDRow, error)
99100
ListChannelsWithPoliciesPaginated(ctx context.Context, arg sqlc.ListChannelsWithPoliciesPaginatedParams) ([]sqlc.ListChannelsWithPoliciesPaginatedRow, error)
101+
ListChannelsPaginated(ctx context.Context, arg sqlc.ListChannelsPaginatedParams) ([]sqlc.ListChannelsPaginatedRow, error)
100102
GetChannelsByPolicyLastUpdateRange(ctx context.Context, arg sqlc.GetChannelsByPolicyLastUpdateRangeParams) ([]sqlc.GetChannelsByPolicyLastUpdateRangeRow, error)
101103
GetChannelByOutpointWithPolicies(ctx context.Context, arg sqlc.GetChannelByOutpointWithPoliciesParams) (sqlc.GetChannelByOutpointWithPoliciesRow, error)
102104
GetPublicV1ChannelsBySCID(ctx context.Context, arg sqlc.GetPublicV1ChannelsBySCIDParams) ([]sqlc.Channel, error)
@@ -125,6 +127,12 @@ type SQLQueries interface {
125127
CountZombieChannels(ctx context.Context, version int16) (int64, error)
126128
DeleteZombieChannel(ctx context.Context, arg sqlc.DeleteZombieChannelParams) (sql.Result, error)
127129
IsZombieChannel(ctx context.Context, arg sqlc.IsZombieChannelParams) (bool, error)
130+
131+
/*
132+
Prune log table queries.
133+
*/
134+
GetPruneTip(ctx context.Context) (sqlc.PruneLog, error)
135+
UpsertPruneLogEntry(ctx context.Context, arg sqlc.UpsertPruneLogEntryParams) error
128136
}
129137

130138
// BatchedSQLQueries is a version of SQLQueries that's capable of batched
@@ -2230,6 +2238,213 @@ func (s *SQLStore) PruneGraphNodes() ([]route.Vertex, error) {
22302238
return prunedNodes, nil
22312239
}
22322240

2241+
// PruneGraph prunes newly closed channels from the channel graph in response
2242+
// to a new block being solved on the network. Any transactions which spend the
2243+
// funding output of any known channels within he graph will be deleted.
2244+
// Additionally, the "prune tip", or the last block which has been used to
2245+
// prune the graph is stored so callers can ensure the graph is fully in sync
2246+
// with the current UTXO state. A slice of channels that have been closed by
2247+
// the target block along with any pruned nodes are returned if the function
2248+
// succeeds without error.
2249+
//
2250+
// NOTE: part of the V1Store interface.
2251+
func (s *SQLStore) PruneGraph(spentOutputs []*wire.OutPoint,
2252+
blockHash *chainhash.Hash, blockHeight uint32) (
2253+
[]*models.ChannelEdgeInfo, []route.Vertex, error) {
2254+
2255+
ctx := context.TODO()
2256+
2257+
s.cacheMu.Lock()
2258+
defer s.cacheMu.Unlock()
2259+
2260+
var (
2261+
closedChans []*models.ChannelEdgeInfo
2262+
prunedNodes []route.Vertex
2263+
)
2264+
err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error {
2265+
for _, outpoint := range spentOutputs {
2266+
// TODO(elle): potentially optimize this by using
2267+
// sqlc.slice() once that works for both SQLite and
2268+
// Postgres.
2269+
//
2270+
// NOTE: this fetches channels for all protocol
2271+
// versions.
2272+
row, err := db.GetChannelByOutpoint(
2273+
ctx, outpoint.String(),
2274+
)
2275+
if errors.Is(err, sql.ErrNoRows) {
2276+
continue
2277+
} else if err != nil {
2278+
return fmt.Errorf("unable to fetch channel: %w",
2279+
err)
2280+
}
2281+
2282+
node1, node2, err := buildNodeVertices(
2283+
row.Node1Pubkey, row.Node2Pubkey,
2284+
)
2285+
if err != nil {
2286+
return err
2287+
}
2288+
2289+
info, err := getAndBuildEdgeInfo(
2290+
ctx, db, s.cfg.ChainHash, row.Channel.ID,
2291+
row.Channel, node1, node2,
2292+
)
2293+
if err != nil {
2294+
return err
2295+
}
2296+
2297+
err = db.DeleteChannel(ctx, row.Channel.ID)
2298+
if err != nil {
2299+
return fmt.Errorf("unable to delete "+
2300+
"channel: %w", err)
2301+
}
2302+
2303+
closedChans = append(closedChans, info)
2304+
}
2305+
2306+
err := db.UpsertPruneLogEntry(
2307+
ctx, sqlc.UpsertPruneLogEntryParams{
2308+
BlockHash: blockHash[:],
2309+
BlockHeight: int64(blockHeight),
2310+
},
2311+
)
2312+
if err != nil {
2313+
return fmt.Errorf("unable to insert prune log "+
2314+
"entry: %w", err)
2315+
}
2316+
2317+
// Now that we've pruned some channels, we'll also prune any
2318+
// nodes that no longer have any channels.
2319+
prunedNodes, err = s.pruneGraphNodes(ctx, db)
2320+
if err != nil {
2321+
return fmt.Errorf("unable to prune graph nodes: %w",
2322+
err)
2323+
}
2324+
2325+
return nil
2326+
}, func() {
2327+
prunedNodes = nil
2328+
closedChans = nil
2329+
})
2330+
if err != nil {
2331+
return nil, nil, fmt.Errorf("unable to prune graph: %w", err)
2332+
}
2333+
2334+
for _, channel := range closedChans {
2335+
s.rejectCache.remove(channel.ChannelID)
2336+
s.chanCache.remove(channel.ChannelID)
2337+
}
2338+
2339+
return closedChans, prunedNodes, nil
2340+
}
2341+
2342+
// ChannelView returns the verifiable edge information for each active channel
2343+
// within the known channel graph. The set of UTXOs (along with their scripts)
2344+
// returned are the ones that need to be watched on chain to detect channel
2345+
// closes on the resident blockchain.
2346+
//
2347+
// NOTE: part of the V1Store interface.
2348+
func (s *SQLStore) ChannelView() ([]EdgePoint, error) {
2349+
var (
2350+
ctx = context.TODO()
2351+
edgePoints []EdgePoint
2352+
)
2353+
2354+
handleChannel := func(db SQLQueries,
2355+
channel sqlc.ListChannelsPaginatedRow) error {
2356+
2357+
pkScript, err := genMultiSigP2WSH(
2358+
channel.BitcoinKey1, channel.BitcoinKey2,
2359+
)
2360+
if err != nil {
2361+
return err
2362+
}
2363+
2364+
op, err := wire.NewOutPointFromString(channel.Outpoint)
2365+
if err != nil {
2366+
return err
2367+
}
2368+
2369+
edgePoints = append(edgePoints, EdgePoint{
2370+
FundingPkScript: pkScript,
2371+
OutPoint: *op,
2372+
})
2373+
2374+
return nil
2375+
}
2376+
2377+
err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error {
2378+
lastID := int64(-1)
2379+
for {
2380+
rows, err := db.ListChannelsPaginated(
2381+
ctx, sqlc.ListChannelsPaginatedParams{
2382+
Version: int16(ProtocolV1),
2383+
ID: lastID,
2384+
Limit: pageSize,
2385+
},
2386+
)
2387+
if err != nil {
2388+
return err
2389+
}
2390+
2391+
if len(rows) == 0 {
2392+
break
2393+
}
2394+
2395+
for _, row := range rows {
2396+
err := handleChannel(db, row)
2397+
if err != nil {
2398+
return err
2399+
}
2400+
2401+
lastID = row.ID
2402+
}
2403+
}
2404+
2405+
return nil
2406+
}, func() {
2407+
edgePoints = nil
2408+
})
2409+
if err != nil {
2410+
return nil, fmt.Errorf("unable to fetch channel view: %w", err)
2411+
}
2412+
2413+
return edgePoints, nil
2414+
}
2415+
2416+
// PruneTip returns the block height and hash of the latest block that has been
2417+
// used to prune channels in the graph. Knowing the "prune tip" allows callers
2418+
// to tell if the graph is currently in sync with the current best known UTXO
2419+
// state.
2420+
//
2421+
// NOTE: part of the V1Store interface.
2422+
func (s *SQLStore) PruneTip() (*chainhash.Hash, uint32, error) {
2423+
var (
2424+
ctx = context.TODO()
2425+
tipHash chainhash.Hash
2426+
tipHeight uint32
2427+
)
2428+
err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error {
2429+
pruneTip, err := db.GetPruneTip(ctx)
2430+
if errors.Is(err, sql.ErrNoRows) {
2431+
return ErrGraphNeverPruned
2432+
} else if err != nil {
2433+
return fmt.Errorf("unable to fetch prune tip: %w", err)
2434+
}
2435+
2436+
tipHash = chainhash.Hash(pruneTip.BlockHash)
2437+
tipHeight = uint32(pruneTip.BlockHeight)
2438+
2439+
return nil
2440+
}, sqldb.NoOpReset)
2441+
if err != nil {
2442+
return nil, 0, err
2443+
}
2444+
2445+
return &tipHash, tipHeight, nil
2446+
}
2447+
22332448
// pruneGraphNodes deletes any node in the DB that doesn't have a channel.
22342449
//
22352450
// NOTE: this prunes nodes across protocol versions. It will never prune the
@@ -3389,6 +3604,11 @@ func getAndBuildEdgeInfo(ctx context.Context, db SQLQueries,
33893604
chain chainhash.Hash, dbChanID int64, dbChan sqlc.Channel, node1,
33903605
node2 route.Vertex) (*models.ChannelEdgeInfo, error) {
33913606

3607+
if dbChan.Version != int16(ProtocolV1) {
3608+
return nil, fmt.Errorf("unsupported channel version: %d",
3609+
dbChan.Version)
3610+
}
3611+
33923612
fv, extras, err := getChanFeaturesAndExtras(
33933613
ctx, db, dbChanID,
33943614
)

0 commit comments

Comments
 (0)