Skip to content

Commit 3840e5a

Browse files
ffranrjtobin
authored andcommitted
chain_bridge: refactor GetBlockTimestamp to use GetBlockHeaderByHeight
Refactored GetBlockTimestamp to call GetBlockHeaderByHeight and return an optional error type. Removed the timestamp-to-block-height cache, as it did not handle re-orgs correctly. This prepares the codebase for a more comprehensive caching mechanism to be added in a follow-up commit.
1 parent 23ddf52 commit 3840e5a

File tree

5 files changed

+62
-67
lines changed

5 files changed

+62
-67
lines changed

lndservices/chain_bridge.go

Lines changed: 18 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"github.com/btcsuite/btcd/chaincfg/chainhash"
1111
"github.com/btcsuite/btcd/wire"
1212
"github.com/lightninglabs/lndclient"
13-
"github.com/lightninglabs/neutrino/cache/lru"
1413
"github.com/lightninglabs/taproot-assets/asset"
1514
"github.com/lightninglabs/taproot-assets/fn"
1615
"github.com/lightninglabs/taproot-assets/proof"
@@ -21,12 +20,6 @@ import (
2120
)
2221

2322
const (
24-
// maxNumBlocksInCache is the maximum number of blocks we'll cache
25-
// timestamps for. With 400k blocks we should only take up approximately
26-
// 3200kB of memory (4 bytes for the block height and 4 bytes for the
27-
// timestamp, not including any map/cache overhead).
28-
maxNumBlocksInCache = 400_000
29-
3023
// medianTimeBlocks is the number of previous blocks which should be
3124
// used to calculate the median time used to validate block timestamps.
3225
medianTimeBlocks = 11
@@ -38,25 +31,16 @@ var (
3831
errTxNotFound = fmt.Errorf("transaction not found in proof file")
3932
)
4033

41-
// cacheableTimestamp is a wrapper around an uint32 that can be used as a value
42-
// in an LRU cache.
43-
type cacheableTimestamp uint32
44-
45-
// Size returns the size of the cacheable timestamp. Since we scale the cache by
46-
// the number of items and not the total memory size, we can simply return 1
47-
// here to count each timestamp as 1 item.
48-
func (c cacheableTimestamp) Size() (uint64, error) {
49-
return 1, nil
50-
}
51-
5234
// LndRpcChainBridge is an implementation of the tapgarden.ChainBridge
5335
// interface backed by an active remote lnd node.
5436
type LndRpcChainBridge struct {
37+
// lnd is the active lnd services client.
5538
lnd *lndclient.LndServices
5639

57-
blockTimestampCache *lru.Cache[uint32, cacheableTimestamp]
58-
retryConfig fn.RetryConfig
40+
// retryConfig is the configuration used for retrying operations.
41+
retryConfig fn.RetryConfig
5942

43+
// assetStore is a handle to the asset store.
6044
assetStore *tapdb.AssetStore
6145
}
6246

@@ -66,10 +50,7 @@ func NewLndRpcChainBridge(lnd *lndclient.LndServices,
6650
assetStore *tapdb.AssetStore) *LndRpcChainBridge {
6751

6852
return &LndRpcChainBridge{
69-
lnd: lnd,
70-
blockTimestampCache: lru.NewCache[uint32, cacheableTimestamp](
71-
maxNumBlocksInCache,
72-
),
53+
lnd: lnd,
7354
retryConfig: fn.DefaultRetryConfig(),
7455
assetStore: assetStore,
7556
}
@@ -268,38 +249,20 @@ func (l *LndRpcChainBridge) CurrentHeight(ctx context.Context) (uint32, error) {
268249

269250
// GetBlockTimestamp returns the timestamp of the block at the given height.
270251
func (l *LndRpcChainBridge) GetBlockTimestamp(ctx context.Context,
271-
height uint32) int64 {
252+
height uint32) (int64, error) {
272253

273254
// Shortcut any lookup in case we don't have a valid height in the first
274255
// place.
275256
if height == 0 {
276-
return 0
277-
}
278-
279-
cacheTS, err := l.blockTimestampCache.Get(height)
280-
if err == nil {
281-
return int64(cacheTS)
282-
}
283-
284-
hash, err := fn.RetryFuncN(
285-
ctx, l.retryConfig, func() (chainhash.Hash, error) {
286-
return l.lnd.ChainKit.GetBlockHash(ctx, int64(height))
287-
},
288-
)
289-
if err != nil {
290-
return 0
257+
return 0, nil
291258
}
292259

293-
// Get block header.
294-
header, err := l.GetBlockHeader(ctx, hash)
260+
blockHeader, err := l.GetBlockHeaderByHeight(ctx, int64(height))
295261
if err != nil {
296-
return 0
262+
return 0, fmt.Errorf("unable to fetch block header: %w", err)
297263
}
298264

299-
ts := uint32(header.Timestamp.Unix())
300-
_, _ = l.blockTimestampCache.Put(height, cacheableTimestamp(ts))
301-
302-
return int64(ts)
265+
return blockHeader.Timestamp.Unix(), nil
303266
}
304267

305268
// PublishTransaction attempts to publish a new transaction to the
@@ -472,7 +435,14 @@ func (l *ProofChainLookup) MeanBlockTimestamp(ctx context.Context,
472435
break
473436
}
474437

475-
unixTs := l.chainBridge.GetBlockTimestamp(ctx, blockHeight-i)
438+
unixTs, err := l.chainBridge.GetBlockTimestamp(
439+
ctx, blockHeight-i,
440+
)
441+
if err != nil {
442+
return time.Time{}, fmt.Errorf("unable to fetch block "+
443+
"header timestamp: %w", err)
444+
}
445+
476446
if unixTs == 0 {
477447
return time.Time{}, fmt.Errorf("couldn't find "+
478448
"timestamp for block height %d", blockHeight)

rpcserver.go

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,9 +1259,15 @@ func (r *rpcServer) MarshalChainAsset(ctx context.Context, a asset.ChainAsset,
12591259

12601260
// Ensure the block timestamp is set if a block height is set.
12611261
if a.AnchorBlockTimestamp == 0 && a.AnchorBlockHeight > 0 {
1262-
a.AnchorBlockTimestamp = r.cfg.ChainBridge.GetBlockTimestamp(
1262+
timestamp, err := r.cfg.ChainBridge.GetBlockTimestamp(
12631263
ctx, a.AnchorBlockHeight,
12641264
)
1265+
if err != nil {
1266+
return nil, fmt.Errorf("unable to fetch block header "+
1267+
"timestamp: %w", err)
1268+
}
1269+
1270+
a.AnchorBlockTimestamp = timestamp
12651271
}
12661272

12671273
return rpcutils.MarshalChainAsset(
@@ -7761,24 +7767,31 @@ func (r *rpcServer) UniverseStats(ctx context.Context,
77617767
// marshalAssetSyncSnapshot maps a universe asset sync stat snapshot to the RPC
77627768
// counterpart.
77637769
func (r *rpcServer) marshalAssetSyncSnapshot(ctx context.Context,
7764-
a universe.AssetSyncSnapshot) *unirpc.AssetStatsSnapshot {
7770+
a universe.AssetSyncSnapshot) (*unirpc.AssetStatsSnapshot, error) {
77657771

77667772
resp := &unirpc.AssetStatsSnapshot{
77677773
TotalSyncs: int64(a.TotalSyncs),
77687774
TotalProofs: int64(a.TotalProofs),
77697775
GroupSupply: int64(a.GroupSupply),
77707776
}
7777+
7778+
blockTimestamp, err := r.cfg.ChainBridge.GetBlockTimestamp(
7779+
ctx, a.GenesisHeight,
7780+
)
7781+
if err != nil {
7782+
return nil, fmt.Errorf("failed to query block header "+
7783+
"timestamp for genesis height: %w", err)
7784+
}
7785+
77717786
rpcAsset := &unirpc.AssetStatsAsset{
7772-
AssetId: a.AssetID[:],
7773-
GenesisPoint: a.GenesisPoint.String(),
7774-
AssetName: a.AssetName,
7775-
AssetType: taprpc.AssetType(a.AssetType),
7776-
TotalSupply: int64(a.TotalSupply),
7777-
GenesisHeight: int32(a.GenesisHeight),
7778-
GenesisTimestamp: r.cfg.ChainBridge.GetBlockTimestamp(
7779-
ctx, a.GenesisHeight,
7780-
),
7781-
AnchorPoint: a.AnchorPoint.String(),
7787+
AssetId: a.AssetID[:],
7788+
GenesisPoint: a.GenesisPoint.String(),
7789+
AssetName: a.AssetName,
7790+
AssetType: taprpc.AssetType(a.AssetType),
7791+
TotalSupply: int64(a.TotalSupply),
7792+
GenesisHeight: int32(a.GenesisHeight),
7793+
GenesisTimestamp: blockTimestamp,
7794+
AnchorPoint: a.AnchorPoint.String(),
77827795
}
77837796

77847797
decDisplay, err := r.cfg.AddrBook.DecDisplayForAssetID(ctx, a.AssetID)
@@ -7795,7 +7808,7 @@ func (r *rpcServer) marshalAssetSyncSnapshot(ctx context.Context,
77957808
resp.Asset = rpcAsset
77967809
}
77977810

7798-
return resp
7811+
return resp, nil
77997812
}
78007813

78017814
// QueryAssetStats returns a set of statistics for a given set of assets.
@@ -7839,7 +7852,13 @@ func (r *rpcServer) QueryAssetStats(ctx context.Context,
78397852
),
78407853
}
78417854
for idx, snapshot := range assetStats.SyncStats {
7842-
resp.AssetStats[idx] = r.marshalAssetSyncSnapshot(ctx, snapshot)
7855+
rpcSnapshot, err := r.marshalAssetSyncSnapshot(ctx, snapshot)
7856+
if err != nil {
7857+
return nil, fmt.Errorf("failed to marshal asset "+
7858+
"snapshot: %w", err)
7859+
}
7860+
7861+
resp.AssetStats[idx] = rpcSnapshot
78437862
}
78447863

78457864
return resp, nil

tapgarden/interface.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ type ChainBridge interface {
348348

349349
// GetBlockTimestamp returns the timestamp of the block at the given
350350
// height.
351-
GetBlockTimestamp(context.Context, uint32) int64
351+
GetBlockTimestamp(context.Context, uint32) (int64, error)
352352

353353
// GetBlockHeaderByHeight returns a block header given the block height.
354354
GetBlockHeaderByHeight(ctx context.Context,

tapgarden/mock.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -734,8 +734,10 @@ func (m *MockChainBridge) CurrentHeight(_ context.Context) (uint32, error) {
734734
return 0, nil
735735
}
736736

737-
func (m *MockChainBridge) GetBlockTimestamp(_ context.Context, _ uint32) int64 {
738-
return 0
737+
func (m *MockChainBridge) GetBlockTimestamp(_ context.Context, _ uint32) (int64,
738+
error) {
739+
740+
return 0, nil
739741
}
740742

741743
func (m *MockChainBridge) PublishTransaction(_ context.Context,

universe/supplycommit/mock.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/lightninglabs/taproot-assets/fn"
1515
"github.com/lightninglabs/taproot-assets/mssmt"
1616
"github.com/lightninglabs/taproot-assets/proof"
17+
"github.com/lightninglabs/taproot-assets/tapgarden"
1718
"github.com/lightninglabs/taproot-assets/tapsend"
1819
"github.com/lightningnetwork/lnd/chainntnfs"
1920
lfn "github.com/lightningnetwork/lnd/fn/v2"
@@ -268,10 +269,10 @@ func (m *mockChainBridge) VerifyBlock(ctx context.Context,
268269
}
269270

270271
func (m *mockChainBridge) GetBlockTimestamp(ctx context.Context,
271-
height uint32) int64 {
272+
height uint32) (int64, error) {
272273

273274
args := m.Called(ctx, height)
274-
return args.Get(0).(int64)
275+
return args.Get(0).(int64), args.Error(1)
275276
}
276277

277278
func (m *mockChainBridge) GenFileChainLookup(f *proof.File) asset.ChainLookup {
@@ -289,6 +290,9 @@ func (m *mockChainBridge) GenProofChainLookup(
289290
return args.Get(0).(asset.ChainLookup), args.Error(1)
290291
}
291292

293+
// Ensure mockChainBridge implements the tapgarden.ChainBridge interface.
294+
var _ tapgarden.ChainBridge = (*mockChainBridge)(nil)
295+
292296
// mockStateMachineStore is a mock implementation of the StateMachineStore
293297
// interface.
294298
type mockStateMachineStore struct {

0 commit comments

Comments
 (0)