Skip to content

Commit

Permalink
Merge pull request #1140 from lightninglabs/add-tls-support-to-exampl…
Browse files Browse the repository at this point in the history
…e-price-oracle

examples: add TLS support to basic price oracle service
  • Loading branch information
Roasbeef authored Oct 7, 2024
2 parents 65539b6 + 2d0b4a4 commit 7ebb640
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 11 deletions.
1 change: 1 addition & 0 deletions docs/examples/basic-price-oracle/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ replace github.com/lightninglabs/taproot-assets => ../../../

require (
github.com/lightninglabs/taproot-assets v0.0.0
github.com/sirupsen/logrus v1.9.2
google.golang.org/grpc v1.59.0
)

Expand Down
16 changes: 16 additions & 0 deletions docs/examples/basic-price-oracle/go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo=
github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
Expand All @@ -6,8 +9,17 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y=
github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
Expand All @@ -22,3 +34,7 @@ google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
162 changes: 151 additions & 11 deletions docs/examples/basic-price-oracle/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,60 @@
package main

import (
"bytes"
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/hex"
"encoding/pem"
"fmt"
"io"
"log"
"math/big"
"net"
"os"
"time"

"github.com/sirupsen/logrus"

oraclerpc "github.com/lightninglabs/taproot-assets/taprpc/priceoraclerpc"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/credentials"
)

const (
// serviceListenAddress is the listening address of the service.
serviceListenAddress = "localhost:8095"
)

// setupLogger sets up the logger to write logs to a file.
func setupLogger() {
// Create a log file.
flags := os.O_CREATE | os.O_WRONLY | os.O_APPEND
file, err := os.OpenFile("basic-price-oracle-example.log", flags, 0666)
if err != nil {
logrus.Fatalf("Failed to open log file: %v", err)
}

// Create a multi-writer to write to both stdout and the file.
multiWriter := io.MultiWriter(os.Stdout, file)

// Set the output of logrus to the multi-writer.
logrus.SetOutput(multiWriter)

// Set the log level (optional).
logrus.SetLevel(logrus.DebugLevel)

// Set the log format (optional).
logrus.SetFormatter(&logrus.TextFormatter{
FullTimestamp: true,
})
}

