Skip to content

Add price oracle proxy #1397

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

Open
wants to merge 5 commits into
base: main
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
7 changes: 7 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/lightninglabs/taproot-assets/tapdb"
"github.com/lightninglabs/taproot-assets/tapfreighter"
"github.com/lightninglabs/taproot-assets/tapgarden"
"github.com/lightninglabs/taproot-assets/taprpc/priceoraclerpc"
"github.com/lightninglabs/taproot-assets/universe"
"github.com/lightningnetwork/lnd"
"github.com/lightningnetwork/lnd/build"
Expand Down Expand Up @@ -52,6 +53,8 @@ type RPCConfig struct {

AllowPublicStats bool

AllowPublicPriceOracle bool

LetsEncryptDir string

LetsEncryptListen string
Expand Down Expand Up @@ -197,6 +200,10 @@ type Config struct {

PriceOracle rfq.PriceOracle

// ProxyPriceOracle is an optional price oracle that can be used to
// proxy price requests to another price oracle.
ProxyPriceOracle priceoraclerpc.PriceOracleClient

UniverseStats universe.Telemetry

AuxLeafSigner *tapchannel.AuxLeafSigner
Expand Down
21 changes: 21 additions & 0 deletions itest/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"time"

"github.com/lightninglabs/taproot-assets/taprpc"
"github.com/lightninglabs/taproot-assets/taprpc/priceoraclerpc"
"github.com/lightningnetwork/lnd/lntest"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -129,3 +130,23 @@ func testGetInfo(t *harnessTest) {
// Ensure the response matches the expected response.
require.Equal(t.t, resp, respCli)
}

// testPriceOracleProxy tests the price oracle proxy.
func testPriceOracleProxy(t *harnessTest) {
ctxb := context.Background()
ctxt, cancel := context.WithTimeout(ctxb, defaultWaitTimeout)
defer cancel()

resp, err := t.tapd.PriceOracleClient.QueryAssetRates(
ctxt, &priceoraclerpc.QueryAssetRatesRequest{},
)
require.NoError(t.t, err)

okResp := resp.GetOk()
require.NotNil(t.t, okResp)

require.Equal(
t.t, fmt.Sprintf("%v", mockPriceOracleCoef),
okResp.AssetRates.SubjectAssetRate.Coefficient,
)
}
9 changes: 8 additions & 1 deletion itest/tapd_harness.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/lightninglabs/taproot-assets/taprpc"
"github.com/lightninglabs/taproot-assets/taprpc/assetwalletrpc"
"github.com/lightninglabs/taproot-assets/taprpc/mintrpc"
"github.com/lightninglabs/taproot-assets/taprpc/priceoraclerpc"
"github.com/lightninglabs/taproot-assets/taprpc/rfqrpc"
tchrpc "github.com/lightninglabs/taproot-assets/taprpc/tapchannelrpc"
"github.com/lightninglabs/taproot-assets/taprpc/tapdevrpc"
Expand Down Expand Up @@ -83,6 +84,10 @@ const (
// timeout we'll use for waiting for a receiver to acknowledge a proof
// transfer.
defaultProofTransferReceiverAckTimeout = 500 * time.Millisecond

// mockPriceOracleCoef is the coefficient used in the mock price oracle
// to calculate the asset rate.
mockPriceOracleCoef = 5_820_600
)

// tapdHarness is a test harness that holds everything that is needed to
Expand All @@ -102,6 +107,7 @@ type tapdHarness struct {
tchrpc.TaprootAssetChannelsClient
universerpc.UniverseClient
tapdevrpc.TapDevClient
priceoraclerpc.PriceOracleClient
}

// tapdConfig holds all configuration items that are required to start a tapd
Expand Down Expand Up @@ -223,7 +229,7 @@ func newTapdHarness(t *testing.T, ht *harnessTest, cfg tapdConfig,
Rfq: rfq.CliConfig{
//nolint:lll
PriceOracleAddress: rfq.MockPriceOracleServiceAddress,
MockOracleAssetsPerBTC: 5_820_600,
MockOracleAssetsPerBTC: mockPriceOracleCoef,
},
}

Expand Down Expand Up @@ -425,6 +431,7 @@ func (hs *tapdHarness) start(expectErrExit bool) error {
)
hs.UniverseClient = universerpc.NewUniverseClient(rpcConn)
hs.TapDevClient = tapdevrpc.NewTapDevClient(rpcConn)
hs.PriceOracleClient = priceoraclerpc.NewPriceOracleClient(rpcConn)

return nil
}
Expand Down
4 changes: 4 additions & 0 deletions itest/test_list_on_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,10 @@ var testCases = []*testCase{
name: "asset signing after lnd restore from seed",
test: testRestoreLndFromSeed,
},
{
name: "price oracle proxy",
test: testPriceOracleProxy,
},
}

var optionalTestCases = []*testCase{
Expand Down
12 changes: 11 additions & 1 deletion perms/perms.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,10 @@ var (
Entity: "assets",
Action: "write",
}},
"/priceoraclerpc.PriceOracle/QueryAssetRates": {{
Entity: "priceoracle",
Action: "read",
}},
}

