Skip to content

Commit 2ef55ba

Browse files
authored
Merge pull request #1654 from lightninglabs/0-6-1-pull-requests
release: create minor release v0.6.1 side branch
2 parents cf2a5e5 + 4de484e commit 2ef55ba

19 files changed

+781
-131
lines changed

address/book.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,11 @@ type Storage interface {
137137
// transfer comes in later on.
138138
InsertScriptKey(ctx context.Context, scriptKey asset.ScriptKey,
139139
keyType asset.ScriptKeyType) error
140+
141+
// FetchAllAssetMeta attempts to fetch all asset meta known to the
142+
// database.
143+
FetchAllAssetMeta(
144+
ctx context.Context) (map[asset.ID]*proof.MetaReveal, error)
140145
}
141146

142147
// KeyRing is used to create script and internal keys for Taproot Asset
@@ -190,6 +195,14 @@ type Book struct {
190195
// subscriberMtx guards the subscribers map and access to the
191196
// subscriptionID.
192197
subscriberMtx sync.Mutex
198+
199+
// decimalDisplayCache is a cache for the decimal display value of
200+
// assets. This is used to avoid repeated database queries for the same
201+
// asset ID.
202+
decimalDisplayCache map[asset.ID]fn.Option[uint32]
203+
204+
// decDisplayCacheMtx guards the decimalDisplayCache map.
205+
decDisplayCacheMtx sync.Mutex
193206
}
194207

195208
// A compile-time assertion to make sure Book satisfies the
@@ -203,6 +216,7 @@ func NewBook(cfg BookConfig) *Book {
203216
subscribers: make(
204217
map[uint64]*fn.EventReceiver[*AddrWithKeyInfo],
205218
),
219+
decimalDisplayCache: make(map[asset.ID]fn.Option[uint32]),
206220
}
207221
}
208222

@@ -334,6 +348,68 @@ func (b *Book) FetchAssetMetaForAsset(ctx context.Context,
334348
return meta, nil
335349
}
336350