// RpcPriceOracleServer is a basic example RPC price oracle server.
type RpcPriceOracleServer struct {
oraclerpc.UnimplementedPriceOracleServer
Expand All @@ -31,15 +70,35 @@ type RpcPriceOracleServer struct {
func isSupportedSubjectAsset(subjectAsset *oraclerpc.AssetSpecifier) bool {
// Ensure that the subject asset is set.
if subjectAsset == nil {
logrus.Info("Subject asset is not set (nil)")
return false
}

// In this example we'll only support a single asset.
assetIdStr := subjectAsset.GetAssetIdStr()
supportedAssetId := "7b4336d33b019df9438e586f83c587ca00fa65602497b9" +
supportedAssetIdStr := "7b4336d33b019df9438e586f83c587ca00fa65602497b9" +
"3ace193e9ce53b1a67"
supportedAssetIdBytes, err := hex.DecodeString(supportedAssetIdStr)
if err != nil {
fmt.Println("Error decoding supported asset hex string:", err)
return false
}

return assetIdStr == supportedAssetId
// Check the subject asset bytes if set.
subjectAssetIdBytes := subjectAsset.GetAssetId()
if len(subjectAssetIdBytes) > 0 {
logrus.Infof("Subject asset ID bytes populated: %x",
supportedAssetIdBytes)
return bytes.Equal(supportedAssetIdBytes, subjectAssetIdBytes)
}

subjectAssetIdStr := subjectAsset.GetAssetIdStr()
if len(subjectAssetIdStr) > 0 {
logrus.Infof("Subject asset ID str populated: %s",
supportedAssetIdStr)
return subjectAssetIdStr == supportedAssetIdStr
}

logrus.Infof("Subject asset ID not set")
return false
}

// getRateTick returns a rate tick for a given transaction type and subject
Expand Down Expand Up @@ -106,6 +165,8 @@ func (p *RpcPriceOracleServer) QueryRateTick(_ context.Context,
// Ensure that the payment asset is BTC. We only support BTC as the
// payment asset in this example.
if !oraclerpc.IsAssetBtc(req.PaymentAsset) {
logrus.Infof("Payment asset is not BTC: %v", req.PaymentAsset)

return &oraclerpc.QueryRateTickResponse{
Result: &oraclerpc.QueryRateTickResponse_Error{
Error: &oraclerpc.QueryRateTickErrResponse{
Expand All @@ -118,11 +179,15 @@ func (p *RpcPriceOracleServer) QueryRateTick(_ context.Context,

// Ensure that the subject asset is set.
if req.SubjectAsset == nil {
logrus.Info("Subject asset is not set")
return nil, fmt.Errorf("subject asset is not set")
}

// Ensure that the subject asset is supported.
if !isSupportedSubjectAsset(req.SubjectAsset) {
logrus.Infof("Unsupported subject asset ID str: %v\n",
req.SubjectAsset)

return &oraclerpc.QueryRateTickResponse{
Result: &oraclerpc.QueryRateTickResponse_Error{
Error: &oraclerpc.QueryRateTickErrResponse{
Expand All @@ -139,16 +204,24 @@ func (p *RpcPriceOracleServer) QueryRateTick(_ context.Context,
// If a rate tick hint is provided, return it as the rate tick.
// In doing so, we effectively accept the rate tick proposed by
// our peer.
logrus.Info("Suggested asset to BTC rate provided, " +
"returning rate as accepted rate")

rateTick.Rate = req.RateTickHint.Rate
rateTick.ExpiryTimestamp = req.RateTickHint.ExpiryTimestamp
} else {
// If a rate tick hint is not provided, fetch a rate tick from
// our internal system.
logrus.Info("Suggested asset to BTC rate not provided, " +
"querying internal system for rate")

rateTick = getRateTick(
req.TransactionType, req.SubjectAssetMaxAmount,
)
}

logrus.Infof("QueryRateTick returning rate: %v", rateTick.Rate)

return &oraclerpc.QueryRateTickResponse{
Result: &oraclerpc.QueryRateTickResponse_Success{
Success: &oraclerpc.QueryRateTickSuccessResponse{
Expand All @@ -162,7 +235,8 @@ func (p *RpcPriceOracleServer) QueryRateTick(_ context.Context,
// shut down.
func startService(grpcServer *grpc.Server) error {
serviceAddr := fmt.Sprintf("rfqrpc://%s", serviceListenAddress)
println("Starting RPC price oracle service at address: ", serviceAddr)
logrus.Infof("Starting RPC price oracle service at address: %s\n",
serviceAddr)

server := RpcPriceOracleServer{}
oraclerpc.RegisterPriceOracleServer(grpcServer, &server)
Expand All @@ -174,12 +248,78 @@ func startService(grpcServer *grpc.Server) error {
return grpcServer.Serve(grpcListener)
}

// Generate a self-signed TLS certificate and private key.
func generateSelfSignedCert() (tls.Certificate, error) {
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return tls.Certificate{}, err
}

keyUsage := x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature
extKeyUsage := []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
template := x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
Organization: []string{"basic-price-oracle"},
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(24 * time.Hour), // Valid for 1 day

KeyUsage: keyUsage,
ExtKeyUsage: extKeyUsage,
BasicConstraintsValid: true,
}

certDER, err := x509.CreateCertificate(
rand.Reader, &template, &template, &privateKey.PublicKey,
privateKey,
)
if err != nil {
return tls.Certificate{}, err
}

privateKeyBits, err := x509.MarshalECPrivateKey(privateKey)
if err != nil {
return tls.Certificate{}, err
}

certPEM := pem.EncodeToMemory(
&pem.Block{Type: "CERTIFICATE", Bytes: certDER},
)
keyPEM := pem.EncodeToMemory(
&pem.Block{Type: "EC PRIVATE KEY", Bytes: privateKeyBits},
)

tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
if err != nil {
return tls.Certificate{}, err
}

return tlsCert, nil
}

func main() {
setupLogger()

// Start the mock RPC price oracle service.
serverOpts := []grpc.ServerOption{
grpc.Creds(insecure.NewCredentials()),
//
// Generate self-signed certificate. This allows us to use TLS for the
// gRPC server.
tlsCert, err := generateSelfSignedCert()
if err != nil {
log.Fatalf("Failed to generate TLS certificate: %v", err)
}
backendService := grpc.NewServer(serverOpts...)
_ = startService(backendService)
backendService.Stop()

// Create the gRPC server with TLS
transportCredentials := credentials.NewTLS(&tls.Config{
Certificates: []tls.Certificate{tlsCert},
})
backendService := grpc.NewServer(grpc.Creds(transportCredentials))

err = startService(backendService)
if err != nil {
log.Fatalf("Start service error: %v", err)
}

backendService.GracefulStop()
}

0 comments on commit 7ebb640

Please sign in to comment.