// defaultMacaroonWhitelist defines a default set of RPC endpoints that
Expand All @@ -332,7 +336,8 @@ var (
// macaroon authentication.
func MacaroonWhitelist(allowUniPublicAccessRead bool,
allowUniPublicAccessWrite bool, allowPublicUniProofCourier bool,
allowPublicStats bool) map[string]struct{} {
allowPublicStats bool, allowPublicPriceOracle bool,
) map[string]struct{} {

// Make a copy of the default whitelist.
whitelist := make(map[string]struct{})
Expand All @@ -357,5 +362,10 @@ func MacaroonWhitelist(allowUniPublicAccessRead bool,
whitelist["/universerpc.Universe/QueryEvents"] = struct{}{}
}

// Conditionally add public price oracle RPC endpoints to the whitelist.
if allowPublicPriceOracle {
whitelist["/priceoraclerpc.PriceOracle/QueryAssetRates"] = struct{}{} // nolint: lll
}

return whitelist
}
31 changes: 31 additions & 0 deletions rfq/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ import (
"github.com/lightninglabs/taproot-assets/fn"
"github.com/lightninglabs/taproot-assets/rfqmath"
"github.com/lightninglabs/taproot-assets/rfqmsg"
"github.com/lightninglabs/taproot-assets/taprpc/priceoraclerpc"
oraclerpc "github.com/lightninglabs/taproot-assets/taprpc/priceoraclerpc"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/mock"
"google.golang.org/grpc"
)

// MockPriceOracle is a mock implementation of the PriceOracle interface.
Expand Down Expand Up @@ -113,6 +116,34 @@ func (m *MockPriceOracle) QueryBidPrice(ctx context.Context,
return resp, args.Error(1)
}

// QueryAssetRates is a mock implementation of the QueryAssetRates method.
func (m *MockPriceOracle) QueryAssetRates(ctx context.Context,
in *priceoraclerpc.QueryAssetRatesRequest, opts ...grpc.CallOption,
) (*priceoraclerpc.QueryAssetRatesResponse, error) {

// Unmarshal the subject asset to BTC rate.
rpcRate := &priceoraclerpc.FixedPoint{
Coefficient: m.assetToBtcRate.Coefficient.String(),
Scale: uint32(m.assetToBtcRate.Scale),
}

return &priceoraclerpc.QueryAssetRatesResponse{
Result: &priceoraclerpc.QueryAssetRatesResponse_Ok{
Ok: &priceoraclerpc.QueryAssetRatesOkResponse{
AssetRates: &priceoraclerpc.AssetRates{
SubjectAssetRate: rpcRate,
},
},
},
}, nil
}

// Client returns a client that can be used to interact with the mock price
// oracle.
func (m *MockPriceOracle) Client() oraclerpc.PriceOracleClient {
return m
}

// Ensure that MockPriceOracle implements the PriceOracle interface.
var _ PriceOracle = (*MockPriceOracle)(nil)

Expand Down
8 changes: 8 additions & 0 deletions rfq/oracle.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ type PriceOracle interface {
paymentMaxAmt fn.Option[lnwire.MilliSatoshi],
assetRateHint fn.Option[rfqmsg.AssetRate]) (
*OracleResponse, error)

// RawClient returns the underlying RPC client.
Client() oraclerpc.PriceOracleClient
}

// RpcPriceOracle is a price oracle that uses an external RPC server to get
Expand Down Expand Up @@ -404,5 +407,10 @@ func (r *RpcPriceOracle) QueryBidPrice(ctx context.Context,
}
}

// Client returns the underlying RPC client.
func (r *RpcPriceOracle) Client() oraclerpc.PriceOracleClient {
return r.client
}

