Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Only fetch onchain transactions from LND within start_time and end_time range #202

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 31 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,
blockRangeLookup func(start, end time.Time) (uint32, uint32, error),
disableFiat bool, txLookup fees.GetDetailsFunc,
priceCfg *fiat.PriceSourceConfig,
categories []CustomCategory) *OnChainConfig {

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

// Set both start and end height to 0, meaning we will query for all
// onchain history.
startHeight := uint32(0)
endHeight := uint32(0)

if blockRangeLookup != nil {
var err error

startHeight, endHeight, err = blockRangeLookup(startTime, endTime)
if err != nil {
log.Errorf("Error finding block height range for start time: %v "+
"end time: %v error: %v", startTime, endTime, err)

// If we cannot find the block height range, set both start and end
// height to 0, meaning we will query for all onchain history.
startHeight = 0
endHeight = 0
}
}

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

return &OnChainConfig{
OpenChannels: lndwrap.ListChannels(
ctx, lnd.Client, false,
Expand All @@ -120,10 +145,12 @@ 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
147 changes: 146 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,21 @@ func parseNodeAuditRequest(ctx context.Context, cfg *Config,
"backend, some fee entries will be missing (see logs)")
}

var blockRangeLookup func(start, end time.Time) (uint32, 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 {
blockRangeLookup = func(start, end time.Time) (uint32, uint32, error) {
return resolveBlockHeightRange(
ctx, cfg.Lnd, info.BlockHeight, start, end,
)
}
}

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

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

// resolveBlockHeightRange determines the block height range that should be
// used for in queries based on the start and end time of the report.
// The function will apply a buffer to ensure the block height range is
// too large rather than too small, so that all relevant transactions are
// fetched from the backend.
func resolveBlockHeightRange(ctx context.Context,
lndClient lndclient.LndServices, latestHeight uint32,
startTime, endTime time.Time) (uint32, uint32, error) {

// Since Bitcoin blocks are not guaranteed to be completely ordered
// by timestamp, and the timestamps can be manipulated by miners within a
// certain range, we will apply a buffer on the time which we use to
// find the block height. This should ensure we use a low enough
// height to fetch all relevant transactions following the start time.
bufferedStartTime := startTime.Add(time.Hour * -24)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: probably would make sense to extract this range value into a named variable at the start of the file.


if bufferedStartTime.Before(time.Unix(0, 0)) {
bufferedStartTime = time.Unix(0, 0)
}

startHeight, err := findFirstBlockBeforeTimestamp(
ctx, lndClient, latestHeight, bufferedStartTime,
)
if err != nil {
return 0, 0, err
}

// Since Bitcoin blocks are not guaranteed to be completely ordered
// by timestamp, and the timestamps can be manipulated by miners within a
// certain range, we will apply a buffer on the time which we use to
// find the block height. This should ensure we use a high enough
// height to fetch all relevant transactions up to the end time.
bufferedEndTime := endTime.Add(time.Hour * 24)

endHeight, err := findFirstBlockBeforeTimestamp(
ctx, lndClient, latestHeight, bufferedEndTime,
)
if err != nil {
return 0, 0, err
}

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

// If startHeight somehow ended up being greater than endHeight,
// set both start and end height to 0, meaning we will query for
// all onchain history.
startHeight = 0
endHeight = 0
}

return startHeight, endHeight, nil
}

// 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.Debugf("Binary search done for targetTimestamp: %v. "+
"Returning height: %v", targetTimestamp, high)

// Closest block before the timestamp.
return high, nil
}

// 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
}
Loading