From 5e2fce053e3ed25f76bc9e49e2693253f2b8a45d Mon Sep 17 00:00:00 2001 From: ffranr Date: Tue, 29 Aug 2023 18:35:21 +0100 Subject: [PATCH] WIP: proof: add multiverse RPC proof courier --- proof/courier.go | 185 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) diff --git a/proof/courier.go b/proof/courier.go index e2b616b87..4433639ed 100644 --- a/proof/courier.go +++ b/proof/courier.go @@ -5,6 +5,7 @@ import ( "context" "crypto/sha512" "crypto/tls" + "encoding/hex" "fmt" "net/url" "sync" @@ -14,6 +15,8 @@ import ( "github.com/lightninglabs/lightning-node-connect/hashmailrpc" "github.com/lightninglabs/taproot-assets/asset" "github.com/lightninglabs/taproot-assets/fn" + "github.com/lightninglabs/taproot-assets/taprpc" + unirpc "github.com/lightninglabs/taproot-assets/taprpc/universerpc" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" @@ -37,6 +40,10 @@ const ( // TODO(ffranr): Rename to HashmailCourier (use protocol name rather // than service). ApertureCourier = "hashmail" + + // UniverseRpcCourier is a courier that uses the daemon universe RPC + // endpoints to deliver proofs. + UniverseRpcCourierType = "universerpc" ) // CourierHarness interface is an integration testing harness for a proof @@ -100,6 +107,8 @@ func ParseCourierAddrUrl(addr url.URL) (CourierAddr, error) { switch addr.Scheme { case ApertureCourier: return NewHashMailCourierAddr(addr) + case UniverseRpcCourierType: + return NewUniverseRpcCourierAddr(addr) } return nil, fmt.Errorf("unknown courier address protocol: %v", @@ -153,6 +162,70 @@ func NewHashMailCourierAddr(addr url.URL) (*HashMailCourierAddr, error) { }, nil } +// UniverseRpcCourierAddr is a universe RPC protocol specific implementation of +// the CourierAddr interface. +type UniverseRpcCourierAddr struct { + addr url.URL +} + +// Url returns the url.URL representation of the courier address. +func (h *UniverseRpcCourierAddr) Url() *url.URL { + return &h.addr +} + +// NewCourier generates a new courier service handle. +func (h *UniverseRpcCourierAddr) NewCourier(_ context.Context, + cfg *CourierCfg) (Courier, error) { + + // Ensure that the courier address is a universe RPC address. + if h.addr.Scheme != UniverseRpcCourierType { + return nil, fmt.Errorf("unsupported courier protocol: %v", + h.addr.Scheme) + } + + // Connect to the universe RPC server. + dialOpts, err := serverDialOpts(cfg.HashMailCfg.TlsCertPath) + if err != nil { + return nil, err + } + + serverAddr := fmt.Sprintf( + "%s:%s", h.addr.Hostname(), h.addr.Port(), + ) + conn, err := grpc.Dial(serverAddr, dialOpts...) + if err != nil { + return nil, err + } + + client := unirpc.NewUniverseClient(conn) + + // Instantiate the events subscribers map. + subscribers := make( + map[uint64]*fn.EventReceiver[fn.Event], + ) + + return &UniverseRpcCourier{ + cfg: cfg.HashMailCfg, + client: client, + deliveryLog: cfg.DeliveryLog, + subscribers: subscribers, + }, nil +} + +// NewUniverseRpcCourierAddr generates a new universe RPC courier address from a +// given URL. This function also performs protocol specific address validation. +func NewUniverseRpcCourierAddr(addr url.URL) (*UniverseRpcCourierAddr, error) { + // We expect the port number to be specified. + if addr.Port() == "" { + return nil, fmt.Errorf("proof courier URI address port " + + "unspecified") + } + + return &UniverseRpcCourierAddr{ + addr, + }, nil +} + // NewCourier instantiates a new courier service handle given a service URL // address. func NewCourier(ctx context.Context, addr url.URL, cfg *CourierCfg) (Courier, @@ -844,6 +917,118 @@ func (h *HashMailCourier) SetSubscribers( // proof.Courier interface. var _ Courier = (*HashMailCourier)(nil) +// UniverseRpcCourier is a universe RPC proof courier service handle. It +// implements the Courier interface. +type UniverseRpcCourier struct { + // cfg contains the courier's configuration parameters. + cfg *HashMailCourierCfg + + // client is the RPC client that the courier will use to interact with + // the universe RPC server. + client unirpc.UniverseClient + + // deliveryLog is the log that the courier will use to record the + // attempted delivery of proofs to the receiver. + deliveryLog DeliveryLog + + // subscribers is a map of components that want to be notified on new + // events, keyed by their subscription ID. + subscribers map[uint64]*fn.EventReceiver[fn.Event] + + // subscriberMtx guards the subscribers map and access to the + // subscriptionID. + subscriberMtx sync.Mutex +} + +// DeliverProof attempts to delivery a proof to the receiver. +func (c *UniverseRpcCourier) DeliverProof(ctx context.Context, + recipient Recipient, annotatedProof *AnnotatedProof) error { + + // Construct universe key. + universeID := unirpc.ID{ + Id: &unirpc.ID_AssetId{ + AssetId: recipient.AssetID[:], + }, + } + + // Serialize the script key in compressed format. The RPC endpoint can + // handle a compressed script key. + scriptKeyBytes := recipient.ScriptKey.SerializeUncompressed() + + outPoint := annotatedProof.OutPoint + + assetKey := unirpc.AssetKey{ + Outpoint: &unirpc.AssetKey_Op{ + Op: &unirpc.Outpoint{ + HashStr: outPoint.Hash.String(), + Index: int32(outPoint.Index), + }, + }, + ScriptKey: &unirpc.AssetKey_ScriptKeyStr{ + ScriptKeyStr: hex.EncodeToString(scriptKeyBytes), + }, + } + + universeKey := unirpc.UniverseKey{ + Id: &universeID, + LeafKey: &assetKey, + } + + // Construct asset leaf. + rpcAsset, err := taprpc.MarshalAsset( + ctx, annotatedProof.Asset, true, true, nil, + ) + if err != nil { + return err + } + + // Extract the last proof from the annotated proof. + proofFile := &File{} + err = proofFile.Decode(bytes.NewReader(annotatedProof.Blob)) + if err != nil { + return err + } + rawProof, err := proofFile.RawLastProof() + if err != nil { + return err + } + + assetLeaf := unirpc.AssetLeaf{ + Asset: rpcAsset, + IssuanceProof: rawProof, + } + + // Submit proof to courier. + _, err = c.client.InsertProof(ctx, &unirpc.AssetProof{ + Key: &universeKey, + AssetLeaf: &assetLeaf, + }) + return err +} + +// ReceiveProof attempts to obtain a proof from the courier service. The proof +// is identified by the given locator. +func (c *UniverseRpcCourier) ReceiveProof(ctx context.Context, + recipient Recipient, loc Locator) (*AnnotatedProof, error) { + + return nil, nil +} + +// SetSubscribers sets the subscribers for the courier. This method is +// thread-safe. +func (c *UniverseRpcCourier) SetSubscribers( + subscribers map[uint64]*fn.EventReceiver[fn.Event]) { + + c.subscriberMtx.Lock() + defer c.subscriberMtx.Unlock() + + c.subscribers = subscribers +} + +// A compile-time assertion to ensure the UniverseRpcCourier meets the +// proof.Courier interface. +var _ Courier = (*UniverseRpcCourier)(nil) + // DeliveryLog is an interface that allows the courier to log the (attempted) // delivery of a proof. type DeliveryLog interface {