// Ensure that RpcPriceOracle implements the PriceOracle interface.
var _ PriceOracle = (*RpcPriceOracle)(nil)
26 changes: 26 additions & 0 deletions rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import (
"github.com/lightninglabs/taproot-assets/taprpc"
wrpc "github.com/lightninglabs/taproot-assets/taprpc/assetwalletrpc"
"github.com/lightninglabs/taproot-assets/taprpc/mintrpc"
"github.com/lightninglabs/taproot-assets/taprpc/priceoraclerpc"
"github.com/lightninglabs/taproot-assets/taprpc/rfqrpc"
tchrpc "github.com/lightninglabs/taproot-assets/taprpc/tapchannelrpc"
"github.com/lightninglabs/taproot-assets/taprpc/tapdevrpc"
Expand All @@ -70,6 +71,8 @@ import (
"golang.org/x/exp/maps"
"golang.org/x/time/rate"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

var (
Expand All @@ -84,6 +87,11 @@ var (
// P2TRChangeType is the type of change address that should be used for
// funding PSBTs, as we'll always want to use P2TR change addresses.
P2TRChangeType = walletrpc.ChangeAddressType_CHANGE_ADDRESS_TYPE_P2TR

// ErrPriceOracleUnimplemented is the error returned when the price
// oracle service is unimplemented.
ErrPriceOracleUnimplemented = status.Error(
codes.Unimplemented, "price oracle service is unimplemented")
)

const (
Expand Down Expand Up @@ -161,6 +169,7 @@ type rpcServer struct {
tchrpc.UnimplementedTaprootAssetChannelsServer
tapdevrpc.UnimplementedTapDevServer
unirpc.UnimplementedUniverseServer
priceoraclerpc.UnimplementedPriceOracleServer

interceptor signal.Interceptor

Expand Down Expand Up @@ -230,6 +239,8 @@ func (r *rpcServer) RegisterWithGrpcServer(grpcServer *grpc.Server) error {
tchrpc.RegisterTaprootAssetChannelsServer(grpcServer, r)
unirpc.RegisterUniverseServer(grpcServer, r)
tapdevrpc.RegisterGrpcServer(grpcServer, r)
priceoraclerpc.RegisterPriceOracleServer(grpcServer, r)

return nil
}

Expand Down Expand Up @@ -8259,3 +8270,18 @@ func (r *rpcServer) RegisterTransfer(ctx context.Context,
RegisteredAsset: rpcAsset,
}, nil
}

// QueryAssetRates retrieves the exchange rate between a tap asset and BTC for
// a specified transaction type, subject asset, and payment asset. The asset
// rate represents the number of tap asset units per BTC.
// NOTE: This call is proxied to the underlying price oracle.
func (r *rpcServer) QueryAssetRates(ctx context.Context,
req *priceoraclerpc.QueryAssetRatesRequest) (
*priceoraclerpc.QueryAssetRatesResponse, error) {

if r.cfg.ProxyPriceOracle == nil {
return nil, ErrPriceOracleUnimplemented
}

return r.cfg.ProxyPriceOracle.QueryAssetRates(ctx, req)
}
3 changes: 3 additions & 0 deletions sample-tapd.conf
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@
; Disable macaroon authentication for stats RPC endpoints
; allow-public-stats=false

; Disble macaroon authentication for price oracle proxy RPC endpoints
; allow-public-price-oracle=false

; Add an ip:port/hostname to allow cross origin access from
; To allow all origins, set as "*"
; restcors=
Expand Down
1 change: 1 addition & 0 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ func (s *Server) RunUntilShutdown(mainErrChan <-chan error) error {
s.cfg.UniversePublicAccess.IsWriteAccessGranted(),
s.cfg.RPCConfig.AllowPublicUniProofCourier,
s.cfg.RPCConfig.AllowPublicStats,
s.cfg.RPCConfig.AllowPublicPriceOracle,
)

// Create a new RPC interceptor that we'll add to the GRPC server. This
Expand Down
1 change: 1 addition & 0 deletions tapcfg/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ type RpcConfig struct {

AllowPublicUniProofCourier bool `long:"allow-public-uni-proof-courier" description:"Disable macaroon authentication for universe proof courier RPC endpoints."`
AllowPublicStats bool `long:"allow-public-stats" description:"Disable macaroon authentication for stats RPC endpoints."`
AllowPublicPriceOracle bool `long:"allow-public-price-oracle" description:"Disable macaroon authentication for price oracle RPC endpoints."`

RestCORS []string `long:"restcors" description:"Add an ip:port/hostname to allow cross origin access from. To allow all origins, set as \"*\"."`

Expand Down
5 changes: 5 additions & 0 deletions tapcfg/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/lightninglabs/taproot-assets/tapdb/sqlc"
"github.com/lightninglabs/taproot-assets/tapfreighter"
"github.com/lightninglabs/taproot-assets/tapgarden"
"github.com/lightninglabs/taproot-assets/taprpc/priceoraclerpc"
"github.com/lightninglabs/taproot-assets/tapscript"
"github.com/lightninglabs/taproot-assets/universe"
"github.com/lightningnetwork/lnd"
Expand Down Expand Up @@ -366,6 +367,7 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger,
// Determine whether we should use the mock price oracle service or a
// real price oracle service.
var priceOracle rfq.PriceOracle
var rpcPriceOracleClient priceoraclerpc.PriceOracleClient

rfqCfg := cfg.Experimental.Rfq
switch rfqCfg.PriceOracleAddress {
Expand All @@ -381,6 +383,7 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger,
3600, rfqCfg.MockOracleSatsPerAsset,
)
}
rpcPriceOracleClient = priceOracle.Client()

case "":
// Leave the price oracle as nil, which will cause the RFQ
Expand All @@ -395,6 +398,7 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger,
return nil, fmt.Errorf("unable to create price "+
"oracle: %w", err)
}
rpcPriceOracleClient = priceOracle.Client()
}

// Construct the RFQ manager.
Expand Down Expand Up @@ -590,6 +594,7 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger,
UniverseQueriesBurst: cfg.Universe.UniverseQueriesBurst,
RfqManager: rfqManager,
PriceOracle: priceOracle,
ProxyPriceOracle: rpcPriceOracleClient,
AuxLeafSigner: auxLeafSigner,
AuxFundingController: auxFundingController,
AuxChanCloser: auxChanCloser,
Expand Down
Loading