mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
assets: validate RFQ timeout conversion
Convert the configured duration once during client creation. Round positive fractional durations up to the whole seconds accepted by tapd. Reject zero, negative, and overflowing values, and cover the conversion boundaries with unit tests.
This commit is contained in:
parent
514c3f06ad
commit
c7d5e466cd
2 changed files with 91 additions and 7 deletions
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
|
@ -78,14 +79,19 @@ type TapdClient struct {
|
|||
rfqrpc.RfqClient
|
||||
universerpc.UniverseClient
|
||||
|
||||
cfg *TapdConfig
|
||||
assetNameCache map[string]string
|
||||
assetNameMutex sync.Mutex
|
||||
cc *grpc.ClientConn
|
||||
rfqTimeoutSeconds uint32
|
||||
assetNameCache map[string]string
|
||||
assetNameMutex sync.Mutex
|
||||
cc *grpc.ClientConn
|
||||
}
|
||||
|
||||
// NewTapdClient returns a new taproot assets client.
|
||||
func NewTapdClient(config *TapdConfig) (*TapdClient, error) {
|
||||
rfqTimeoutSeconds, err := getRfqTimeoutSeconds(config.RFQtimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create the client connection to the server.
|
||||
conn, err := getClientConn(config)
|
||||
if err != nil {
|
||||
|
|
@ -96,7 +102,7 @@ func NewTapdClient(config *TapdConfig) (*TapdClient, error) {
|
|||
client := &TapdClient{
|
||||
assetNameCache: make(map[string]string),
|
||||
cc: conn,
|
||||
cfg: config,
|
||||
rfqTimeoutSeconds: rfqTimeoutSeconds,
|
||||
TaprootAssetsClient: taprpc.NewTaprootAssetsClient(conn),
|
||||
TaprootAssetChannelsClient: tapchannelrpc.NewTaprootAssetChannelsClient(conn),
|
||||
PriceOracleClient: priceoraclerpc.NewPriceOracleClient(conn),
|
||||
|
|
@ -139,7 +145,7 @@ func (c *TapdClient) GetRfqForAsset(ctx context.Context,
|
|||
PeerPubKey: peerPubkey,
|
||||
PaymentMaxAmt: uint64(paymentMaxAmt),
|
||||
Expiry: uint64(expiry),
|
||||
TimeoutSeconds: uint32(c.cfg.RFQtimeout.Seconds()),
|
||||
TimeoutSeconds: c.rfqTimeoutSeconds,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -220,7 +226,7 @@ func (c *TapdClient) GetAssetPrice(ctx context.Context, assetID string,
|
|||
},
|
||||
PaymentMaxAmt: uint64(msatAmt),
|
||||
Expiry: uint64(rfqExpiry),
|
||||
TimeoutSeconds: uint32(c.cfg.RFQtimeout.Seconds()),
|
||||
TimeoutSeconds: c.rfqTimeoutSeconds,
|
||||
PeerPubKey: peerPubkey,
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -288,6 +294,26 @@ func getPaymentMaxAmount(satAmount btcutil.Amount, feeLimitMultiplier float64) (
|
|||
)
|
||||
}
|
||||
|
||||
// getRfqTimeoutSeconds converts the configured RFQ timeout to the whole
|
||||
// seconds accepted by tapd. Fractional seconds are rounded up so tapd's
|
||||
// timeout is never shorter than the configured duration.
|
||||
func getRfqTimeoutSeconds(timeout time.Duration) (uint32, error) {
|
||||
if timeout <= 0 {
|
||||
return 0, fmt.Errorf("RFQ timeout must be greater than zero")
|
||||
}
|
||||
|
||||
seconds := timeout / time.Second
|
||||
if timeout%time.Second != 0 {
|
||||
seconds++
|
||||
}
|
||||
if seconds > time.Duration(math.MaxUint32) {
|
||||
return 0, fmt.Errorf("RFQ timeout exceeds maximum of %v seconds",
|
||||
uint64(math.MaxUint32))
|
||||
}
|
||||
|
||||
return uint32(seconds), nil
|
||||
}
|
||||
|
||||
func getClientConn(config *TapdConfig) (*grpc.ClientConn, error) {
|
||||
// Load the specified TLS certificate and build transport credentials.
|
||||
creds, err := credentials.NewClientTLSFromFile(config.TLSPath, "")
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@ package assets
|
|||
|
||||
import (
|
||||
"encoding/pem"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/lightninglabs/taproot-assets/taprpc/rfqrpc"
|
||||
|
|
@ -141,6 +143,62 @@ func TestGetPaymentMaxAmount(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestGetRfqTimeoutSeconds verifies that configured durations are safely
|
||||
// converted to tapd's whole-second timeout field.
|
||||
func TestGetRfqTimeoutSeconds(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
timeout time.Duration
|
||||
expectedSeconds uint32
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "whole seconds",
|
||||
timeout: 60 * time.Second,
|
||||
expectedSeconds: 60,
|
||||
},
|
||||
{
|
||||
name: "sub-second rounded up",
|
||||
timeout: time.Millisecond,
|
||||
expectedSeconds: 1,
|
||||
},
|
||||
{
|
||||
name: "fractional second rounded up",
|
||||
timeout: time.Second + time.Nanosecond,
|
||||
expectedSeconds: 2,
|
||||
},
|
||||
{
|
||||
name: "zero",
|
||||
timeout: 0,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "negative",
|
||||
timeout: -time.Second,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "overflow",
|
||||
timeout: time.Duration(math.MaxUint32)*time.Second +
|
||||
time.Nanosecond,
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
seconds, err := getRfqTimeoutSeconds(test.timeout)
|
||||
if test.expectError {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.expectedSeconds, seconds)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSatsFromAssetAmt(t *testing.T) {
|
||||
tests := []struct {
|
||||
assetAmt uint64
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue