-
Notifications
You must be signed in to change notification settings - Fork 100
/
assets_common.go
335 lines (295 loc) · 10.9 KB
/
assets_common.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
package tarodb
import (
"bytes"
"context"
"database/sql"
"fmt"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/taro/asset"
"github.com/lightninglabs/taro/tarodb/sqlite"
)
// UpsertAssetStore is a sub-set of the main sqlite.Querier interface that
// contains methods related to inserting/updating assets.
type UpsertAssetStore interface {
// UpsertGenesisPoint inserts a new or updates an existing genesis point
// on disk, and returns the primary key.
UpsertGenesisPoint(ctx context.Context, prevOut []byte) (int32, error)
// UpsertGenesisAsset inserts a new or updates an existing genesis asset
// (the base asset info) in the DB, and returns the primary key.
//
// TODO(roasbeef): hybrid version of the main tx interface that an
// accept two diff storage interfaces?
//
// * or use a sort of mix-in type?
UpsertGenesisAsset(ctx context.Context, arg GenesisAsset) (int32, error)
// UpsertInternalKey inserts a new or updates an existing internal key
// into the database.
UpsertInternalKey(ctx context.Context, arg InternalKey) (int32, error)
// UpsertScriptKey inserts a new script key on disk into the DB.
UpsertScriptKey(context.Context, NewScriptKey) (int32, error)
// UpsertAssetFamilySig inserts a new asset family sig into the DB.
UpsertAssetFamilySig(ctx context.Context, arg AssetFamSig) (int32, error)
// UpsertAssetFamilyKey inserts a new or updates an existing family key
// on disk, and returns the primary key.
UpsertAssetFamilyKey(ctx context.Context, arg AssetFamilyKey) (int32,
error)
// InsertNewAsset inserts a new asset on disk.
InsertNewAsset(ctx context.Context,
arg sqlite.InsertNewAssetParams) (int32, error)
}
// upsertGenesis imports a new genesis point into the database or returns the
// existing ID if that point already exists.
func upsertGenesisPoint(ctx context.Context, q UpsertAssetStore,
genesisOutpoint wire.OutPoint) (int32, error) {
genesisPoint, err := encodeOutpoint(genesisOutpoint)
if err != nil {
return 0, fmt.Errorf("unable to encode genesis point: %w", err)
}
// First, we'll insert the component that ties together all the assets
// in a batch: the genesis point.
genesisPointID, err := q.UpsertGenesisPoint(ctx, genesisPoint)
if err != nil {
return 0, fmt.Errorf("unable to insert genesis point: %w", err)
}
return genesisPointID, nil
}
// upsertGenesis imports a new genesis record into the database or returns the
// existing ID of the genesis if it already exists.
func upsertGenesis(ctx context.Context, q UpsertAssetStore,
genesisPointID int32, genesis asset.Genesis) (int32, error) {
// Then we'll insert the genesis_assets row which tracks all the
// information that uniquely derives a given asset ID.
assetID := genesis.ID()
genAssetID, err := q.UpsertGenesisAsset(ctx, GenesisAsset{
AssetID: assetID[:],
AssetTag: genesis.Tag,
MetaData: genesis.Metadata,
OutputIndex: int32(genesis.OutputIndex),
AssetType: int16(genesis.Type),
GenesisPointID: genesisPointID,
})
if err != nil {
return 0, fmt.Errorf("unable to insert genesis asset: %w", err)
}
return genAssetID, nil
}
// upsertAssetsWithGenesis imports new assets and their genesis information into
// the database.
func upsertAssetsWithGenesis(ctx context.Context, q UpsertAssetStore,
genesisOutpoint wire.OutPoint, assets []*asset.Asset,
anchorUtxoIDs []sql.NullInt32) (int32, []int32, error) {
// First, we'll insert the component that ties together all the assets
// in a batch: the genesis point.
genesisPointID, err := upsertGenesisPoint(ctx, q, genesisOutpoint)
if err != nil {
return 0, nil, fmt.Errorf("unable to upsert genesis point: %w",
err)
}
// We'll now insert each asset into the database. Some assets have a key
// family, so we'll need to insert them before we can insert the asset
// itself.
assetIDs := make([]int32, len(assets))
for idx, a := range assets {
// First, we make sure the genesis asset information exists in
// the database.
genAssetID, err := upsertGenesis(
ctx, q, genesisPointID, a.Genesis,
)
if err != nil {
return 0, nil, fmt.Errorf("unable to upsert genesis: "+
"%w", err)
}
// This asset has as key family, so we'll insert it into the
// database. If it doesn't exist, the UPSERT query will still
// return the family_id we'll need.
familySigID, err := upsertFamilyKey(
ctx, a.FamilyKey, q, genesisPointID, genAssetID,
)
if err != nil {
return 0, nil, fmt.Errorf("unable to upsert family "+
"key: %w", err)
}
scriptKeyID, err := upsertScriptKey(ctx, a.ScriptKey, q)
if err != nil {
return 0, nil, fmt.Errorf("unable to upsert script "+
"key: %w", err)
}
// Is the asset anchored already?
var anchorUtxoID sql.NullInt32
if len(anchorUtxoIDs) > 0 {
anchorUtxoID = anchorUtxoIDs[idx]
}
// With all the dependent data inserted, we can now insert the
// base asset information itself.
assetIDs[idx], err = q.InsertNewAsset(
ctx, sqlite.InsertNewAssetParams{
GenesisID: genAssetID,
Version: int32(a.Version),
ScriptKeyID: scriptKeyID,
AssetFamilySigID: familySigID,
ScriptVersion: int32(a.ScriptVersion),
Amount: int64(a.Amount),
LockTime: sqlInt32(a.LockTime),
RelativeLockTime: sqlInt32(a.RelativeLockTime),
AnchorUtxoID: anchorUtxoID,
},
)
if err != nil {
return 0, nil, fmt.Errorf("unable to insert asset: %w",
err)
}
}
return genesisPointID, assetIDs, nil
}
// upsertFamilyKey inserts or updates a family key and its associated internal
// key.
func upsertFamilyKey(ctx context.Context, familyKey *asset.FamilyKey,
q UpsertAssetStore, genesisPointID, genAssetID int32) (sql.NullInt32,
error) {
// No family key, this asset is not re-issuable.
var nullID sql.NullInt32
if familyKey == nil {
return nullID, nil
}
// Before we can insert a new asset key family, we'll also need to
// insert an internal key which will be referenced by the key family.
// When we insert a proof, we don't know the raw key. So we just insert
// the tweaked key as the internal key.
//
// TODO(roasbeef):
// * don't have the key desc information here necessarily
// * inserting the fam key rn, which is ok as its external w/ no key
// desc info
tweakedKeyBytes := familyKey.FamKey.SerializeCompressed()
rawKeyBytes := familyKey.FamKey.SerializeCompressed()
if familyKey.RawKey.PubKey != nil {
rawKeyBytes = familyKey.RawKey.PubKey.SerializeCompressed()
}
keyID, err := q.UpsertInternalKey(ctx, InternalKey{
RawKey: rawKeyBytes,
KeyFamily: int32(familyKey.RawKey.Family),
KeyIndex: int32(familyKey.RawKey.Index),
})
if err != nil {
return nullID, fmt.Errorf("unable to insert internal key: %w",
err)
}
famID, err := q.UpsertAssetFamilyKey(ctx, AssetFamilyKey{
TweakedFamKey: tweakedKeyBytes,
InternalKeyID: keyID,
GenesisPointID: genesisPointID,
})
if err != nil {
return nullID, fmt.Errorf("unable to insert family key: %w",
err)
}
// With the statement above complete, we'll now insert the
// asset_family_sig entry for this, which has a one-to-many relationship
// with family keys (there can be many sigs for a family key which link
// together otherwise disparate asset IDs).
//
// TODO(roasbeef): sig here doesn't actually matter?
famSigID, err := q.UpsertAssetFamilySig(ctx, AssetFamSig{
GenesisSig: familyKey.Sig.Serialize(),
GenAssetID: genAssetID,
KeyFamID: famID,
})
if err != nil {
return nullID, fmt.Errorf("unable to insert fam sig: %w", err)
}
return sqlInt32(famSigID), nil
}
// upsertScriptKey inserts or updates a script key and its associated internal
// key.
func upsertScriptKey(ctx context.Context, scriptKey asset.ScriptKey,
q UpsertAssetStore) (int32, error) {
if scriptKey.TweakedScriptKey != nil {
rawScriptKeyID, err := q.UpsertInternalKey(ctx, InternalKey{
RawKey: scriptKey.RawKey.PubKey.SerializeCompressed(),
KeyFamily: int32(scriptKey.RawKey.Family),
KeyIndex: int32(scriptKey.RawKey.Index),
})
if err != nil {
return 0, fmt.Errorf("unable to insert internal key: "+
"%w", err)
}
scriptKeyID, err := q.UpsertScriptKey(ctx, NewScriptKey{
InternalKeyID: rawScriptKeyID,
TweakedScriptKey: scriptKey.PubKey.SerializeCompressed(),
Tweak: scriptKey.Tweak,
})
if err != nil {
return 0, fmt.Errorf("unable to insert script key: "+
"%w", err)
}
return scriptKeyID, nil
}
// At this point, we only have the actual asset as read from a TLV, so
// we don't actually have the raw script key here. Instead, we'll use
// an UPSERT to trigger a conflict on the tweaked script key so we can
// obtain the script key ID we need here. This is for the proof
// import based on an addr send.
//
// TODO(roasbeef): or just fetch the one we need?
scriptKeyID, err := q.UpsertScriptKey(ctx, NewScriptKey{
TweakedScriptKey: scriptKey.PubKey.SerializeCompressed(),
})
if err != nil {
// If this fails, then we're just importing the proof to mirror
// the state of another node. In this case, we'll just import
// the key in the asset (a tweaked key) as an internal key. We
// can't actually use this asset, but the import will complete.
//
// TODO(roasbeef): remove after itest work
rawScriptKeyID, err := q.UpsertInternalKey(ctx, InternalKey{
RawKey: scriptKey.PubKey.SerializeCompressed(),
})
if err != nil {
return 0, fmt.Errorf("unable to insert internal key: "+
"%w", err)
}
scriptKeyID, err = q.UpsertScriptKey(ctx, NewScriptKey{
InternalKeyID: rawScriptKeyID,
TweakedScriptKey: scriptKey.PubKey.SerializeCompressed(),
})
if err != nil {
return 0, fmt.Errorf("unable to insert script key: "+
"%w", err)
}
}
return scriptKeyID, nil
}
// FetchGenesisStore houses the methods related to fetching genesis assets.
type FetchGenesisStore interface {
// FetchGenesisByID returns a single genesis asset by its primary key
// ID.
FetchGenesisByID(ctx context.Context, assetID int32) (Genesis, error)
}
// fetchGenesis returns a fully populated genesis record from the database,
// identified by its primary key ID.
func fetchGenesis(ctx context.Context, q FetchGenesisStore,
assetID int32) (asset.Genesis, error) {
// Now we fetch the genesis information that so far we
// only have the ID for in the address record.
gen, err := q.FetchGenesisByID(ctx, assetID)
if err != nil {
return asset.Genesis{}, fmt.Errorf("unable to fetch genesis: "+
"%w", err)
}
// Next, we'll populate the asset genesis information which
// includes the genesis prev out, and the other information
// needed to derive an asset ID.
var genesisPrevOut wire.OutPoint
err = readOutPoint(bytes.NewReader(gen.PrevOut), 0, 0, &genesisPrevOut)
if err != nil {
return asset.Genesis{}, fmt.Errorf("unable to read outpoint: "+
"%w", err)
}
return asset.Genesis{
FirstPrevOut: genesisPrevOut,
Tag: gen.AssetTag,
Metadata: gen.MetaData,
OutputIndex: uint32(gen.OutputIndex),
Type: asset.Type(gen.AssetType),
}, nil
}