@@ -69,6 +69,15 @@ type SQLQueries interface {
69
69
*/
70
70
AddSourceNode (ctx context.Context , nodeID int64 ) error
71
71
GetSourceNodesByVersion (ctx context.Context , version int16 ) ([]sqlc.GetSourceNodesByVersionRow , error )
72
+
73
+ /*
74
+ Channel queries.
75
+ */
76
+ CreateChannel (ctx context.Context , arg sqlc.CreateChannelParams ) (int64 , error )
77
+ GetChannelBySCID (ctx context.Context , arg sqlc.GetChannelBySCIDParams ) (sqlc.Channel , error )
78
+
79
+ CreateChannelExtraType (ctx context.Context , arg sqlc.CreateChannelExtraTypeParams ) error
80
+ InsertChannelFeature (ctx context.Context , arg sqlc.InsertChannelFeatureParams ) error
72
81
}
73
82
74
83
// BatchedSQLQueries is a version of SQLQueries that's capable of batched
@@ -455,6 +464,54 @@ func (s *SQLStore) NodeUpdatesInHorizon(startTime,
455
464
return nodes , nil
456
465
}
457
466
467
+ // AddChannelEdge adds a new (undirected, blank) edge to the graph database. An
468
+ // undirected edge from the two target nodes are created. The information stored
469
+ // denotes the static attributes of the channel, such as the channelID, the keys
470
+ // involved in creation of the channel, and the set of features that the channel
471
+ // supports. The chanPoint and chanID are used to uniquely identify the edge
472
+ // globally within the database.
473
+ //
474
+ // NOTE: part of the V1Store interface.
475
+ func (s * SQLStore ) AddChannelEdge (edge * models.ChannelEdgeInfo ,
476
+ opts ... batch.SchedulerOption ) error {
477
+
478
+ ctx := context .TODO ()
479
+
480
+ var alreadyExists bool
481
+ r := & batch.Request [SQLQueries ]{
482
+ Opts : batch .NewSchedulerOptions (opts ... ),
483
+ Reset : func () {
484
+ alreadyExists = false
485
+ },
486
+ Do : func (tx SQLQueries ) error {
487
+ err := insertChannel (ctx , tx , edge )
488
+
489
+ // Silence ErrEdgeAlreadyExist so that the batch can
490
+ // succeed, but propagate the error via local state.
491
+ if errors .Is (err , ErrEdgeAlreadyExist ) {
492
+ alreadyExists = true
493
+ return nil
494
+ }
495
+
496
+ return err
497
+ },
498
+ OnCommit : func (err error ) error {
499
+ switch {
500
+ case err != nil :
501
+ return err
502
+ case alreadyExists :
503
+ return ErrEdgeAlreadyExist
504
+ default :
505
+ s .rejectCache .remove (edge .ChannelID )
506
+ s .chanCache .remove (edge .ChannelID )
507
+ return nil
508
+ }
509
+ },
510
+ }
511
+
512
+ return s .chanScheduler .Execute (ctx , r )
513
+ }
514
+
458
515
// getNodeByPubKey attempts to look up a target node by its public key.
459
516
func getNodeByPubKey (ctx context.Context , db SQLQueries ,
460
517
pubKey route.Vertex ) (int64 , * models.LightningNode , error ) {
@@ -1022,3 +1079,151 @@ func marshalExtraOpaqueData(data []byte) (map[uint64][]byte, error) {
1022
1079
1023
1080
return records , nil
1024
1081
}
1082
+
1083
+ // insertChannel inserts a new channel record into the database.
1084
+ func insertChannel (ctx context.Context , db SQLQueries ,
1085
+ edge * models.ChannelEdgeInfo ) error {
1086
+
1087
+ var chanIDB [8 ]byte
1088
+ byteOrder .PutUint64 (chanIDB [:], edge .ChannelID )
1089
+
1090
+ // Make sure that the channel doesn't already exist. We do this
1091
+ // explicitly instead of relying on catching a unique constraint error
1092
+ // because relying on SQL to throw that error would abort the entire
1093
+ // batch of transactions.
1094
+ _ , err := db .GetChannelBySCID (
1095
+ ctx , sqlc.GetChannelBySCIDParams {
1096
+ Scid : chanIDB [:],
1097
+ Version : int16 (ProtocolV1 ),
1098
+ },
1099
+ )
1100
+ if err == nil {
1101
+ return ErrEdgeAlreadyExist
1102
+ } else if ! errors .Is (err , sql .ErrNoRows ) {
1103
+ return fmt .Errorf ("unable to fetch channel: %w" , err )
1104
+ }
1105
+
1106
+ // Make sure that at least a "shell" entry for each node is present in
1107
+ // the nodes table.
1108
+ node1DBID , err := maybeCreateShellNode (ctx , db , edge .NodeKey1Bytes )
1109
+ if err != nil {
1110
+ return fmt .Errorf ("unable to create shell node: %w" , err )
1111
+ }
1112
+
1113
+ node2DBID , err := maybeCreateShellNode (ctx , db , edge .NodeKey2Bytes )
1114
+ if err != nil {
1115
+ return fmt .Errorf ("unable to create shell node: %w" , err )
1116
+ }
1117
+
1118
+ var capacity sql.NullInt64
1119
+ if edge .Capacity != 0 {
1120
+ capacity = sqldb .SQLInt64 (int64 (edge .Capacity ))
1121
+ }
1122
+
1123
+ createParams := sqlc.CreateChannelParams {
1124
+ Version : int16 (ProtocolV1 ),
1125
+ Scid : chanIDB [:],
1126
+ NodeID1 : node1DBID ,
1127
+ NodeID2 : node2DBID ,
1128
+ Outpoint : edge .ChannelPoint .String (),
1129
+ Capacity : capacity ,
1130
+ BitcoinKey1 : edge .BitcoinKey1Bytes [:],
1131
+ BitcoinKey2 : edge .BitcoinKey2Bytes [:],
1132
+ }
1133
+
1134
+ if edge .AuthProof != nil {
1135
+ proof := edge .AuthProof
1136
+
1137
+ createParams .Node1Signature = proof .NodeSig1Bytes
1138
+ createParams .Node2Signature = proof .NodeSig2Bytes
1139
+ createParams .Bitcoin1Signature = proof .BitcoinSig1Bytes
1140
+ createParams .Bitcoin2Signature = proof .BitcoinSig2Bytes
1141
+ }
1142
+
1143
+ // Insert the new channel record.
1144
+ dbChanID , err := db .CreateChannel (ctx , createParams )
1145
+ if err != nil {
1146
+ return err
1147
+ }
1148
+
1149
+ // Insert any channel features.
1150
+ if len (edge .Features ) != 0 {
1151
+ chanFeatures := lnwire .NewRawFeatureVector ()
1152
+ err := chanFeatures .Decode (bytes .NewReader (edge .Features ))
1153
+ if err != nil {
1154
+ return err
1155
+ }
1156
+
1157
+ fv := lnwire .NewFeatureVector (chanFeatures , lnwire .Features )
1158
+ for feature := range fv .Features () {
1159
+ err = db .InsertChannelFeature (
1160
+ ctx , sqlc.InsertChannelFeatureParams {
1161
+ ChannelID : dbChanID ,
1162
+ FeatureBit : int32 (feature ),
1163
+ },
1164
+ )
1165
+ if err != nil {
1166
+ return fmt .Errorf ("unable to insert " +
1167
+ "channel(%d) feature(%v): %w" , dbChanID ,
1168
+ feature , err )
1169
+ }
1170
+ }
1171
+ }
1172
+
1173
+ // Finally, insert any extra TLV fields in the channel announcement.
1174
+ extra , err := marshalExtraOpaqueData (edge .ExtraOpaqueData )
1175
+ if err != nil {
1176
+ return fmt .Errorf ("unable to marshal extra opaque data: %w" ,
1177
+ err )
1178
+ }
1179
+
1180
+ for tlvType , value := range extra {
1181
+ err := db .CreateChannelExtraType (
1182
+ ctx , sqlc.CreateChannelExtraTypeParams {
1183
+ ChannelID : dbChanID ,
1184
+ Type : int64 (tlvType ),
1185
+ Value : value ,
1186
+ },
1187
+ )
1188
+ if err != nil {
1189
+ return fmt .Errorf ("unable to upsert channel(%d) extra " +
1190
+ "signed field(%v): %w" , edge .ChannelID ,
1191
+ tlvType , err )
1192
+ }
1193
+ }
1194
+
1195
+ return nil
1196
+ }
1197
+
1198
+ // maybeCreateShellNode checks if a shell node entry exists for the
1199
+ // given public key. If it does not exist, then a new shell node entry is
1200
+ // created. The ID of the node is returned. A shell node only has a protocol
1201
+ // version and public key persisted.
1202
+ func maybeCreateShellNode (ctx context.Context , db SQLQueries ,
1203
+ pubKey route.Vertex ) (int64 , error ) {
1204
+
1205
+ dbNode , err := db .GetNodeByPubKey (
1206
+ ctx , sqlc.GetNodeByPubKeyParams {
1207
+ PubKey : pubKey [:],
1208
+ Version : int16 (ProtocolV1 ),
1209
+ },
1210
+ )
1211
+ // The node exists. Return the ID.
1212
+ if err == nil {
1213
+ return dbNode .ID , nil
1214
+ } else if ! errors .Is (err , sql .ErrNoRows ) {
1215
+ return 0 , err
1216
+ }
1217
+
1218
+ // Otherwise, the node does not exist, so we create a shell entry for
1219
+ // it.
1220
+ id , err := db .UpsertNode (ctx , sqlc.UpsertNodeParams {
1221
+ Version : int16 (ProtocolV1 ),
1222
+ PubKey : pubKey [:],
1223
+ })
1224
+ if err != nil {
1225
+ return 0 , fmt .Errorf ("unable to create shell node: %w" , err )
1226
+ }
1227
+
1228
+ return id , nil
1229
+ }
0 commit comments