351+
// DecDisplayForAssetID attempts to fetch the meta reveal for a specific asset
352+
// ID and extract the decimal display value from it.
353+
func (b *Book) DecDisplayForAssetID(ctx context.Context,
354+
id asset.ID) (fn.Option[uint32], error) {
355+
356+
b.decDisplayCacheMtx.Lock()
357+
defer b.decDisplayCacheMtx.Unlock()
358+
359+
// If we don't have anything in the cache, we'll attempt to load it.
360+
// This will be re-attempted every time if there are no assets in the
361+
// database. But this isn't expected to remain the case for long.
362+
if len(b.decimalDisplayCache) == 0 {
363+
// If the cache is empty, we'll populate it with all asset
364+
// metas known to the database.
365+
allMeta, err := b.cfg.Store.FetchAllAssetMeta(ctx)
366+
if err != nil {
367+
return fn.None[uint32](), fmt.Errorf("unable to fetch "+
368+
"all asset meta: %v", err)
369+
}
370+
371+
for assetID, meta := range allMeta {
372+
if meta == nil {
373+
continue
374+
}
375+
376+
displayOpt, err := meta.DecDisplayOption()
377+
if err != nil {
378+
return fn.None[uint32](), fmt.Errorf("unable "+
379+
"to extract decimal display option "+
380+
"for asset %v: %v", assetID, err)
381+
}
382+
383+
b.decimalDisplayCache[assetID] = displayOpt
384+
}
385+
}
386+
387+
// If we have the value in the cache, return it from there.
388+
if displayOpt, ok := b.decimalDisplayCache[id]; ok {
389+
return displayOpt, nil
390+
}
391+
392+
// If we don't have the value in the cache, it was added after we filled
393+
// the cache, and we'll attempt to fetch the asset meta from the
394+
// database instead.
395+
meta, err := b.FetchAssetMetaForAsset(ctx, id)
396+
if err != nil {
397+
return fn.None[uint32](), fmt.Errorf("unable to fetch asset "+
398+
"meta for asset_id=%v :%v", id, err)
399+
}
400+
401+
opt, err := meta.DecDisplayOption()
402+
if err != nil {
403+
return fn.None[uint32](), fmt.Errorf("unable to extract "+
404+
"decimal display option for asset %v: %v", id, err)
405+
}
406+
407+
// Store the value in the cache for future lookups.
408+
b.decimalDisplayCache[id] = opt
409+
410+
return opt, nil
411+
}
412+
337413
// NewAddress creates a new Taproot Asset address based on the input parameters.
338414
func (b *Book) NewAddress(ctx context.Context, addrVersion Version,
339415
assetID asset.ID, amount uint64,

asset/asset.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,85 @@ func (s *Specifier) AssertNotEmpty() error {
521521
return nil
522522
}
523523

524+
// Equal compares this specifier to another one, returning true if they are
525+
// equal. If strict is true, then both specifiers need to either have both equal
526+
// asset IDs and group keys set or only one of those (but matching). If strict
527+
// is false, then it's enough to have the same group key _or_ the same asset ID
528+
// (if one has no group key set). This is useful for cases where one specifier
529+
// only specifies the group key, while the other one specifies both the group
530+
// key and the asset ID. In that case, we can consider them equal if the group
531+
// keys match, even if the asset IDs are different (or one is not set).
532+
func (s *Specifier) Equal(other *Specifier, strict bool) (bool, error) {
533+
// If both specifiers are nil, they are equal.
534+
if s == nil && other == nil {
535+
return true, nil
536+
}
537+
538+
// If one of the specifiers is nil, they are not equal.
539+
if s == nil || other == nil {
540+
return false, nil
541+
}
542+
543+
// If either of them is empty while the other is not, they are not
544+
// equal.
545+
if (s.HasId() || s.HasGroupPubKey()) !=
546+
(other.HasId() || other.HasGroupPubKey()) {
547+
548+
return false, nil
549+
}
550+
551+
// If they both have distinct elements set, then they are not equal.
552+
if s.HasId() != other.HasId() &&
553+
s.HasGroupPubKey() != other.HasGroupPubKey() {
554+
555+
return false, nil
556+
}
557+
558+
// If both specifiers have a group public key, compare them.
559+
if s.HasGroupPubKey() && other.HasGroupPubKey() {
560+
groupKeyA := s.UnwrapGroupKeyToPtr()
561+
groupKeyB := other.UnwrapGroupKeyToPtr()
562+
563+
// If any unwrapped element is nil, something's wrong, and we
564+
// can't compare them.
565+
if groupKeyA == nil || groupKeyB == nil {
566+
return false, fmt.Errorf("unable to unwrap group key "+
567+
"from specifier: %v vs %v", s, other)
568+
}
569+
570+
if !groupKeyA.IsEqual(groupKeyB) {
571+
return false, nil
572+
}
573+
574+
// If we're not doing a strict comparison, then we can return
575+
// true here if the group keys match. The group key has higher
576+
// priority than the ID, so if they match and the comparison
577+
// isn't strict, we can consider the specifiers equal.
578+
if !strict {
579+
return true, nil
580+
}
581+
}
582+
583+
// If both specifiers have an ID, compare them.
584+
if s.HasId() && other.HasId() {
585+
idA := s.UnwrapIdToPtr()
586+
idB := other.UnwrapIdToPtr()
587+
588+
// If any unwrapped element is nil, something's wrong and we
589+
// can't compare them.
590+
if idA == nil || idB == nil {
591+
return false, fmt.Errorf("unable to unwrap asset ID "+
592+
"from specifier: %v vs %v", s, other)
593+
}
594+
595+
if *idA != *idB {
596+
return false, nil
597+
}
598+
}
599+
600+
return true, nil
601+
}
602+
524603
// Type denotes the asset types supported by the Taproot Asset protocol.
525604
type Type uint8
526605

asset/asset_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1247,3 +1247,125 @@ func TestDecodeAsset(t *testing.T) {
12471247

12481248
t.Logf("Decoded asset: %v", string(assetJSON))
12491249
}
1250+
1251+
// TestSpecifierEqual tests the Specifier.Equal method for all possible cases.
1252+
func TestSpecifierEqual(t *testing.T) {
1253+
id1 := ID{1, 2, 3}
1254+
id2 := ID{4, 5, 6}
1255+
pk1, _ := btcec.NewPrivateKey()
1256+
pubKey1 := pk1.PubKey()
1257+
pk2, _ := btcec.NewPrivateKey()
1258+
pubKey2 := pk2.PubKey()
1259+
1260+
cases := []struct {
1261+
name string
1262+
s, other *Specifier
1263+
strict bool
1264+
expects bool
1265+
expectErr bool
1266+
}{
1267+
{
1268+
name: "both nil",
1269+
s: nil,
1270+
other: nil,
1271+
strict: false,
1272+
expects: true,
1273+
},
1274+
{
1275+
name: "one nil (s)",
1276+
s: nil,
1277+
other: &Specifier{},
1278+
strict: false,
1279+
expects: false,
1280+
},
1281+
{
1282+
name: "one nil (other)",
1283+
s: &Specifier{},
1284+
other: nil,
1285+
strict: false,
1286+
expects: false,
1287+
},
1288+
{
1289+
name: "both empty",
1290+
s: &Specifier{},
1291+
other: &Specifier{},
1292+
strict: false,
1293+
expects: true,
1294+
},
1295+
{
1296+
name: "both with same ID",
1297+
s: &Specifier{id: fn.Some(id1)},
1298+
other: &Specifier{id: fn.Some(id1)},
1299+
strict: false,
1300+
expects: true,
1301+
},
1302+
{
1303+
name: "both with different ID",
1304+
s: &Specifier{id: fn.Some(id1)},
1305+
other: &Specifier{id: fn.Some(id2)},
1306+
strict: false,
1307+
expects: false,
1308+
},
1309+
{
1310+
name: "both with same group key",
1311+
s: &Specifier{groupKey: fn.Some(*pubKey1)},
1312+
other: &Specifier{groupKey: fn.Some(*pubKey1)},
1313+
strict: false,
1314+
expects: true,
1315+
},
1316+
{
1317+
name: "both with different group key",
1318+
s: &Specifier{groupKey: fn.Some(*pubKey1)},
1319+
other: &Specifier{groupKey: fn.Some(*pubKey2)},
1320+
strict: false,
1321+
expects: false,
1322+
},
1323+
{
1324+
name: "one with ID, one with group key",
1325+
s: &Specifier{id: fn.Some(id1)},
1326+
other: &Specifier{groupKey: fn.Some(*pubKey1)},
1327+
strict: false,
1328+
expects: false,
1329+
},
1330+
{
1331+
name: "both with ID, strict true",
1332+
s: &Specifier{id: fn.Some(id1)},
1333+
other: &Specifier{id: fn.Some(id1)},
1334+
strict: true,
1335+
expects: true,
1336+
},
1337+
{
1338+
name: "both with group key, strict true",
1339+
s: &Specifier{groupKey: fn.Some(*pubKey1)},
1340+
other: &Specifier{groupKey: fn.Some(*pubKey1)},
1341+
strict: true,
1342+
expects: true,
1343+
},
1344+
{
1345+
name: "one with ID, one with group key, strict true",
1346+
s: &Specifier{id: fn.Some(id1)},
1347+
other: &Specifier{groupKey: fn.Some(*pubKey1)},
1348+
strict: true,
1349+
expects: false,
1350+
},
1351+
{
1352+
name: "both empty, strict true",
1353+
s: &Specifier{},
1354+
other: &Specifier{},
1355+
strict: true,
1356+
expects: true,
1357+
},
1358+
}
1359+
1360+
for _, tc := range cases {
1361+
t.Run(tc.name, func(t *testing.T) {
1362+
eq, err := tc.s.Equal(tc.other, tc.strict)
1363+
if tc.expectErr {
1364+
require.Error(t, err)
1365+
} else {
1366+
require.NoError(t, err)
1367+
require.Equal(t, tc.expects, eq)
1368+
}
1369+
})
1370+
}
1371+
}

chain_bridge.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ import (
2727

2828
const (
2929
// maxNumBlocksInCache is the maximum number of blocks we'll cache
30-
// timestamps for. With 100k blocks we should only take up approximately
31-
// 800kB of memory (4 bytes for the block height and 4 bytes for the
30+
// timestamps for. With 400k blocks we should only take up approximately
31+
// 3200kB of memory (4 bytes for the block height and 4 bytes for the
3232
// timestamp, not including any map/cache overhead).
33-
maxNumBlocksInCache = 100_000
33+
maxNumBlocksInCache = 400_000
3434
)
3535

3636
// cacheableTimestamp is a wrapper around an uint32 that can be used as a value

go.mod

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ require (
99
github.com/btcsuite/btcd/btcutil/psbt v1.1.10
1010
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0
1111
github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c
12-
github.com/btcsuite/btclog/v2 v2.0.1-0.20250110154127-3ae4bf1cb318
13-
github.com/btcsuite/btcwallet v0.16.13
12+
github.com/btcsuite/btclog/v2 v2.0.1-0.20250602222548-9967d19bb084
13+
github.com/btcsuite/btcwallet v0.16.14
1414
github.com/btcsuite/btcwallet/wallet/txsizes v1.2.5
1515
github.com/btcsuite/btcwallet/wtxmgr v1.5.6
1616
github.com/caddyserver/certmagic v0.17.2
@@ -27,10 +27,11 @@ require (
2727
github.com/lib/pq v1.10.9
2828
github.com/lightninglabs/aperture v0.3.13-beta
2929
github.com/lightninglabs/lightning-node-connect/hashmailrpc v1.0.3
30-
github.com/lightninglabs/lndclient v0.19.0-7
30+
github.com/lightninglabs/lndclient v0.19.0-12
3131
github.com/lightninglabs/neutrino/cache v1.1.2
32-
github.com/lightninglabs/taproot-assets/taprpc v1.0.7
33-
github.com/lightningnetwork/lnd v0.19.0-beta
32+
// The commit cf2a5e5b corresponds to the tapd v0.6.0 tag.
33+
github.com/lightninglabs/taproot-assets/taprpc v1.0.8-0.20250617163017-cf2a5e5bb47c
34+
github.com/lightningnetwork/lnd v0.19.2-beta
3435
github.com/lightningnetwork/lnd/cert v1.2.2
3536
github.com/lightningnetwork/lnd/clock v1.1.1
3637
github.com/lightningnetwork/lnd/fn/v2 v2.0.8
@@ -88,7 +89,7 @@ require (
8889
github.com/fergusstrange/embedded-postgres v1.25.0 // indirect
8990
github.com/go-logr/logr v1.4.2 // indirect
9091
github.com/go-logr/stdr v1.2.2 // indirect
91-
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
92+
github.com/go-viper/mapstructure/v2 v2.3.0 // indirect
9293
github.com/goccy/go-yaml v1.15.23 // indirect
9394
github.com/gogo/protobuf v1.3.2 // indirect
9495
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect

0 commit comments

Comments
 (0)