Skip to content

Commit

Permalink
Only fetch onchain transactions from LND within start_time and end_ti…
Browse files Browse the repository at this point in the history
…me range
  • Loading branch information
nordbjorn committed Jan 15, 2025
1 parent a3aba5b commit 74518d2
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 5 deletions.
63 changes: 59 additions & 4 deletions accounting/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,10 @@ type CommonConfig struct {
// The txLookup function may be nil if a connection to a bitcoin backend is not
// available. If this is the case, the fee report will log warnings indicating
// that fee lookups are not possible in certain cases.
func NewOnChainConfig(ctx context.Context, lnd lndclient.LndServices, startTime,
endTime time.Time, disableFiat bool, txLookup fees.GetDetailsFunc,
func NewOnChainConfig(ctx context.Context,
lnd lndclient.LndServices, startTime, endTime time.Time,
blockHeightLookup func(targetTimestamp time.Time) (uint32, error),
disableFiat bool, txLookup fees.GetDetailsFunc,
priceCfg *fiat.PriceSourceConfig,
categories []CustomCategory) *OnChainConfig {

Expand All @@ -109,6 +111,58 @@ func NewOnChainConfig(ctx context.Context, lnd lndclient.LndServices, startTime,
}
}

// Start end end height can initially be set to 0, meaning we will query
// for all onchain history
startHeight := uint32(0)
endHeight := uint32(0)

// If a function to lookup block heights based on timestamps is provided,
// we will use it to find the block height for the start and end time.
if blockHeightLookup != nil {
var err error

startHeight, err = blockHeightLookup(startTime)
if err != nil {
log.Errorf("Error finding block height based on startTime: %v "+
"error: %v", startTime, err)

// If we cannot find the block height based on the start time,
// start looking from the genesis block.
startHeight = 0
} else {
// Subtract 3 blocks just to be sure we do not grab too few blocks
// and miss any transaction
// (with safe handling for uint32 underflow)
startHeight = uint32(max(0, int64(startHeight)-3))
}

endHeight, err = blockHeightLookup(endTime)
if err != nil {
log.Errorf("Error finding block height based on endHeight: %v "+
"error: %v", endHeight, err)

// If we cannot find the block height based on the end time,
// set both start and end height to 0, meaning we will query for
// all onchain history.
startHeight = 0
endHeight = 0
} else if endHeight != 0 {
// Add 3 blocks just to be sure we do not grab too few blocks
// and miss any transaction
endHeight += 3
}

if startHeight > endHeight {
log.Errorf("Start height: %v is greater than end height: %v, "+
"setting both to 0", startHeight, endHeight)
startHeight = 0
endHeight = 0
}
}

log.Infof("Using startheight: %v endheight: %v while querying onchain"+
"activity", startHeight, endHeight)

return &OnChainConfig{
OpenChannels: lndwrap.ListChannels(
ctx, lnd.Client, false,
Expand All @@ -120,10 +174,11 @@ func NewOnChainConfig(ctx context.Context, lnd lndclient.LndServices, startTime,
return lnd.Client.PendingChannels(ctx)
},
OnChainTransactions: func() ([]lndclient.Transaction, error) {
return lnd.Client.ListTransactions(ctx, 0, 0)
return lnd.Client.ListTransactions(ctx, int32(startHeight),
int32(endHeight))
},
ListSweeps: func() ([]string, error) {
return lnd.WalletKit.ListSweeps(ctx, 0)
return lnd.WalletKit.ListSweeps(ctx, int32(startHeight))
},
CommonConfig: CommonConfig{
StartTime: startTime,
Expand Down
86 changes: 85 additions & 1 deletion frdrpcserver/node_audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import (
"sort"
"time"

"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightninglabs/faraday/accounting"
"github.com/lightninglabs/faraday/fees"
"github.com/lightninglabs/faraday/fiat"
"github.com/lightninglabs/faraday/frdrpc"
"github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/shopspring/decimal"
)
Expand Down Expand Up @@ -84,8 +86,20 @@ func parseNodeAuditRequest(ctx context.Context, cfg *Config,
"backend, some fee entries will be missing (see logs)")
}

var blockHeightLookup func(targetTimestamp time.Time) (uint32, error)

// If a time range is set, we will use a block height lookup function
// to find the block heights for the start and end time.
timeRangeSet := req.StartTime > 0 || req.EndTime > 0
if timeRangeSet {
blockHeightLookup = func(targetTimestamp time.Time) (uint32, error) {
return findFirstBlockBeforeTimestamp(ctx, cfg.Lnd, info.BlockHeight,
targetTimestamp)
}
}

onChain := accounting.NewOnChainConfig(
ctx, cfg.Lnd, start, end, req.DisableFiat,
ctx, cfg.Lnd, start, end, blockHeightLookup, req.DisableFiat,
feeLookup, priceSourceCfg, onChainCategories,
)

Expand Down Expand Up @@ -278,3 +292,73 @@ func rpcEntryType(t accounting.EntryType) (frdrpc.EntryType, error) {
return 0, fmt.Errorf("unknown entrytype: %v", t)
}
}

// findFirstBlockBeforeTimestamp finds the block height from just before the
// given timestamp.
func findFirstBlockBeforeTimestamp(ctx context.Context,
lndClient lndclient.LndServices, latestHeight uint32,
targetTime time.Time) (uint32, error) {
targetTimestamp := targetTime.Unix()

// Set the search range to the genesis block and the latest block.
low := uint32(0)
high := latestHeight

// Perform binary search to find the block height that is just before the
// target timestamp.
for low <= high {
mid := (low + high) / 2

// Lookup the block in the middle of the search range.
blockHash, err := getBlockHash(ctx, lndClient, mid)
if err != nil {
return 0, err
}

blockTime, err := getBlockTimestamp(ctx, lndClient, blockHash)
if err != nil {
return 0, err
}

blockTimestamp := blockTime.Unix()
if blockTimestamp < targetTimestamp {
// If the block we looked up is before the target timestamp,
// we set the new low height to the next block after that
low = mid + 1
} else if blockTimestamp > targetTimestamp {
// If the block we looked up is after the target timestamp,
// we set the new high height to the block before that
high = mid - 1
} else {
// If we find an exact match of block timestamp and target
// timestamp, ruturn the height of this block.
return mid, nil
}
}

log.Infof("Binary search done for targetTimestamp: %v. "+
"Returning height: %v", targetTimestamp, high)
return high, nil // Closest block before the timestamp
}

// getBlockHash retrieves the block hash for a given height.
func getBlockHash(ctx context.Context, lndClient lndclient.LndServices,
height uint32) (chainhash.Hash, error) {
blockHash, err := lndClient.ChainKit.GetBlockHash(ctx, int64(height))
if err != nil {
return chainhash.Hash{}, err
}

return blockHash, nil
}

// getBlockTimestamp retrieves the block timestamp for a given block hash.
func getBlockTimestamp(ctx context.Context,
lndClient lndclient.LndServices, hash chainhash.Hash) (time.Time, error) {
blockHeader, err := lndClient.ChainKit.GetBlockHeader(ctx, hash)
if err != nil {
return time.Time{}, err
}

return blockHeader.Timestamp, nil
}

0 comments on commit 74518d2

Please sign in to comment.