From 3c5814811676216983a63f447ff569fa6c0b6a69 Mon Sep 17 00:00:00 2001 From: Andras Banki-Horvath Date: Thu, 26 Feb 2026 20:40:09 +0100 Subject: [PATCH] client: wire mailbox round/OOR plumbing and toy SDK facade This commit introduces the client/runtime-side plumbing needed to run a real mailbox-backed round+OOR flow and expose it through a minimal SDK surface that systests can drive end-to-end. What is added: - Round mailbox wire protocol: - add `roundwire` payload schema/serialization for round request/response and server event envelopes. - add round mailbox codec + dispatchers to convert mailbox envelopes to existing round actor messages and vice versa. - OOR mailbox wire protocol: - add `oorwire` payload schema/serialization for submit/finalize package RPCs. - add mailbox outbox handler for OOR FSM side effects (submit, finalize, checkpoint-signing follow-through, and local spend marking). - Connector/runtime wiring: - extend `serverconn` actor/runtime dispatch integration so inbound mailbox round notifications are routed into the client round actor cleanly. - update `darepod/server` dispatch map construction for mailbox request/event handling through the existing actor system. - Toy SDK layer for e2e demonstration: - add `sdk/client.go` with a high-level API for round output request/join, recipient address generation, OOR send, incoming sync/materialization, and live balance/VTXO listing. - hide mailbox envelope plumbing from SDK consumers; keep advanced knobs at config boundaries. - Harness CI stability hardening: - extend container startup retry behavior to also retry transient image pull failures (e.g. registry "unknown blob") in addition to port bind conflicts. - add targeted unit tests for image-pull error detection and retry behavior. - Tests: - add SDK receive address tests and update round outbox tests to cover new mailbox payload encode/decode paths. Notes: - This is intentionally an incomplete/"toy" SDK path aimed at validating e2e plumbing shape. - Includes lint-driven cleanup in touched files. --- darepod/server.go | 64 +- harness/harness.go | 435 +++++- harness/port_retry_test.go | 102 +- oor/mailbox_outbox_handler.go | 221 +++ oorwire/oorwire.pb.go | 485 ++++++ oorwire/oorwire.proto | 75 + oorwire/oorwire_grpc.pb.go | 152 ++ oorwire/oorwire_mailboxrpc.pb.go | 101 ++ oorwire/payloads.go | 311 ++++ oorwire/payloads_test.go | 197 +++ round/mailbox_codec.go | 286 ++++ round/mailbox_dispatchers.go | 62 + round/outbox_messages.go | 429 +++++- round/outbox_messages_test.go | 24 +- roundwire/payloads.go | 590 +++++++ roundwire/roundwire.pb.go | 2111 ++++++++++++++++++++++++++ roundwire/roundwire.proto | 217 +++ roundwire/roundwire_grpc.pb.go | 550 +++++++ roundwire/roundwire_mailboxrpc.pb.go | 452 ++++++ scripts/gen_protos.sh | 6 + sdk/address_test.go | 66 + sdk/client.go | 792 ++++++++++ serverconn/actor.go | 72 +- 23 files changed, 7674 insertions(+), 126 deletions(-) create mode 100644 oor/mailbox_outbox_handler.go create mode 100644 oorwire/oorwire.pb.go create mode 100644 oorwire/oorwire.proto create mode 100644 oorwire/oorwire_grpc.pb.go create mode 100644 oorwire/oorwire_mailboxrpc.pb.go create mode 100644 oorwire/payloads.go create mode 100644 oorwire/payloads_test.go create mode 100644 round/mailbox_codec.go create mode 100644 round/mailbox_dispatchers.go create mode 100644 roundwire/payloads.go create mode 100644 roundwire/roundwire.pb.go create mode 100644 roundwire/roundwire.proto create mode 100644 roundwire/roundwire_grpc.pb.go create mode 100644 roundwire/roundwire_mailboxrpc.pb.go create mode 100644 sdk/address_test.go create mode 100644 sdk/client.go diff --git a/darepod/server.go b/darepod/server.go index c6dd8971a..df8dc146f 100644 --- a/darepod/server.go +++ b/darepod/server.go @@ -25,6 +25,7 @@ import ( mailboxpb "github.com/lightninglabs/darepo-client/mailbox/pb" mailboxrpc "github.com/lightninglabs/darepo-client/mailbox/rpc" "github.com/lightninglabs/darepo-client/round" + "github.com/lightninglabs/darepo-client/roundwire" "github.com/lightninglabs/darepo-client/serverconn" "github.com/lightninglabs/darepo-client/wallet" "github.com/lightninglabs/lndclient" @@ -33,7 +34,9 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/anypb" + "google.golang.org/protobuf/types/known/wrapperspb" ) // Main is the true entry point for the daemon. It is called after CLI flag @@ -449,19 +452,68 @@ func (s *Server) buildRPCDispatchers( return s.handleInboundRPC(ctx, edge, env) } + dispatchRoundEvent := func(ctx context.Context, + env *mailboxpb.Envelope) error { + + if env == nil || env.Rpc == nil || env.Body == nil { + return fmt.Errorf("invalid round event envelope") + } + + wrapped := &wrapperspb.BytesValue{} + if err := proto.Unmarshal(env.Body.Value, wrapped); err != nil { + return fmt.Errorf("unmarshal round payload: %w", err) + } + + event, err := round.DecodeServerMailboxPayload( + env.Rpc.Method, wrapped.Value, + ) + if err != nil { + return fmt.Errorf("decode round event: %w", err) + } + + msg := &round.ServerMessageNotification{ + Message: event, + } + + roundKey := round.NewServiceKey() + + return roundKey.Ref(s.actorSystem).Tell(ctx, msg) + } + // TODO(roasbeef): Add indexer and wallet service methods // here once their clients are initialized (e.g., // WalletService.SignVTXO, RoundService.SubmitNonces). // Missing entries will cause the ingress loop to silently // drop inbound KIND_REQUEST envelopes for unregistered // methods. - return map[mailboxrpc.ServiceMethod]serverconn.EnvelopeDispatcher{ - // DaemonService.GetInfo — server queries client status. - { - Service: "daemonrpc.DaemonService", - Method: "GetInfo", - }: dispatch, + dispatchers := make( + map[mailboxrpc.ServiceMethod]serverconn.EnvelopeDispatcher, + ) + + // DaemonService.GetInfo — server queries client status. + dispatchers[mailboxrpc.ServiceMethod{ + Service: "daemonrpc.DaemonService", + Method: "GetInfo", + }] = dispatch + + roundMethods := []string{ + roundwire.MethodClientErrorResp, + roundwire.MethodClientSuccessResp, + roundwire.MethodClientAwaitingInputSigsResp, + roundwire.MethodClientVTXOAggNonces, + roundwire.MethodClientVTXOAggSigs, + roundwire.MethodClientBatchInfo, + roundwire.MethodClientRoundFailedResp, } + + for _, method := range roundMethods { + dispatchers[mailboxrpc.ServiceMethod{ + Service: roundwire.ServiceName, + Method: method, + }] = dispatchRoundEvent + } + + return dispatchers } // handleInboundRPC dispatches a single inbound KIND_REQUEST envelope through diff --git a/harness/harness.go b/harness/harness.go index 764c0b0cd..2b0b1964a 100644 --- a/harness/harness.go +++ b/harness/harness.go @@ -16,6 +16,7 @@ import ( "net" "net/http" "os" + "os/exec" "os/signal" "path/filepath" "runtime" @@ -83,6 +84,17 @@ const ( // starting a container when Docker fails to bind a randomly assigned // host port due to a race with parallel test execution. maxPortBindRetries = 15 + + // dockerCleanupOpTimeout bounds best-effort Docker cleanup calls so a + // wedged daemon does not stall the entire test harness startup. + dockerCleanupOpTimeout = 10 * time.Second + + // dockerStartOpTimeout bounds one container start attempt so a wedged + // daemon cannot hang the harness for the full test timeout. + dockerStartOpTimeout = 90 * time.Second + + // mirrorGCRPrefix is the Docker registry mirror prefix used in CI. + mirrorGCRPrefix = "mirror.gcr.io/" ) var ( @@ -619,8 +631,10 @@ func (h *Harness) killContainer( return } - err := h.pool.Client.KillContainer(docker.KillContainerOptions{ - ID: res.Container.ID, + err := h.runDockerOpWithTimeout("kill container", func() error { + return h.pool.Client.KillContainer(docker.KillContainerOptions{ + ID: res.Container.ID, + }) }) if err != nil { h.Logf("failed to kill %s: %v", name, err) @@ -664,7 +678,9 @@ func (h *Harness) purgeResource( return } - err := h.pool.Purge(res) + err := h.runDockerOpWithTimeout("purge container", func() error { + return h.pool.Purge(res) + }) if err != nil { h.Logf("failed to purge %s: %v", name, err) } @@ -752,23 +768,29 @@ func (h *Harness) forceRemoveNetwork() { networkID := h.network.Network.ID // Get the network details to find connected containers. - network, err := h.pool.Client.NetworkInfo(networkID) + network, err := h.networkInfoWithTimeout(networkID) if err != nil { h.Logf("[DEBUG] Failed to get network info: %v", err) // Try to remove anyway. - _ = h.pool.Client.RemoveNetwork(networkID) + _ = h.runDockerOpWithTimeout("remove network", func() error { + return h.pool.Client.RemoveNetwork(networkID) + }) return } // Disconnect all containers from the network. for containerID := range network.Containers { - err := h.pool.Client.DisconnectNetwork( - networkID, - docker.NetworkConnectionOptions{ - Container: containerID, - Force: true, + err := h.runDockerOpWithTimeout( + "disconnect network", func() error { + return h.pool.Client.DisconnectNetwork( + networkID, + docker.NetworkConnectionOptions{ + Container: containerID, + Force: true, + }, + ) }, ) if err != nil { @@ -781,7 +803,9 @@ func (h *Harness) forceRemoveNetwork() { } // Now remove the network. - err = h.pool.Client.RemoveNetwork(networkID) + err = h.runDockerOpWithTimeout("remove network", func() error { + return h.pool.Client.RemoveNetwork(networkID) + }) if err != nil { h.Logf("[DEBUG] Failed to remove network: %v", err) } @@ -791,8 +815,17 @@ func (h *Harness) forceRemoveNetwork() { // best-effort operation used to clean up leftover containers from previous // failed runs. func (h *Harness) removeContainerByName(name string) { + // Prefer the Docker CLI path first. It avoids long hangs observed in + // go-dockerclient cleanup calls when the daemon is under load. + if err := h.removeContainerByNameCLI(name); err == nil { + return + } else { + h.Logf("[DEBUG] CLI container cleanup fallback for %s: %v", + name, err) + } + // Best-effort, ignore errors. - containers, err := h.pool.Client.ListContainers( + containers, err := h.listContainersWithTimeout( docker.ListContainersOptions{ All: true, Filters: map[string][]string{ @@ -806,20 +839,17 @@ func (h *Harness) removeContainerByName(name string) { } for _, container := range containers { - // Kill container if it's still running. - err := h.pool.Client.KillContainer(docker.KillContainerOptions{ - ID: container.ID, - }) - if err != nil { - h.Logf("[DEBUG] Failed to kill container %s: %v", - container.ID[:12], err) - } - - // Remove the container. - err = h.pool.Client.RemoveContainer( - docker.RemoveContainerOptions{ - ID: container.ID, - Force: true, + // Force remove also stops running containers. + // Skipping explicit kill avoids an extra daemon call. + // That extra call can wedge under load. + err := h.runDockerOpWithTimeout( + "remove container", func() error { + return h.pool.Client.RemoveContainer( + docker.RemoveContainerOptions{ + ID: container.ID, + Force: true, + }, + ) }, ) if err != nil { @@ -829,6 +859,113 @@ func (h *Harness) removeContainerByName(name string) { } } +// removeContainerByNameCLI force-removes a container by name via the docker +// CLI with a hard timeout. +func (h *Harness) removeContainerByNameCLI(name string) error { + dockerPath, err := exec.LookPath("docker") + if err != nil { + return err + } + + ctx, cancel := context.WithTimeout( + context.Background(), dockerCleanupOpTimeout, + ) + defer cancel() + + cmd := exec.CommandContext(ctx, dockerPath, "rm", "-f", name) + output, err := cmd.CombinedOutput() + outputMsg := strings.TrimSpace(string(output)) + if err != nil { + if strings.Contains(outputMsg, "No such container") { + return nil + } + + return fmt.Errorf("docker rm -f %s failed: %w: %s", + name, err, outputMsg) + } + + return nil +} + +// listContainersWithTimeout executes ListContainers with a hard timeout so +// startup cannot hang indefinitely on a stuck Docker daemon. +func (h *Harness) listContainersWithTimeout( + opts docker.ListContainersOptions, +) ( + []docker.APIContainers, error, +) { + + type listResult struct { + containers []docker.APIContainers + err error + } + + resultChan := make(chan listResult, 1) + go func() { + containers, err := h.pool.Client.ListContainers(opts) + resultChan <- listResult{ + containers: containers, + err: err, + } + }() + + select { + case result := <-resultChan: + return result.containers, result.err + + case <-time.After(dockerCleanupOpTimeout): + return nil, fmt.Errorf("docker list containers timed out") + } +} + +// networkInfoWithTimeout executes NetworkInfo with a hard timeout so teardown +// cannot block indefinitely when Docker is wedged. +func (h *Harness) networkInfoWithTimeout( + networkID string, +) ( + *docker.Network, error, +) { + + type networkResult struct { + network *docker.Network + err error + } + + resultChan := make(chan networkResult, 1) + go func() { + network, err := h.pool.Client.NetworkInfo(networkID) + resultChan <- networkResult{ + network: network, + err: err, + } + }() + + select { + case result := <-resultChan: + return result.network, result.err + + case <-time.After(dockerCleanupOpTimeout): + return nil, fmt.Errorf("docker network info timed out") + } +} + +// runDockerOpWithTimeout executes a best-effort Docker cleanup operation with +// a hard timeout so harness setup can proceed when Docker cleanup wedges. +func (h *Harness) runDockerOpWithTimeout(op string, fn func() error) error { + errChan := make(chan error, 1) + go func() { + errChan <- fn() + }() + + select { + case err := <-errChan: + return err + + case <-time.After(dockerCleanupOpTimeout): + return fmt.Errorf("%s timed out", op) + } +} + // waitContainerRunning polls until the given container is running. func (h *Harness) waitContainerRunning(res *dockertest.Resource) { // Poll container status until running, or timeout via dockertest retry. @@ -849,7 +986,8 @@ func (h *Harness) waitContainerRunning(res *dockertest.Resource) { } // runWithPortBindRetry starts a container using run and retries if Docker -// fails due to a host port bind conflict. +// fails due to transient startup errors (for example host port bind conflicts +// or flaky image pull errors). func (h *Harness) runWithPortBindRetry(containerName string, run func() (*dockertest.Resource, error)) (*dockertest.Resource, error) { @@ -857,14 +995,13 @@ func (h *Harness) runWithPortBindRetry(containerName string, var backoff time.Duration = 25 * time.Millisecond var lastErr error for attempt := 1; attempt <= maxPortBindRetries; attempt++ { - res, err := run() + res, err := h.runContainerStartWithTimeout(containerName, run) if err == nil { return res, nil } lastErr = err - // If it's not a port bind error, fail immediately. - if !isDockerPortBindError(err) { + if !isDockerRetriableStartError(err) { return nil, err } @@ -874,8 +1011,11 @@ func (h *Harness) runWithPortBindRetry(containerName string, break } - h.Logf("Port bind conflict for %s (attempt %d/%d): %v", - containerName, attempt, maxPortBindRetries, err) + h.Logf( + "Transient container start error for %s "+ + "(attempt %d/%d): %v", + containerName, attempt, maxPortBindRetries, err, + ) // The failing start may leave a stopped container behind. // Remove it so the retry can reuse the same name. @@ -894,8 +1034,49 @@ func (h *Harness) runWithPortBindRetry(containerName string, } } - return nil, fmt.Errorf("exhausted port bind retries for %s: %w", - containerName, lastErr) + return nil, fmt.Errorf( + "exhausted startup retries for %s: %w", containerName, lastErr, + ) +} + +// runContainerStartWithTimeout executes one container start attempt with a hard +// timeout to avoid indefinite hangs in Docker API calls. +func (h *Harness) runContainerStartWithTimeout( + containerName string, + run func() (*dockertest.Resource, error), +) ( + *dockertest.Resource, error, +) { + + type startResult struct { + resource *dockertest.Resource + err error + } + + resultChan := make(chan startResult, 1) + go func() { + resource, err := run() + resultChan <- startResult{ + resource: resource, + err: err, + } + }() + + select { + case result := <-resultChan: + return result.resource, result.err + + case <-time.After(dockerStartOpTimeout): + return nil, fmt.Errorf("docker start timed out for %s", + containerName) + } +} + +// isDockerRetriableStartError returns true when err is a transient container +// startup failure that is worth retrying. +func isDockerRetriableStartError(err error) bool { + return isDockerPortBindError(err) || isDockerImagePullError(err) || + isDockerContainerNameConflictError(err) } // isDockerPortBindError returns true if err indicates that Docker failed to @@ -922,6 +1103,64 @@ func isDockerPortBindError(err error) bool { return false } +// isDockerContainerNameConflictError returns true when Docker reports a name +// conflict for a container that should be cleaned up and retried. +func isDockerContainerNameConflictError(err error) bool { + if err == nil { + return false + } + + msg := err.Error() + conflicts := []string{ + "container already exists", + "is already in use by container", + "Conflict. The container name", + } + + for _, s := range conflicts { + if strings.Contains(msg, s) { + return true + } + } + + return false +} + +// isDockerStartTimeoutError returns true when a Docker start operation exceeds +// dockerStartOpTimeout. +func isDockerStartTimeoutError(err error) bool { + if err == nil { + return false + } + + return strings.Contains(err.Error(), "docker start timed out") +} + +// isDockerImagePullError returns true if err indicates a transient image pull +// failure from a registry mirror (for example "unknown blob" during manifest +// or layer fetches). +func isDockerImagePullError(err error) bool { + if err == nil { + return false + } + + msg := err.Error() + imagePullErrors := []string{ + "error pulling image configuration", + "unknown blob", + "received unexpected http status", + "toomanyrequests", + } + + for _, errStr := range imagePullErrors { + if strings.Contains(msg, errStr) { + return true + } + } + + return false +} + // randJitter returns a uniformly distributed duration in [0, maxJitter). // // randJitter exists to desynchronize retries between concurrently running @@ -1082,48 +1321,72 @@ func (h *Harness) startBitcoind() { require.NoError(h.T, err, "failed to get absolute path "+ "for bitcoind data dir") - res, err := h.runWithPortBindRetry(containerName, func() ( - *dockertest.Resource, error) { - - return h.pool.RunWithOptions(&dockertest.RunOptions{ - Repository: imageRepo(h.opts.BitcoindImage), - Tag: imageTag(h.opts.BitcoindImage), - Cmd: cmd, - Env: []string{}, - ExposedPorts: []string{ - "18443/tcp", "28332/tcp", "28333/tcp", - }, - Name: containerName, - Networks: []*dockertest.Network{h.network}, - Labels: map[string]string{ - "ark.harness": h.group, - "com.docker.compose.project": h.group, - }, - Mounts: []string{ - fmt.Sprintf("%s:%s", btcHostDir, - "/home/bitcoin/.bitcoin"), - }, - }, func(hc *docker.HostConfig) { - // Keep container for logs on failure; Purge() will - // clean up. - hc.AutoRemove = false - hc.PortBindings = - map[docker.Port][]docker.PortBinding{ - "18443/tcp": {{ - HostIP: "0.0.0.0", - HostPort: "", - }}, - "28332/tcp": {{ - HostIP: "0.0.0.0", - HostPort: "", - }}, - "28333/tcp": {{ - HostIP: "0.0.0.0", - HostPort: "", - }}, - } + startBitcoindWithImage := func(image string) (*dockertest.Resource, + error) { + + return h.runWithPortBindRetry(containerName, func() ( + *dockertest.Resource, error) { + + return h.pool.RunWithOptions(&dockertest.RunOptions{ + Repository: imageRepo(image), + Tag: imageTag(image), + Cmd: cmd, + Env: []string{}, + ExposedPorts: []string{ + "18443/tcp", "28332/tcp", "28333/tcp", + }, + Name: containerName, + Networks: []*dockertest.Network{h.network}, + Labels: map[string]string{ + "ark.harness": h.group, + "com.docker.compose.project": h.group, + }, + Mounts: []string{ + fmt.Sprintf("%s:%s", btcHostDir, + "/home/bitcoin/.bitcoin"), + }, + }, func(hc *docker.HostConfig) { + // Keep container for logs on failure. + // Purge() will clean up. + hc.AutoRemove = false + hc.PortBindings = + map[docker.Port][]docker.PortBinding{ + "18443/tcp": {{ + HostIP: "0.0.0.0", + HostPort: "", + }}, + "28332/tcp": {{ + HostIP: "0.0.0.0", + HostPort: "", + }}, + "28333/tcp": {{ + HostIP: "0.0.0.0", + HostPort: "", + }}, + } + }) }) - }) + } + + res, err := startBitcoindWithImage(h.opts.BitcoindImage) + if err != nil && (isDockerImagePullError(err) || + isDockerStartTimeoutError(err)) { + + fallbackImage, ok := mirrorFallbackImage(h.opts.BitcoindImage) + if ok { + h.Logf( + "bitcoind startup failed for %s, retrying %s", + h.opts.BitcoindImage, fallbackImage, + ) + + // Remove stale containers from previous retries before + // retrying with the fallback image. + h.removeContainerByName(containerName) + + res, err = startBitcoindWithImage(fallbackImage) + } + } + require.NoError(h.T, err, "failed to start bitcoind") h.bitcoind = res @@ -2189,14 +2452,24 @@ func imageTag(image string) string { return "" } -// containerName returns a container name, optionally with random suffix. If the -// harness has an explicit GroupName, use it without suffix for predictable -// names. -func (h *Harness) containerName(prefix string) string { - if h.opts.GroupName != "" { - return prefix + "-" + h.group +// mirrorFallbackImage rewrites a mirror.gcr.io image reference to the direct +// Docker Hub image reference. +func mirrorFallbackImage(image string) (string, bool) { + if !strings.HasPrefix(image, mirrorGCRPrefix) { + return "", false } + fallback := strings.TrimPrefix(image, mirrorGCRPrefix) + if fallback == "" { + return "", false + } + + return fallback, true +} + +// containerName returns a unique container name using a stable group prefix +// plus a random suffix for collision resistance. +func (h *Harness) containerName(prefix string) string { return prefix + "-" + h.group + "-" + randSuffix() } diff --git a/harness/port_retry_test.go b/harness/port_retry_test.go index 4cb0fd565..d64626eb3 100644 --- a/harness/port_retry_test.go +++ b/harness/port_retry_test.go @@ -9,6 +9,9 @@ import ( "github.com/stretchr/testify/require" ) +const dockerImagePullErr = "error pulling image configuration: " + + "download failed after attempts=1: unknown blob" + // TestIsDockerPortBindError verifies we recognize common Docker errors that // indicate a published host port was already in use. func TestIsDockerPortBindError(t *testing.T) { @@ -64,8 +67,79 @@ func TestIsDockerPortBindError(t *testing.T) { } } -// TestRunWithPortBindRetry verifies we retry transient Docker port bind -// conflicts but do not retry for unrelated errors. +// TestIsDockerImagePullError verifies we detect transient image pull failures +// that should be retried. +func TestIsDockerImagePullError(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + err error + want bool + }{ + { + name: "nil", + err: nil, + want: false, + }, + { + name: "unknown blob", + err: errors.New(dockerImagePullErr), + want: true, + }, + { + name: "rate limited", + err: errors.New( + "toomanyrequests: too many requests", + ), + want: true, + }, + { + name: "other error", + err: errors.New("some other docker error"), + want: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got := isDockerImagePullError(tc.err) + require.Equal(t, tc.want, got) + }) + } +} + +// TestIsDockerStartTimeoutError verifies timeout error detection for docker +// start attempts. +func TestIsDockerStartTimeoutError(t *testing.T) { + t.Parallel() + + require.True(t, isDockerStartTimeoutError( + errors.New("docker start timed out for bitcoin-test"), + )) + require.False(t, isDockerStartTimeoutError( + errors.New("some other error"), + )) + require.False(t, isDockerStartTimeoutError(nil)) +} + +// TestMirrorFallbackImage verifies mirror image rewriting behavior. +func TestMirrorFallbackImage(t *testing.T) { + t.Parallel() + + fallback, ok := mirrorFallbackImage( + "mirror.gcr.io/lightninglabs/bitcoin-core:29", + ) + require.True(t, ok) + require.Equal(t, "lightninglabs/bitcoin-core:29", fallback) + + fallback, ok = mirrorFallbackImage("lightninglabs/bitcoin-core:29") + require.False(t, ok) + require.Empty(t, fallback) +} + +// TestRunWithPortBindRetry verifies we retry transient Docker startup failures +// but do not retry for unrelated errors. func TestRunWithPortBindRetry(t *testing.T) { t.Parallel() @@ -96,6 +170,30 @@ func TestRunWithPortBindRetry(t *testing.T) { require.Equal(t, 3, attempts) }) + t.Run("retries on image pull errors", func(t *testing.T) { + var attempts int + h := &Harness{ + T: t, + opts: &Options{}, + } + + res, err := h.runWithPortBindRetry("test-container", func() ( + *dockertest.Resource, error) { + + attempts++ + if attempts < 3 { + return nil, errors.New(dockerImagePullErr) + } + + return &dockertest.Resource{ + Container: &docker.Container{Name: "ok"}, + }, nil + }) + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, 3, attempts) + }) + t.Run("does not retry other errors", func(t *testing.T) { var attempts int h := &Harness{ diff --git a/oor/mailbox_outbox_handler.go b/oor/mailbox_outbox_handler.go new file mode 100644 index 000000000..dc6b9e907 --- /dev/null +++ b/oor/mailbox_outbox_handler.go @@ -0,0 +1,221 @@ +package oor + +import ( + "context" + "fmt" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + mailboxrpc "github.com/lightninglabs/darepo-client/mailbox/rpc" + "github.com/lightninglabs/darepo-client/oorwire" + "github.com/lightningnetwork/lnd/input" +) + +type oorMailboxClient = oorwire.OORMailboxServiceMailboxClient + +// InputSpendMarker abstracts marking local input VTXOs spent after finalize is +// accepted by the server. +type InputSpendMarker interface { + MarkVTXOSpent(ctx context.Context, outpoint wire.OutPoint) error +} + +// MailboxOutboxHandler executes outgoing OOR FSM outbox side effects by +// calling server-side OOR methods over mailbox unary RPC. +// +// This is the production-path plumbing layer for: +// - submit package transport; +// - finalize package transport; +// - checkpoint signing; and +// - local input-spent persistence. +type MailboxOutboxHandler struct { + // RPCClient is the mailbox unary RPC client (typically + // serverconn.Runtime.Unary()). + RPCClient mailboxrpc.RPCClient + + // Signer signs checkpoint inputs at RequestCheckpointSignatures. + Signer input.Signer + + // SpendMarker marks local inputs spent after finalize is accepted. + SpendMarker InputSpendMarker +} + +// Handle executes one OOR outbox request and returns follow-up FSM events. +func (h *MailboxOutboxHandler) Handle(ctx context.Context, sessionID SessionID, + outbox OutboxEvent) ([]Event, error) { + + switch msg := outbox.(type) { + case *RequestArkSignatures: + // v0 does not require extra local Ark signing beyond + // deterministic package construction in this path. + // Preserve the current behavior by forwarding the Ark + // PSBT as signed. + return []Event{ + &ArkSignedEvent{ + ArkPSBT: msg.ArkPSBT, + }, + }, nil + + case *SendSubmitPackageRequest: + return h.handleSubmit(ctx, msg) + + case *RequestCheckpointSignatures: + return h.handleCheckpointSignatures(msg) + + case *SendFinalizePackageRequest: + return h.handleFinalize(ctx, sessionID, msg) + + case *MarkInputsSpentRequest: + return h.handleMarkInputsSpent(ctx, msg) + + case *ScheduleRetryRequest: + // Retry scheduling policy is owned by the higher + // layer running this handler. For now we emit + // RetryDueEvent immediately. + return []Event{ + &RetryDueEvent{}, + }, nil + + default: + return nil, nil + } +} + +func (h *MailboxOutboxHandler) handleSubmit(ctx context.Context, + msg *SendSubmitPackageRequest) ([]Event, error) { + + if h == nil || h.RPCClient == nil { + return nil, fmt.Errorf("rpc client is required") + } + + signDescs := make( + []oorwire.SigningDescriptor, 0, len(msg.TransferInputs), + ) + for i := range msg.TransferInputs { + in := msg.TransferInputs[i] + if in.VTXO == nil { + return nil, fmt.Errorf( + "transfer input %d missing vtxo", i, + ) + } + if in.VTXO.ClientKey.PubKey == nil { + return nil, fmt.Errorf( + "transfer input %d missing client key", i, + ) + } + + signDescs = append(signDescs, oorwire.SigningDescriptor{ + Outpoint: in.VTXO.Outpoint, + OwnerKey: in.VTXO.ClientKey.PubKey, + ExitDelay: in.VTXO.RelativeExpiry, + }) + } + + req, err := oorwire.NewSubmitPackageRequest( + msg.ArkPSBT, msg.CheckpointPSBTs, signDescs, + ) + if err != nil { + return nil, err + } + + rpcClient := h.oorRPCClient() + resp, err := rpcClient.SubmitPackage(ctx, req) + if err != nil { + return nil, err + } + + sessionHash, checkpoints, err := oorwire.ParseSubmitPackageResponse( + resp, + ) + if err != nil { + return nil, err + } + + return []Event{ + &SubmitAcceptedEvent{ + SessionID: SessionID(sessionHash), + ArkPSBT: msg.ArkPSBT, + CoSignedCheckpointPSBTs: checkpoints, + }, + }, nil +} + +func (h *MailboxOutboxHandler) handleCheckpointSignatures( + msg *RequestCheckpointSignatures) ([]Event, error) { + + if h == nil || h.Signer == nil { + return nil, fmt.Errorf("signer is required") + } + + err := SignCheckpointPSBTs( + h.Signer, msg.TransferInputs, msg.CoSignedCheckpointPSBTs, + ) + if err != nil { + return nil, err + } + + return []Event{ + &CheckpointsSignedEvent{ + FinalCheckpointPSBTs: msg.CoSignedCheckpointPSBTs, + }, + }, nil +} + +func (h *MailboxOutboxHandler) handleFinalize(ctx context.Context, + sessionID SessionID, msg *SendFinalizePackageRequest) ([]Event, error) { + + if h == nil || h.RPCClient == nil { + return nil, fmt.Errorf("rpc client is required") + } + + req, err := oorwire.NewFinalizePackageRequest( + chainhash.Hash(sessionID), msg.FinalCheckpointPSBTs, + ) + if err != nil { + return nil, err + } + + rpcClient := h.oorRPCClient() + resp, err := rpcClient.FinalizePackage(ctx, req) + if err != nil { + return nil, err + } + + serverSessionID, err := oorwire.ParseFinalizePackageResponse(resp) + if err != nil { + return nil, err + } + + if SessionID(serverSessionID) != sessionID { + return nil, fmt.Errorf("finalize response session mismatch") + } + + return []Event{ + &FinalizeAcceptedEvent{}, + }, nil +} + +func (h *MailboxOutboxHandler) handleMarkInputsSpent(ctx context.Context, + msg *MarkInputsSpentRequest) ([]Event, error) { + + if h == nil || h.SpendMarker == nil { + return nil, fmt.Errorf("spend marker is required") + } + + for i := range msg.Outpoints { + err := h.SpendMarker.MarkVTXOSpent(ctx, msg.Outpoints[i]) + if err != nil { + return nil, err + } + } + + return []Event{ + &InputsMarkedSpentEvent{}, + }, nil +} + +// oorRPCClient returns a typed mailbox client for OOR unary methods. +func (h *MailboxOutboxHandler) oorRPCClient() *oorMailboxClient { + return oorwire.NewOORMailboxServiceMailboxClient(h.RPCClient) +} + +var _ OutboxHandler = (*MailboxOutboxHandler)(nil) diff --git a/oorwire/oorwire.pb.go b/oorwire/oorwire.pb.go new file mode 100644 index 000000000..30e458391 --- /dev/null +++ b/oorwire/oorwire.pb.go @@ -0,0 +1,485 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.1 +// protoc v6.33.1 +// source: oorwire.proto + +package oorwire + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// OOROutPoint is a protobuf representation of wire.OutPoint. +type OOROutPoint struct { + state protoimpl.MessageState `protogen:"open.v1"` + // txid is the 32-byte transaction hash. + Txid []byte `protobuf:"bytes,1,opt,name=txid,proto3" json:"txid,omitempty"` + // vout is the transaction output index. + Vout uint32 `protobuf:"varint,2,opt,name=vout,proto3" json:"vout,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *OOROutPoint) Reset() { + *x = OOROutPoint{} + mi := &file_oorwire_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OOROutPoint) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OOROutPoint) ProtoMessage() {} + +func (x *OOROutPoint) ProtoReflect() protoreflect.Message { + mi := &file_oorwire_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OOROutPoint.ProtoReflect.Descriptor instead. +func (*OOROutPoint) Descriptor() ([]byte, []int) { + return file_oorwire_proto_rawDescGZIP(), []int{0} +} + +func (x *OOROutPoint) GetTxid() []byte { + if x != nil { + return x.Txid + } + return nil +} + +func (x *OOROutPoint) GetVout() uint32 { + if x != nil { + return x.Vout + } + return 0 +} + +// OORSigningDescriptor carries the minimal signing metadata required by the +// server OOR actor. +type OORSigningDescriptor struct { + state protoimpl.MessageState `protogen:"open.v1"` + // outpoint identifies the input VTXO. + Outpoint *OOROutPoint `protobuf:"bytes,1,opt,name=outpoint,proto3" json:"outpoint,omitempty"` + // owner_key is the compressed 33-byte owner public key. + OwnerKey []byte `protobuf:"bytes,2,opt,name=owner_key,json=ownerKey,proto3" json:"owner_key,omitempty"` + // exit_delay is the CSV delay for the input script path. + ExitDelay uint32 `protobuf:"varint,3,opt,name=exit_delay,json=exitDelay,proto3" json:"exit_delay,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *OORSigningDescriptor) Reset() { + *x = OORSigningDescriptor{} + mi := &file_oorwire_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OORSigningDescriptor) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OORSigningDescriptor) ProtoMessage() {} + +func (x *OORSigningDescriptor) ProtoReflect() protoreflect.Message { + mi := &file_oorwire_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OORSigningDescriptor.ProtoReflect.Descriptor instead. +func (*OORSigningDescriptor) Descriptor() ([]byte, []int) { + return file_oorwire_proto_rawDescGZIP(), []int{1} +} + +func (x *OORSigningDescriptor) GetOutpoint() *OOROutPoint { + if x != nil { + return x.Outpoint + } + return nil +} + +func (x *OORSigningDescriptor) GetOwnerKey() []byte { + if x != nil { + return x.OwnerKey + } + return nil +} + +func (x *OORSigningDescriptor) GetExitDelay() uint32 { + if x != nil { + return x.ExitDelay + } + return 0 +} + +// SubmitPackageRequest carries submit-phase OOR data. +type SubmitPackageRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // ark_psbt is the serialized Ark PSBT. + ArkPsbt []byte `protobuf:"bytes,1,opt,name=ark_psbt,json=arkPsbt,proto3" json:"ark_psbt,omitempty"` + // checkpoint_psbts are serialized checkpoint PSBTs. + CheckpointPsbts [][]byte `protobuf:"bytes,2,rep,name=checkpoint_psbts,json=checkpointPsbts,proto3" json:"checkpoint_psbts,omitempty"` + // signing_descriptors carry per-input signing metadata. + SigningDescriptors []*OORSigningDescriptor `protobuf:"bytes,3,rep,name=signing_descriptors,json=signingDescriptors,proto3" json:"signing_descriptors,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubmitPackageRequest) Reset() { + *x = SubmitPackageRequest{} + mi := &file_oorwire_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubmitPackageRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubmitPackageRequest) ProtoMessage() {} + +func (x *SubmitPackageRequest) ProtoReflect() protoreflect.Message { + mi := &file_oorwire_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubmitPackageRequest.ProtoReflect.Descriptor instead. +func (*SubmitPackageRequest) Descriptor() ([]byte, []int) { + return file_oorwire_proto_rawDescGZIP(), []int{2} +} + +func (x *SubmitPackageRequest) GetArkPsbt() []byte { + if x != nil { + return x.ArkPsbt + } + return nil +} + +func (x *SubmitPackageRequest) GetCheckpointPsbts() [][]byte { + if x != nil { + return x.CheckpointPsbts + } + return nil +} + +func (x *SubmitPackageRequest) GetSigningDescriptors() []*OORSigningDescriptor { + if x != nil { + return x.SigningDescriptors + } + return nil +} + +// SubmitPackageResponse carries server submit-phase results. +type SubmitPackageResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // session_id is the 32-byte session hash. + SessionId []byte `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + // co_signed_checkpoint_psbts are serialized server-co-signed checkpoint + // PSBTs. + CoSignedCheckpointPsbts [][]byte `protobuf:"bytes,2,rep,name=co_signed_checkpoint_psbts,json=coSignedCheckpointPsbts,proto3" json:"co_signed_checkpoint_psbts,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubmitPackageResponse) Reset() { + *x = SubmitPackageResponse{} + mi := &file_oorwire_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubmitPackageResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubmitPackageResponse) ProtoMessage() {} + +func (x *SubmitPackageResponse) ProtoReflect() protoreflect.Message { + mi := &file_oorwire_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubmitPackageResponse.ProtoReflect.Descriptor instead. +func (*SubmitPackageResponse) Descriptor() ([]byte, []int) { + return file_oorwire_proto_rawDescGZIP(), []int{3} +} + +func (x *SubmitPackageResponse) GetSessionId() []byte { + if x != nil { + return x.SessionId + } + return nil +} + +func (x *SubmitPackageResponse) GetCoSignedCheckpointPsbts() [][]byte { + if x != nil { + return x.CoSignedCheckpointPsbts + } + return nil +} + +// FinalizePackageRequest carries finalize-phase OOR data. +type FinalizePackageRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // session_id is the 32-byte session hash. + SessionId []byte `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + // final_checkpoint_psbts are serialized finalized checkpoint PSBTs. + FinalCheckpointPsbts [][]byte `protobuf:"bytes,2,rep,name=final_checkpoint_psbts,json=finalCheckpointPsbts,proto3" json:"final_checkpoint_psbts,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FinalizePackageRequest) Reset() { + *x = FinalizePackageRequest{} + mi := &file_oorwire_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FinalizePackageRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FinalizePackageRequest) ProtoMessage() {} + +func (x *FinalizePackageRequest) ProtoReflect() protoreflect.Message { + mi := &file_oorwire_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FinalizePackageRequest.ProtoReflect.Descriptor instead. +func (*FinalizePackageRequest) Descriptor() ([]byte, []int) { + return file_oorwire_proto_rawDescGZIP(), []int{4} +} + +func (x *FinalizePackageRequest) GetSessionId() []byte { + if x != nil { + return x.SessionId + } + return nil +} + +func (x *FinalizePackageRequest) GetFinalCheckpointPsbts() [][]byte { + if x != nil { + return x.FinalCheckpointPsbts + } + return nil +} + +// FinalizePackageResponse carries finalize-phase OOR results. +type FinalizePackageResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // session_id is the 32-byte session hash. + SessionId []byte `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FinalizePackageResponse) Reset() { + *x = FinalizePackageResponse{} + mi := &file_oorwire_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FinalizePackageResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FinalizePackageResponse) ProtoMessage() {} + +func (x *FinalizePackageResponse) ProtoReflect() protoreflect.Message { + mi := &file_oorwire_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FinalizePackageResponse.ProtoReflect.Descriptor instead. +func (*FinalizePackageResponse) Descriptor() ([]byte, []int) { + return file_oorwire_proto_rawDescGZIP(), []int{5} +} + +func (x *FinalizePackageResponse) GetSessionId() []byte { + if x != nil { + return x.SessionId + } + return nil +} + +var File_oorwire_proto protoreflect.FileDescriptor + +var file_oorwire_proto_rawDesc = []byte{ + 0x0a, 0x0d, 0x6f, 0x6f, 0x72, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x07, 0x6f, 0x6f, 0x72, 0x77, 0x69, 0x72, 0x65, 0x22, 0x35, 0x0a, 0x0b, 0x4f, 0x4f, 0x52, 0x4f, + 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x76, + 0x6f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x76, 0x6f, 0x75, 0x74, 0x22, + 0x84, 0x01, 0x0a, 0x14, 0x4f, 0x4f, 0x52, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x44, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x30, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6f, 0x6f, 0x72, + 0x77, 0x69, 0x72, 0x65, 0x2e, 0x4f, 0x4f, 0x52, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, + 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x77, + 0x6e, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6f, + 0x77, 0x6e, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x78, 0x69, 0x74, 0x5f, + 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x65, 0x78, 0x69, + 0x74, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x22, 0xac, 0x01, 0x0a, 0x14, 0x53, 0x75, 0x62, 0x6d, 0x69, + 0x74, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x19, 0x0a, 0x08, 0x61, 0x72, 0x6b, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x07, 0x61, 0x72, 0x6b, 0x50, 0x73, 0x62, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x68, + 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x50, 0x73, 0x62, 0x74, 0x73, 0x12, 0x4e, 0x0a, 0x13, 0x73, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, + 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6f, 0x6f, 0x72, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x4f, 0x4f, 0x52, + 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, + 0x72, 0x52, 0x12, 0x73, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x6f, 0x72, 0x73, 0x22, 0x73, 0x0a, 0x15, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x50, + 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, + 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x3b, 0x0a, + 0x1a, 0x63, 0x6f, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x0c, 0x52, 0x17, 0x63, 0x6f, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x43, 0x68, 0x65, 0x63, 0x6b, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x50, 0x73, 0x62, 0x74, 0x73, 0x22, 0x6d, 0x0a, 0x16, 0x46, 0x69, + 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x49, 0x64, 0x12, 0x34, 0x0a, 0x16, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x63, 0x68, 0x65, + 0x63, 0x6b, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x70, 0x73, 0x62, 0x74, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0c, 0x52, 0x14, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x50, 0x73, 0x62, 0x74, 0x73, 0x22, 0x38, 0x0a, 0x17, 0x46, 0x69, 0x6e, + 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x49, 0x64, 0x32, 0xb9, 0x01, 0x0a, 0x11, 0x4f, 0x4f, 0x52, 0x4d, 0x61, 0x69, 0x6c, 0x62, + 0x6f, 0x78, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4e, 0x0a, 0x0d, 0x53, 0x75, 0x62, + 0x6d, 0x69, 0x74, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x1d, 0x2e, 0x6f, 0x6f, 0x72, + 0x77, 0x69, 0x72, 0x65, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x50, 0x61, 0x63, 0x6b, 0x61, + 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6f, 0x6f, 0x72, 0x77, + 0x69, 0x72, 0x65, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0f, 0x46, 0x69, 0x6e, + 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x1f, 0x2e, 0x6f, + 0x6f, 0x72, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, + 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, + 0x6f, 0x6f, 0x72, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, + 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, + 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, + 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x64, 0x61, 0x72, 0x65, + 0x70, 0x6f, 0x2d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2f, 0x6f, 0x6f, 0x72, 0x77, 0x69, 0x72, + 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_oorwire_proto_rawDescOnce sync.Once + file_oorwire_proto_rawDescData = file_oorwire_proto_rawDesc +) + +func file_oorwire_proto_rawDescGZIP() []byte { + file_oorwire_proto_rawDescOnce.Do(func() { + file_oorwire_proto_rawDescData = protoimpl.X.CompressGZIP(file_oorwire_proto_rawDescData) + }) + return file_oorwire_proto_rawDescData +} + +var file_oorwire_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_oorwire_proto_goTypes = []any{ + (*OOROutPoint)(nil), // 0: oorwire.OOROutPoint + (*OORSigningDescriptor)(nil), // 1: oorwire.OORSigningDescriptor + (*SubmitPackageRequest)(nil), // 2: oorwire.SubmitPackageRequest + (*SubmitPackageResponse)(nil), // 3: oorwire.SubmitPackageResponse + (*FinalizePackageRequest)(nil), // 4: oorwire.FinalizePackageRequest + (*FinalizePackageResponse)(nil), // 5: oorwire.FinalizePackageResponse +} +var file_oorwire_proto_depIdxs = []int32{ + 0, // 0: oorwire.OORSigningDescriptor.outpoint:type_name -> oorwire.OOROutPoint + 1, // 1: oorwire.SubmitPackageRequest.signing_descriptors:type_name -> oorwire.OORSigningDescriptor + 2, // 2: oorwire.OORMailboxService.SubmitPackage:input_type -> oorwire.SubmitPackageRequest + 4, // 3: oorwire.OORMailboxService.FinalizePackage:input_type -> oorwire.FinalizePackageRequest + 3, // 4: oorwire.OORMailboxService.SubmitPackage:output_type -> oorwire.SubmitPackageResponse + 5, // 5: oorwire.OORMailboxService.FinalizePackage:output_type -> oorwire.FinalizePackageResponse + 4, // [4:6] is the sub-list for method output_type + 2, // [2:4] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_oorwire_proto_init() } +func file_oorwire_proto_init() { + if File_oorwire_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_oorwire_proto_rawDesc, + NumEnums: 0, + NumMessages: 6, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_oorwire_proto_goTypes, + DependencyIndexes: file_oorwire_proto_depIdxs, + MessageInfos: file_oorwire_proto_msgTypes, + }.Build() + File_oorwire_proto = out.File + file_oorwire_proto_rawDesc = nil + file_oorwire_proto_goTypes = nil + file_oorwire_proto_depIdxs = nil +} diff --git a/oorwire/oorwire.proto b/oorwire/oorwire.proto new file mode 100644 index 000000000..78d661926 --- /dev/null +++ b/oorwire/oorwire.proto @@ -0,0 +1,75 @@ +syntax = "proto3"; + +package oorwire; + +option go_package = "github.com/lightninglabs/darepo-client/oorwire"; + +// OORMailboxService defines unary OOR mailbox methods used in the toy E2E +// connector path. +service OORMailboxService { + // SubmitPackage submits an Ark package and returns server co-signed + // checkpoint PSBTs. + rpc SubmitPackage (SubmitPackageRequest) returns (SubmitPackageResponse); + + // FinalizePackage submits finalized checkpoint PSBTs for a session. + rpc FinalizePackage (FinalizePackageRequest) returns (FinalizePackageResponse); +} + +// OOROutPoint is a protobuf representation of wire.OutPoint. +message OOROutPoint { + // txid is the 32-byte transaction hash. + bytes txid = 1; + + // vout is the transaction output index. + uint32 vout = 2; +} + +// OORSigningDescriptor carries the minimal signing metadata required by the +// server OOR actor. +message OORSigningDescriptor { + // outpoint identifies the input VTXO. + OOROutPoint outpoint = 1; + + // owner_key is the compressed 33-byte owner public key. + bytes owner_key = 2; + + // exit_delay is the CSV delay for the input script path. + uint32 exit_delay = 3; +} + +// SubmitPackageRequest carries submit-phase OOR data. +message SubmitPackageRequest { + // ark_psbt is the serialized Ark PSBT. + bytes ark_psbt = 1; + + // checkpoint_psbts are serialized checkpoint PSBTs. + repeated bytes checkpoint_psbts = 2; + + // signing_descriptors carry per-input signing metadata. + repeated OORSigningDescriptor signing_descriptors = 3; +} + +// SubmitPackageResponse carries server submit-phase results. +message SubmitPackageResponse { + // session_id is the 32-byte session hash. + bytes session_id = 1; + + // co_signed_checkpoint_psbts are serialized server-co-signed checkpoint + // PSBTs. + repeated bytes co_signed_checkpoint_psbts = 2; +} + +// FinalizePackageRequest carries finalize-phase OOR data. +message FinalizePackageRequest { + // session_id is the 32-byte session hash. + bytes session_id = 1; + + // final_checkpoint_psbts are serialized finalized checkpoint PSBTs. + repeated bytes final_checkpoint_psbts = 2; +} + +// FinalizePackageResponse carries finalize-phase OOR results. +message FinalizePackageResponse { + // session_id is the 32-byte session hash. + bytes session_id = 1; +} diff --git a/oorwire/oorwire_grpc.pb.go b/oorwire/oorwire_grpc.pb.go new file mode 100644 index 000000000..52e5bc284 --- /dev/null +++ b/oorwire/oorwire_grpc.pb.go @@ -0,0 +1,152 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.3.0 +// - protoc v6.33.1 +// source: oorwire.proto + +package oorwire + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +const ( + OORMailboxService_SubmitPackage_FullMethodName = "/oorwire.OORMailboxService/SubmitPackage" + OORMailboxService_FinalizePackage_FullMethodName = "/oorwire.OORMailboxService/FinalizePackage" +) + +// OORMailboxServiceClient is the client API for OORMailboxService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type OORMailboxServiceClient interface { + // SubmitPackage submits an Ark package and returns server co-signed + // checkpoint PSBTs. + SubmitPackage(ctx context.Context, in *SubmitPackageRequest, opts ...grpc.CallOption) (*SubmitPackageResponse, error) + // FinalizePackage submits finalized checkpoint PSBTs for a session. + FinalizePackage(ctx context.Context, in *FinalizePackageRequest, opts ...grpc.CallOption) (*FinalizePackageResponse, error) +} + +type oORMailboxServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewOORMailboxServiceClient(cc grpc.ClientConnInterface) OORMailboxServiceClient { + return &oORMailboxServiceClient{cc} +} + +func (c *oORMailboxServiceClient) SubmitPackage(ctx context.Context, in *SubmitPackageRequest, opts ...grpc.CallOption) (*SubmitPackageResponse, error) { + out := new(SubmitPackageResponse) + err := c.cc.Invoke(ctx, OORMailboxService_SubmitPackage_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *oORMailboxServiceClient) FinalizePackage(ctx context.Context, in *FinalizePackageRequest, opts ...grpc.CallOption) (*FinalizePackageResponse, error) { + out := new(FinalizePackageResponse) + err := c.cc.Invoke(ctx, OORMailboxService_FinalizePackage_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// OORMailboxServiceServer is the server API for OORMailboxService service. +// All implementations must embed UnimplementedOORMailboxServiceServer +// for forward compatibility +type OORMailboxServiceServer interface { + // SubmitPackage submits an Ark package and returns server co-signed + // checkpoint PSBTs. + SubmitPackage(context.Context, *SubmitPackageRequest) (*SubmitPackageResponse, error) + // FinalizePackage submits finalized checkpoint PSBTs for a session. + FinalizePackage(context.Context, *FinalizePackageRequest) (*FinalizePackageResponse, error) + mustEmbedUnimplementedOORMailboxServiceServer() +} + +// UnimplementedOORMailboxServiceServer must be embedded to have forward compatible implementations. +type UnimplementedOORMailboxServiceServer struct { +} + +func (UnimplementedOORMailboxServiceServer) SubmitPackage(context.Context, *SubmitPackageRequest) (*SubmitPackageResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SubmitPackage not implemented") +} +func (UnimplementedOORMailboxServiceServer) FinalizePackage(context.Context, *FinalizePackageRequest) (*FinalizePackageResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method FinalizePackage not implemented") +} +func (UnimplementedOORMailboxServiceServer) mustEmbedUnimplementedOORMailboxServiceServer() {} + +// UnsafeOORMailboxServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to OORMailboxServiceServer will +// result in compilation errors. +type UnsafeOORMailboxServiceServer interface { + mustEmbedUnimplementedOORMailboxServiceServer() +} + +func RegisterOORMailboxServiceServer(s grpc.ServiceRegistrar, srv OORMailboxServiceServer) { + s.RegisterService(&OORMailboxService_ServiceDesc, srv) +} + +func _OORMailboxService_SubmitPackage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SubmitPackageRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OORMailboxServiceServer).SubmitPackage(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OORMailboxService_SubmitPackage_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OORMailboxServiceServer).SubmitPackage(ctx, req.(*SubmitPackageRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OORMailboxService_FinalizePackage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(FinalizePackageRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OORMailboxServiceServer).FinalizePackage(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OORMailboxService_FinalizePackage_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OORMailboxServiceServer).FinalizePackage(ctx, req.(*FinalizePackageRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// OORMailboxService_ServiceDesc is the grpc.ServiceDesc for OORMailboxService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var OORMailboxService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "oorwire.OORMailboxService", + HandlerType: (*OORMailboxServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "SubmitPackage", + Handler: _OORMailboxService_SubmitPackage_Handler, + }, + { + MethodName: "FinalizePackage", + Handler: _OORMailboxService_FinalizePackage_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "oorwire.proto", +} diff --git a/oorwire/oorwire_mailboxrpc.pb.go b/oorwire/oorwire_mailboxrpc.pb.go new file mode 100644 index 000000000..a58a8a6d8 --- /dev/null +++ b/oorwire/oorwire_mailboxrpc.pb.go @@ -0,0 +1,101 @@ +// Code generated by protoc-gen-mailboxrpc. DO NOT EDIT. + +package oorwire + +import ( + context "context" + fmt "fmt" + rpc "github.com/lightninglabs/darepo-client/mailbox/rpc" + proto "google.golang.org/protobuf/proto" +) + +// OORMailboxServiceMailboxClient is a typed mailbox RPC client for OORMailboxService. +type OORMailboxServiceMailboxClient struct { + // C is the underlying RPC-over-mailbox runtime client. + C rpc.RPCClient +} + +// NewOORMailboxServiceMailboxClient creates a typed mailbox client. +func NewOORMailboxServiceMailboxClient(c rpc.RPCClient) *OORMailboxServiceMailboxClient { + return &OORMailboxServiceMailboxClient{ + C: c, + } +} + +// OORMailboxServiceMailboxServer is the mailbox server interface for OORMailboxService. +type OORMailboxServiceMailboxServer interface { + // SubmitPackage handles SubmitPackage. + SubmitPackage(ctx context.Context, req *SubmitPackageRequest) (*SubmitPackageResponse, error) + // FinalizePackage handles FinalizePackage. + FinalizePackage(ctx context.Context, req *FinalizePackageRequest) (*FinalizePackageResponse, error) +} + +// RegisterOORMailboxServiceMailboxServer registers handlers for OORMailboxService. +func RegisterOORMailboxServiceMailboxServer(r rpc.Router, impl OORMailboxServiceMailboxServer) { + r.Handle("oorwire.OORMailboxService", "SubmitPackage", func() proto.Message { + return &SubmitPackageRequest{} + }, func(ctx context.Context, msg proto.Message) (proto.Message, error) { + req, ok := msg.(*SubmitPackageRequest) + if !ok { + return nil, fmt.Errorf("unexpected request type: %T", msg) + } + + return impl.SubmitPackage(ctx, req) + }) + r.Handle("oorwire.OORMailboxService", "FinalizePackage", func() proto.Message { + return &FinalizePackageRequest{} + }, func(ctx context.Context, msg proto.Message) (proto.Message, error) { + req, ok := msg.(*FinalizePackageRequest) + if !ok { + return nil, fmt.Errorf("unexpected request type: %T", msg) + } + + return impl.FinalizePackage(ctx, req) + }) +} + +// SubmitPackage calls the SubmitPackage RPC. +func (c *OORMailboxServiceMailboxClient) SubmitPackage(ctx context.Context, req *SubmitPackageRequest, opts ...rpc.RPCOptions) (*SubmitPackageResponse, error) { + var opt rpc.RPCOptions + if len(opts) > 0 { + opt = opts[0] + } + + result, err := c.C.SendRPC(ctx, rpc.ServiceMethod{ + Service: "oorwire.OORMailboxService", + Method: "SubmitPackage", + }, req, opt) + if err != nil { + return nil, err + } + + resp := new(SubmitPackageResponse) + if err := c.C.AwaitRPC(ctx, result.CorrelationID, resp); err != nil { + return nil, err + } + + return resp, nil +} + +// FinalizePackage calls the FinalizePackage RPC. +func (c *OORMailboxServiceMailboxClient) FinalizePackage(ctx context.Context, req *FinalizePackageRequest, opts ...rpc.RPCOptions) (*FinalizePackageResponse, error) { + var opt rpc.RPCOptions + if len(opts) > 0 { + opt = opts[0] + } + + result, err := c.C.SendRPC(ctx, rpc.ServiceMethod{ + Service: "oorwire.OORMailboxService", + Method: "FinalizePackage", + }, req, opt) + if err != nil { + return nil, err + } + + resp := new(FinalizePackageResponse) + if err := c.C.AwaitRPC(ctx, result.CorrelationID, resp); err != nil { + return nil, err + } + + return resp, nil +} diff --git a/oorwire/payloads.go b/oorwire/payloads.go new file mode 100644 index 000000000..8515fadc7 --- /dev/null +++ b/oorwire/payloads.go @@ -0,0 +1,311 @@ +package oorwire + +import ( + "fmt" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/lightninglabs/darepo-client/lib/tx/psbtutil" +) + +const ( + // ServiceName is the mailbox RPC service name for client/server OOR + // submit/finalize request-response flows. + ServiceName = "oorwire.OORMailboxService" + + // MethodSubmitPackage maps a client submit-package request. + MethodSubmitPackage = "SubmitPackage" + + // MethodFinalizePackage maps a client finalize-package request. + MethodFinalizePackage = "FinalizePackage" +) + +// SigningDescriptor is the minimal signing metadata needed by the server OOR +// actor to co-sign checkpoint inputs. +type SigningDescriptor struct { + Outpoint wire.OutPoint + OwnerKey *btcec.PublicKey + ExitDelay uint32 +} + +// NewSubmitPackageRequest builds a typed proto request for SubmitPackage. +func NewSubmitPackageRequest(ark *psbt.Packet, checkpoints []*psbt.Packet, + descs []SigningDescriptor) (*SubmitPackageRequest, error) { + + arkRaw, err := psbtutil.Serialize(ark) + if err != nil { + return nil, err + } + + checkpointRaw, err := encodePSBTSlice(checkpoints) + if err != nil { + return nil, err + } + + protoDescs := make([]*OORSigningDescriptor, 0, len(descs)) + for i := range descs { + desc, err := encodeSigningDescriptor(descs[i], i) + if err != nil { + return nil, err + } + + protoDescs = append(protoDescs, desc) + } + + return &SubmitPackageRequest{ + ArkPsbt: arkRaw, + CheckpointPsbts: checkpointRaw, + SigningDescriptors: protoDescs, + }, nil +} + +// ParseSubmitPackageRequest decodes a SubmitPackageRequest into domain types. +func ParseSubmitPackageRequest(req *SubmitPackageRequest) (*psbt.Packet, + []*psbt.Packet, []SigningDescriptor, error) { + + if req == nil { + return nil, nil, nil, fmt.Errorf("submit request is required") + } + + ark, err := psbtutil.Parse(req.ArkPsbt) + if err != nil { + return nil, nil, nil, err + } + + checkpoints, err := decodePSBTSlice(req.CheckpointPsbts) + if err != nil { + return nil, nil, nil, err + } + + descs := make([]SigningDescriptor, 0, len(req.SigningDescriptors)) + for i := range req.SigningDescriptors { + desc, err := decodeSigningDescriptor( + req.SigningDescriptors[i], i, + ) + if err != nil { + return nil, nil, nil, err + } + + descs = append(descs, desc) + } + + return ark, checkpoints, descs, nil +} + +// NewSubmitPackageResponse builds a typed proto response for SubmitPackage. +func NewSubmitPackageResponse(sessionID chainhash.Hash, + coSignedCheckpoints []*psbt.Packet) (*SubmitPackageResponse, error) { + + checkpointRaw, err := encodePSBTSlice(coSignedCheckpoints) + if err != nil { + return nil, err + } + + return &SubmitPackageResponse{ + SessionId: sessionID.CloneBytes(), + CoSignedCheckpointPsbts: checkpointRaw, + }, nil +} + +// ParseSubmitPackageResponse decodes a SubmitPackageResponse. +func ParseSubmitPackageResponse(resp *SubmitPackageResponse) (chainhash.Hash, + []*psbt.Packet, error) { + + if resp == nil { + return chainhash.Hash{}, nil, + fmt.Errorf("submit response is required") + } + + sessionID, err := decodeSessionID(resp.SessionId) + if err != nil { + return chainhash.Hash{}, nil, err + } + + checkpoints, err := decodePSBTSlice(resp.CoSignedCheckpointPsbts) + if err != nil { + return chainhash.Hash{}, nil, err + } + + return sessionID, checkpoints, nil +} + +// NewFinalizePackageRequest builds a typed proto request for FinalizePackage. +func NewFinalizePackageRequest(sessionID chainhash.Hash, + finalCheckpoints []*psbt.Packet) (*FinalizePackageRequest, error) { + + checkpointRaw, err := encodePSBTSlice(finalCheckpoints) + if err != nil { + return nil, err + } + + return &FinalizePackageRequest{ + SessionId: sessionID.CloneBytes(), + FinalCheckpointPsbts: checkpointRaw, + }, nil +} + +// ParseFinalizePackageRequest decodes a FinalizePackageRequest. +func ParseFinalizePackageRequest(req *FinalizePackageRequest) (chainhash.Hash, + []*psbt.Packet, error) { + + if req == nil { + return chainhash.Hash{}, nil, + fmt.Errorf("finalize request is required") + } + + sessionID, err := decodeSessionID(req.SessionId) + if err != nil { + return chainhash.Hash{}, nil, err + } + + finalCheckpoints, err := decodePSBTSlice(req.FinalCheckpointPsbts) + if err != nil { + return chainhash.Hash{}, nil, err + } + + return sessionID, finalCheckpoints, nil +} + +// NewFinalizePackageResponse builds a typed proto response for FinalizePackage. +func NewFinalizePackageResponse( + sessionID chainhash.Hash, +) *FinalizePackageResponse { + + return &FinalizePackageResponse{ + SessionId: sessionID.CloneBytes(), + } +} + +// ParseFinalizePackageResponse decodes a FinalizePackageResponse. +func ParseFinalizePackageResponse( + resp *FinalizePackageResponse, +) (chainhash.Hash, error) { + + if resp == nil { + return chainhash.Hash{}, + fmt.Errorf("finalize response is required") + } + + return decodeSessionID(resp.SessionId) +} + +// encodeSigningDescriptor converts one descriptor to proto form. +func encodeSigningDescriptor(desc SigningDescriptor, + index int) (*OORSigningDescriptor, error) { + + if desc.OwnerKey == nil { + return nil, fmt.Errorf( + "signing descriptor %d missing owner key", index, + ) + } + + return &OORSigningDescriptor{ + Outpoint: encodeOutPoint(desc.Outpoint), + OwnerKey: desc.OwnerKey.SerializeCompressed(), + ExitDelay: desc.ExitDelay, + }, nil +} + +// decodeSigningDescriptor converts one proto descriptor to domain form. +func decodeSigningDescriptor(desc *OORSigningDescriptor, + index int) (SigningDescriptor, error) { + + if desc == nil { + return SigningDescriptor{}, fmt.Errorf( + "signing descriptor %d is nil", index, + ) + } + + outpoint, err := decodeOutPoint(desc.Outpoint) + if err != nil { + return SigningDescriptor{}, err + } + + ownerKey, err := btcec.ParsePubKey(desc.OwnerKey) + if err != nil { + return SigningDescriptor{}, err + } + + return SigningDescriptor{ + Outpoint: outpoint, + OwnerKey: ownerKey, + ExitDelay: desc.ExitDelay, + }, nil +} + +// encodeOutPoint converts wire.OutPoint to proto form. +func encodeOutPoint(op wire.OutPoint) *OOROutPoint { + return &OOROutPoint{ + Txid: op.Hash.CloneBytes(), + Vout: op.Index, + } +} + +// decodeOutPoint converts proto outpoint to wire.OutPoint. +func decodeOutPoint(op *OOROutPoint) (wire.OutPoint, error) { + if op == nil { + return wire.OutPoint{}, fmt.Errorf("outpoint is required") + } + + if len(op.Txid) != chainhash.HashSize { + return wire.OutPoint{}, fmt.Errorf( + "invalid outpoint txid length: got %d want %d", + len(op.Txid), chainhash.HashSize, + ) + } + + var hash chainhash.Hash + copy(hash[:], op.Txid) + + return wire.OutPoint{ + Hash: hash, + Index: op.Vout, + }, nil +} + +// decodeSessionID converts a 32-byte session id into chainhash.Hash. +func decodeSessionID(raw []byte) (chainhash.Hash, error) { + if len(raw) != chainhash.HashSize { + return chainhash.Hash{}, fmt.Errorf( + "invalid session id length: got %d want %d", + len(raw), chainhash.HashSize, + ) + } + + var hash chainhash.Hash + copy(hash[:], raw) + + return hash, nil +} + +// encodePSBTSlice serializes a slice of PSBT packets. +func encodePSBTSlice(packets []*psbt.Packet) ([][]byte, error) { + out := make([][]byte, 0, len(packets)) + for i := range packets { + raw, err := psbtutil.Serialize(packets[i]) + if err != nil { + return nil, err + } + + out = append(out, raw) + } + + return out, nil +} + +// decodePSBTSlice parses a slice of serialized PSBT packets. +func decodePSBTSlice(rawPSBTs [][]byte) ([]*psbt.Packet, error) { + out := make([]*psbt.Packet, 0, len(rawPSBTs)) + for i := range rawPSBTs { + packet, err := psbtutil.Parse(rawPSBTs[i]) + if err != nil { + return nil, err + } + + out = append(out, packet) + } + + return out, nil +} diff --git a/oorwire/payloads_test.go b/oorwire/payloads_test.go new file mode 100644 index 000000000..6e1084897 --- /dev/null +++ b/oorwire/payloads_test.go @@ -0,0 +1,197 @@ +package oorwire + +import ( + "bytes" + "testing" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/lightninglabs/darepo-client/lib/tx/psbtutil" + "github.com/stretchr/testify/require" +) + +// TestSubmitPackageRequestRoundTrip verifies submit request conversion through +// typed proto payloads. +func TestSubmitPackageRequestRoundTrip(t *testing.T) { + t.Parallel() + + ark := mustTestPSBT(t, 11) + checkpoints := []*psbt.Packet{ + mustTestPSBT(t, 21), + mustTestPSBT(t, 22), + } + + priv, err := btcec.NewPrivateKey() + require.NoError(t, err) + + hashHex := "0f555f77697777895555121212121212" + + "12121212121212121212121212121212" + descs := []SigningDescriptor{{ + Outpoint: wire.OutPoint{ + Hash: mustHash(t, hashHex), + Index: 7, + }, + OwnerKey: priv.PubKey(), + ExitDelay: 144, + }} + + req, err := NewSubmitPackageRequest(ark, checkpoints, descs) + require.NoError(t, err) + + decArk, decCheckpoints, decDescs, err := ParseSubmitPackageRequest(req) + require.NoError(t, err) + require.Equal(t, 1, len(decDescs)) + require.Equal(t, descs[0].Outpoint, decDescs[0].Outpoint) + require.Equal( + t, + descs[0].OwnerKey.SerializeCompressed(), + decDescs[0].OwnerKey.SerializeCompressed(), + ) + require.Equal(t, descs[0].ExitDelay, decDescs[0].ExitDelay) + + require.True( + t, + bytes.Equal( + mustSerializePSBT(t, ark), + mustSerializePSBT(t, decArk), + ), + ) + require.Equal(t, len(checkpoints), len(decCheckpoints)) + for i := range checkpoints { + require.True( + t, + bytes.Equal( + mustSerializePSBT(t, checkpoints[i]), + mustSerializePSBT(t, decCheckpoints[i]), + ), + ) + } +} + +// TestSubmitPackageResponseRoundTrip verifies submit response conversion +// through typed proto payloads. +func TestSubmitPackageResponseRoundTrip(t *testing.T) { + t.Parallel() + + submitSessionIDHex := "8f555f77697777895555121212121212" + + "12121212121212121212121212121212" + sessionID := mustHash( + t, + submitSessionIDHex, + ) + checkpoints := []*psbt.Packet{ + mustTestPSBT(t, 31), + mustTestPSBT(t, 32), + } + + resp, err := NewSubmitPackageResponse(sessionID, checkpoints) + require.NoError(t, err) + + decSessionID, decCheckpoints, err := ParseSubmitPackageResponse(resp) + require.NoError(t, err) + require.Equal(t, sessionID, decSessionID) + require.Equal(t, len(checkpoints), len(decCheckpoints)) + + for i := range checkpoints { + require.True( + t, + bytes.Equal( + mustSerializePSBT(t, checkpoints[i]), + mustSerializePSBT(t, decCheckpoints[i]), + ), + ) + } +} + +// TestFinalizePackageRoundTrip verifies finalize request/response conversion +// through typed proto payloads. +func TestFinalizePackageRoundTrip(t *testing.T) { + t.Parallel() + + finalizeSessionIDHex := "af555f77697777895555121212121212" + + "12121212121212121212121212121212" + sessionID := mustHash( + t, + finalizeSessionIDHex, + ) + finalCheckpoints := []*psbt.Packet{ + mustTestPSBT(t, 41), + } + + req, err := NewFinalizePackageRequest(sessionID, finalCheckpoints) + require.NoError(t, err) + + decSessionID, decFinalCheckpoints, err := ParseFinalizePackageRequest( + req, + ) + require.NoError(t, err) + require.Equal(t, sessionID, decSessionID) + require.Equal(t, len(finalCheckpoints), len(decFinalCheckpoints)) + require.True( + t, + bytes.Equal( + mustSerializePSBT(t, finalCheckpoints[0]), + mustSerializePSBT(t, decFinalCheckpoints[0]), + ), + ) + + resp := NewFinalizePackageResponse(sessionID) + respSessionID, err := ParseFinalizePackageResponse(resp) + require.NoError(t, err) + require.Equal(t, sessionID, respSessionID) +} + +// TestParseFinalizePackageResponseRejectsInvalidSessionLength verifies session +// id validation on typed finalize responses. +func TestParseFinalizePackageResponseRejectsInvalidSessionLength(t *testing.T) { + t.Parallel() + + _, err := ParseFinalizePackageResponse(&FinalizePackageResponse{ + SessionId: []byte{1, 2, 3}, + }) + require.ErrorContains(t, err, "invalid session id length") +} + +// mustHash parses a chain hash string for tests. +func mustHash(t *testing.T, hash string) chainhash.Hash { + t.Helper() + + parsed, err := chainhash.NewHashFromStr(hash) + require.NoError(t, err) + + return *parsed +} + +// mustTestPSBT builds a minimal serializable PSBT packet for tests. +func mustTestPSBT(t *testing.T, marker byte) *psbt.Packet { + t.Helper() + + tx := wire.NewMsgTx(2) + tx.AddTxIn(&wire.TxIn{ + PreviousOutPoint: wire.OutPoint{ + Hash: chainhash.Hash{marker}, + }, + }) + tx.AddTxOut(&wire.TxOut{ + Value: 1000, + PkScript: []byte{txscript.OP_TRUE}, + }) + + packet, err := psbt.NewFromUnsignedTx(tx) + require.NoError(t, err) + + return packet +} + +// mustSerializePSBT serializes a PSBT packet in tests. +func mustSerializePSBT(t *testing.T, packet *psbt.Packet) []byte { + t.Helper() + + raw, err := psbtutil.Serialize(packet) + require.NoError(t, err) + + return raw +} diff --git a/round/mailbox_codec.go b/round/mailbox_codec.go new file mode 100644 index 000000000..4df9e2c32 --- /dev/null +++ b/round/mailbox_codec.go @@ -0,0 +1,286 @@ +package round + +import ( + "fmt" + + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/lightninglabs/darepo-client/lib/tree" + "github.com/lightninglabs/darepo-client/roundwire" +) + +// DecodeServerMailboxPayload decodes a roundwire payload into a client round +// FSM event. +func DecodeServerMailboxPayload( + method string, raw []byte, +) (ClientEvent, error) { + + switch method { + case roundwire.MethodClientSuccessResp: + return decodeClientSuccessResp(raw) + + case roundwire.MethodClientBatchInfo: + return decodeClientBatchInfo(raw) + + case roundwire.MethodClientAwaitingInputSigsResp: + return decodeClientAwaitingInputSigsResp(raw) + + case roundwire.MethodClientVTXOAggNonces: + return decodeClientVTXOAggNonces(raw) + + case roundwire.MethodClientVTXOAggSigs: + return decodeClientVTXOAggSigs(raw) + + case roundwire.MethodClientErrorResp: + return decodeClientErrorResp(raw) + + case roundwire.MethodClientRoundFailedResp: + return decodeClientRoundFailedResp(raw) + + default: + return nil, fmt.Errorf("unknown roundwire method: %s", method) + } +} + +func decodeClientSuccessResp(raw []byte) (ClientEvent, error) { + var payload roundwire.ClientSuccessRespPayload + if err := roundwire.DecodePayload(raw, &payload); err != nil { + return nil, err + } + + roundID, err := ParseRoundID(payload.RoundId) + if err != nil { + return nil, err + } + + boardingOutpoints, err := decodeOutpointSlice( + payload.AcceptedBoardingOutpoints, + ) + if err != nil { + return nil, err + } + + vtxoOutpoints, err := decodeOutpointSlice( + payload.AcceptedVtxoOutpoints, + ) + if err != nil { + return nil, err + } + + return &RoundJoined{ + RoundID: roundID, + AcceptedBoardingOutpoints: boardingOutpoints, + AcceptedVTXOOutpoints: vtxoOutpoints, + }, nil +} + +func decodeClientBatchInfo(raw []byte) (ClientEvent, error) { + var payload roundwire.ClientBatchInfoPayload + if err := roundwire.DecodePayload(raw, &payload); err != nil { + return nil, err + } + + roundID, err := ParseRoundID(payload.RoundId) + if err != nil { + return nil, err + } + + packet, err := roundwire.DecodePSBT(payload.BatchPsbtHex) + if err != nil { + return nil, err + } + + treePaths := make(map[int]*tree.Tree, len(payload.VtxoTreePaths)) + for _, path := range payload.VtxoTreePaths { + if path == nil { + continue + } + + decodedTree, decodeErr := roundwire.DecodeTree(path.Tree) + if decodeErr != nil { + return nil, decodeErr + } + + treePaths[int(path.OutputIndex)] = decodedTree + } + + forfeitMappings := make( + map[wire.OutPoint]*ConnectorLeafInfo, + len(payload.ConnectorLeaves), + ) + for _, leaf := range payload.ConnectorLeaves { + if leaf == nil || leaf.VtxoOutpoint == nil || + leaf.LeafOutpoint == nil || leaf.LeafOutput == nil { + + return nil, fmt.Errorf( + "connector leaf payload incomplete", + ) + } + + vtxoOutpoint, decodeErr := roundwire.DecodeOutPoint( + leaf.VtxoOutpoint, + ) + if decodeErr != nil { + return nil, decodeErr + } + + connectorOutpoint, decodeErr := roundwire.DecodeOutPoint( + leaf.LeafOutpoint, + ) + if decodeErr != nil { + return nil, decodeErr + } + + leafOutput, decodeErr := roundwire.DecodeTxOut(leaf.LeafOutput) + if decodeErr != nil { + return nil, decodeErr + } + + forfeitMappings[vtxoOutpoint] = &ConnectorLeafInfo{ + ConnectorOutpoint: connectorOutpoint, + ConnectorPkScript: leafOutput.PkScript, + ConnectorAmount: leafOutput.Value, + } + } + + return &CommitmentTxBuilt{ + RoundID: roundID, + Tx: packet, + VTXOTreePaths: treePaths, + ForfeitMappings: forfeitMappings, + }, nil +} + +func decodeClientAwaitingInputSigsResp(raw []byte) (ClientEvent, error) { + var payload roundwire.ClientAwaitingInputSigsRespPayload + if err := roundwire.DecodePayload(raw, &payload); err != nil { + return nil, err + } + + roundID, err := ParseRoundID(payload.RoundId) + if err != nil { + return nil, err + } + + return &AwaitingBoardingSigs{ + RoundID: roundID, + }, nil +} + +func decodeClientVTXOAggNonces(raw []byte) (ClientEvent, error) { + var payload roundwire.ClientVTXOAggNoncesPayload + if err := roundwire.DecodePayload(raw, &payload); err != nil { + return nil, err + } + + roundID, err := ParseRoundID(payload.RoundId) + if err != nil { + return nil, err + } + + nonces := make(map[tree.TxID]tree.Musig2PubNonce, len(payload.Nonces)) + for _, nonceEntry := range payload.Nonces { + txID, decodeErr := chainhash.NewHashFromStr( + nonceEntry.TxIdHex, + ) + if decodeErr != nil { + return nil, decodeErr + } + + nonce, decodeErr := roundwire.DecodeNonce( + nonceEntry.NonceHex, + ) + if decodeErr != nil { + return nil, decodeErr + } + + nonces[*txID] = nonce + } + + return &NoncesAggregated{ + RoundID: roundID, + AggNonces: nonces, + }, nil +} + +func decodeClientVTXOAggSigs(raw []byte) (ClientEvent, error) { + var payload roundwire.ClientVTXOAggSigsPayload + if err := roundwire.DecodePayload(raw, &payload); err != nil { + return nil, err + } + + roundID, err := ParseRoundID(payload.RoundId) + if err != nil { + return nil, err + } + + sigs := make(map[tree.TxID]*schnorr.Signature, len(payload.Signatures)) + for _, sigEntry := range payload.Signatures { + txID, decodeErr := chainhash.NewHashFromStr( + sigEntry.TxIdHex, + ) + if decodeErr != nil { + return nil, decodeErr + } + + sig, decodeErr := roundwire.DecodeSchnorrSignature( + sigEntry.SignatureHex, + ) + if decodeErr != nil { + return nil, decodeErr + } + + sigs[*txID] = sig + } + + return &OperatorSigned{ + RoundID: roundID, + AggSigs: sigs, + }, nil +} + +func decodeClientErrorResp(raw []byte) (ClientEvent, error) { + var payload roundwire.ClientErrorRespPayload + if err := roundwire.DecodePayload(raw, &payload); err != nil { + return nil, err + } + + return &BoardingFailed{ + Reason: payload.Error, + Recoverable: true, + }, nil +} + +func decodeClientRoundFailedResp(raw []byte) (ClientEvent, error) { + var payload roundwire.ClientRoundFailedRespPayload + if err := roundwire.DecodePayload(raw, &payload); err != nil { + return nil, err + } + + return &BoardingFailed{ + Reason: payload.Reason, + Recoverable: true, + }, nil +} + +func decodeOutpointSlice( + payloads []*roundwire.OutPointPayload, +) ([]wire.OutPoint, error) { + + outpoints := make([]wire.OutPoint, 0, len(payloads)) + for _, payload := range payloads { + if payload == nil { + continue + } + + decoded, err := roundwire.DecodeOutPoint(payload) + if err != nil { + return nil, err + } + + outpoints = append(outpoints, decoded) + } + + return outpoints, nil +} diff --git a/round/mailbox_dispatchers.go b/round/mailbox_dispatchers.go new file mode 100644 index 000000000..c71bd8fed --- /dev/null +++ b/round/mailbox_dispatchers.go @@ -0,0 +1,62 @@ +package round + +import ( + "context" + "fmt" + + "github.com/lightninglabs/darepo-client/baselib/actor" + "github.com/lightninglabs/darepo-client/lib/actormsg" + mailboxpb "github.com/lightninglabs/darepo-client/mailbox/pb" + mailboxrpc "github.com/lightninglabs/darepo-client/mailbox/rpc" + "github.com/lightninglabs/darepo-client/roundwire" + "github.com/lightninglabs/darepo-client/serverconn" +) + +// NewMailboxDispatchers returns serverconn dispatchers for server->client round +// EVENT envelopes. +func NewMailboxDispatchers( + roundRef actor.ActorRef[ + actormsg.RoundReceivable, + actormsg.RoundActorResp, + ], +) serverconn.DispatcherMap { + + dispatch := func(ctx context.Context, env *mailboxpb.Envelope) error { + if env == nil || env.Rpc == nil || env.Body == nil { + return fmt.Errorf("invalid round event envelope") + } + + event, err := DecodeServerMailboxPayload( + env.Rpc.Method, env.Body.Value, + ) + if err != nil { + return fmt.Errorf("decode round event payload: %w", err) + } + + return roundRef.Tell( + ctx, &ServerMessageNotification{ + Message: event, + }, + ) + } + + methods := []string{ + roundwire.MethodClientErrorResp, + roundwire.MethodClientSuccessResp, + roundwire.MethodClientAwaitingInputSigsResp, + roundwire.MethodClientVTXOAggNonces, + roundwire.MethodClientVTXOAggSigs, + roundwire.MethodClientBatchInfo, + roundwire.MethodClientRoundFailedResp, + } + + dispatchers := make(serverconn.DispatcherMap, len(methods)) + for _, method := range methods { + dispatchers[mailboxrpc.ServiceMethod{ + Service: roundwire.ServiceName, + Method: method, + }] = dispatch + } + + return dispatchers +} diff --git a/round/outbox_messages.go b/round/outbox_messages.go index ea124ca78..fbb49e61e 100644 --- a/round/outbox_messages.go +++ b/round/outbox_messages.go @@ -1,6 +1,10 @@ package round import ( + "encoding/hex" + "fmt" + "sort" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" @@ -9,6 +13,7 @@ import ( "github.com/lightninglabs/darepo-client/baselib/actor" "github.com/lightninglabs/darepo-client/lib/tree" "github.com/lightninglabs/darepo-client/lib/types" + "github.com/lightninglabs/darepo-client/roundwire" fn "github.com/lightningnetwork/lnd/fn/v2" "google.golang.org/protobuf/proto" ) @@ -115,40 +120,213 @@ type SubmitForfeitSigRequest struct { func (m *SubmitForfeitSigRequest) clientOutMsgSealed() {} -// ToProto converts JoinRoundRequest to a protobuf message. -// TODO: Implement actual proto conversion once proto definitions are available. +// RPCService returns the mailbox RPC service for this message. +func (m *JoinRoundRequest) RPCService() string { + return roundwire.ServiceName +} + +// RPCMethod returns the mailbox RPC method for this message. +func (m *JoinRoundRequest) RPCMethod() string { + return roundwire.MethodJoinRoundRequest +} + +// ToProto converts JoinRoundRequest to a protobuf payload message. func (m *JoinRoundRequest) ToProto() proto.Message { - // Placeholder: return nil for now. This will be replaced with actual - // proto message construction: - // return &pb.JoinRoundRequest{...} - return nil + payload, err := joinRoundRequestPayload(m) + if err != nil { + log.ErrorS(nil, "Encode JoinRoundRequest failed", err) + + return nil + } + + msg, err := roundwire.WrapPayload(payload) + if err != nil { + log.ErrorS(nil, "Wrap JoinRoundRequest payload failed", err) + + return nil + } + + return msg +} + +// RPCService returns the mailbox RPC service for this message. +func (m *SubmitNoncesRequest) RPCService() string { + return roundwire.ServiceName } -// ToProto converts SubmitNoncesRequest to a protobuf message. -// TODO: Implement actual proto conversion once proto definitions are available. +// RPCMethod returns the mailbox RPC method for this message. +func (m *SubmitNoncesRequest) RPCMethod() string { + return roundwire.MethodSubmitNoncesRequest +} + +// ToProto converts SubmitNoncesRequest to a protobuf payload message. func (m *SubmitNoncesRequest) ToProto() proto.Message { - // Placeholder: return nil for now. This will be replaced with actual - // proto message construction: - // return &pb.SubmitNoncesRequest{...} - return nil + payload := &roundwire.SubmitNoncesPayload{ + RoundId: m.RoundID.String(), + Entries: make( + []*roundwire.SignerNonceBundle, 0, + len(m.Nonces), + ), + } + + for signerKey, txNonces := range m.Nonces { + entry := &roundwire.SignerNonceBundle{ + SignerKeyHex: hex.EncodeToString(signerKey[:]), + Nonces: make( + []*roundwire.TxNonceEntry, 0, len(txNonces), + ), + } + + for txID, nonce := range txNonces { + entry.Nonces = append(entry.Nonces, + &roundwire.TxNonceEntry{ + TxIdHex: txID.String(), + NonceHex: roundwire.EncodeNonce(nonce), + }, + ) + } + + roundwire.SortTxNonceEntries(entry.Nonces) + payload.Entries = append(payload.Entries, entry) + } + + roundwire.SortSignerNonceBundles(payload.Entries) + + msg, err := roundwire.WrapPayload(payload) + if err != nil { + log.ErrorS(nil, "Wrap SubmitNoncesRequest payload failed", err) + + return nil + } + + return msg } -// ToProto converts SubmitPartialSigRequest to a protobuf message. -// TODO: Implement actual proto conversion once proto definitions are available. +// RPCService returns the mailbox RPC service for this message. +func (m *SubmitPartialSigRequest) RPCService() string { + return roundwire.ServiceName +} + +// RPCMethod returns the mailbox RPC method for this message. +func (m *SubmitPartialSigRequest) RPCMethod() string { + return roundwire.MethodSubmitPartialSigRequest +} + +// ToProto converts SubmitPartialSigRequest to a protobuf payload message. func (m *SubmitPartialSigRequest) ToProto() proto.Message { - // Placeholder: return nil for now. This will be replaced with actual - // proto message construction: - // return &pb.SubmitPartialSigRequest{...} - return nil + payload := &roundwire.SubmitPartialSigsPayload{ + RoundId: m.RoundID.String(), + Entries: make( + []*roundwire.SignerSigBundle, 0, + len(m.Signatures), + ), + } + + for signerKey, txSigs := range m.Signatures { + entry := &roundwire.SignerSigBundle{ + SignerKeyHex: hex.EncodeToString(signerKey[:]), + Signatures: make( + []*roundwire.TxSigEntry, 0, len(txSigs), + ), + } + + for txID, sig := range txSigs { + sigHex, err := roundwire.EncodePartialSignature(sig) + if err != nil { + log.ErrorS(nil, "Encode partial signature failed", err) + + return nil + } + + entry.Signatures = append(entry.Signatures, + &roundwire.TxSigEntry{ + TxIdHex: txID.String(), + SignatureHex: sigHex, + }, + ) + } + + roundwire.SortTxSigEntries(entry.Signatures) + payload.Entries = append(payload.Entries, entry) + } + + roundwire.SortSignerSigBundles(payload.Entries) + + msg, err := roundwire.WrapPayload(payload) + if err != nil { + log.ErrorS(nil, "Wrap SubmitPartialSigRequest payload failed", + err) + + return nil + } + + return msg +} + +// RPCService returns the mailbox RPC service for this message. +func (m *SubmitForfeitSigRequest) RPCService() string { + return roundwire.ServiceName +} + +// RPCMethod returns the mailbox RPC method for this message. +func (m *SubmitForfeitSigRequest) RPCMethod() string { + return roundwire.MethodSubmitForfeitSigRequest } -// ToProto converts SubmitForfeitSigRequest to a protobuf message. -// TODO: Implement actual proto conversion once proto definitions are available. +// ToProto converts SubmitForfeitSigRequest to a protobuf payload message. func (m *SubmitForfeitSigRequest) ToProto() proto.Message { - // Placeholder: return nil for now. This will be replaced with actual - // proto message construction: - // return &pb.SubmitForfeitSigRequest{...} - return nil + payload := &roundwire.SubmitForfeitSigsPayload{ + RoundId: m.RoundID.String(), + Signatures: make( + []*roundwire.BoardingInputSigPayload, 0, + len(m.Signatures), + ), + } + + for _, sig := range m.Signatures { + encodedOutpoint := roundwire.EncodeOutPoint(sig.Outpoint) + payload.Signatures = append(payload.Signatures, + &roundwire.BoardingInputSigPayload{ + InputIndex: int32(sig.InputIndex), + Outpoint: &roundwire.OutPointPayload{ + TxId: encodedOutpoint.TxId, + Vout: encodedOutpoint.Vout, + }, + SignatureHex: roundwire.EncodeSchnorrSignature( + sig.ClientSignature, + ), + }, + ) + } + + sort.Slice(payload.Signatures, func(i, j int) bool { + if payload.Signatures[i].Outpoint.TxId != + payload.Signatures[j].Outpoint.TxId { + + return payload.Signatures[i].Outpoint.TxId < + payload.Signatures[j].Outpoint.TxId + } + + if payload.Signatures[i].Outpoint.Vout != + payload.Signatures[j].Outpoint.Vout { + + return payload.Signatures[i].Outpoint.Vout < + payload.Signatures[j].Outpoint.Vout + } + + return payload.Signatures[i].InputIndex < + payload.Signatures[j].InputIndex + }) + + msg, err := roundwire.WrapPayload(payload) + if err != nil { + log.ErrorS(nil, "Wrap SubmitForfeitSigRequest payload failed", + err) + + return nil + } + + return msg } // ForfeitRequestToVTXO is emitted by the FSM when a VTXO must sign a forfeit @@ -242,10 +420,207 @@ func (m *SubmitVTXOForfeitSigsToServer) MessageType() string { return "SubmitVTXOForfeitSigsToServer" } -// ToProto converts SubmitVTXOForfeitSigsToServer to a protobuf message. -// TODO: Implement actual proto conversion once proto definitions are available. +// RPCService returns the mailbox RPC service for this message. +func (m *SubmitVTXOForfeitSigsToServer) RPCService() string { + return roundwire.ServiceName +} + +// RPCMethod returns the mailbox RPC method for this message. +func (m *SubmitVTXOForfeitSigsToServer) RPCMethod() string { + return roundwire.MethodSubmitVTXOForfeitSigsRequest +} + +// ToProto converts SubmitVTXOForfeitSigsToServer to a protobuf payload. func (m *SubmitVTXOForfeitSigsToServer) ToProto() proto.Message { - return nil + payload := &roundwire.SubmitVTXOForfeitSigsPayload{ + RoundId: m.RoundID.String(), + Entries: make( + []*roundwire.VTXOForfeitSigPayload, 0, + len(m.ForfeitSigs), + ), + } + + for outpoint, sig := range m.ForfeitSigs { + unsignedTx, ok := m.ForfeitTxs[outpoint] + if !ok { + log.ErrorS(nil, + "Missing forfeit tx for outpoint in "+ + "SubmitVTXOForfeitSigsToServer", + fmt.Errorf("forfeit tx missing"), + ) + + return nil + } + + txHex, err := roundwire.EncodeMsgTx(unsignedTx) + if err != nil { + log.ErrorS(nil, "Encode forfeit tx failed", err) + + return nil + } + + encodedOutpoint := roundwire.EncodeOutPoint(outpoint) + payload.Entries = append(payload.Entries, + &roundwire.VTXOForfeitSigPayload{ + VtxoOutpoint: &roundwire.OutPointPayload{ + TxId: encodedOutpoint.TxId, + Vout: encodedOutpoint.Vout, + }, + SignatureHex: roundwire.EncodeSchnorrSignature( + sig, + ), + UnsignedTxHex: txHex, + }, + ) + } + + sort.Slice(payload.Entries, func(i, j int) bool { + if payload.Entries[i].VtxoOutpoint.TxId != + payload.Entries[j].VtxoOutpoint.TxId { + + return payload.Entries[i].VtxoOutpoint.TxId < + payload.Entries[j].VtxoOutpoint.TxId + } + + return payload.Entries[i].VtxoOutpoint.Vout < + payload.Entries[j].VtxoOutpoint.Vout + }) + + msg, err := roundwire.WrapPayload(payload) + if err != nil { + log.ErrorS(nil, + "Wrap SubmitVTXOForfeitSigsToServer payload failed", + err, + ) + + return nil + } + + return msg +} + +func joinRoundRequestPayload( + msg *JoinRoundRequest, +) (*roundwire.JoinRoundRequestPayload, error) { + + payload := &roundwire.JoinRoundRequestPayload{ + RoundId: msg.RoundID, + Identifier: roundwire.EncodePubKey(msg.Identifier), + BoardingRequests: make( + []*roundwire.BoardingRequestPayload, 0, + len(msg.BoardingRequests), + ), + VtxoRequests: make( + []*roundwire.VTXORequestPayload, 0, + len(msg.VTXORequests), + ), + ForfeitRequests: make( + []*roundwire.ForfeitRequestPayload, 0, + len(msg.ForfeitRequests), + ), + LeaveRequests: make( + []*roundwire.LeaveRequestPayload, 0, + len(msg.LeaveRequests), + ), + } + + for i := range msg.BoardingRequests { + req := msg.BoardingRequests[i] + if req.Outpoint == nil { + return nil, fmt.Errorf( + "boarding request %d has nil outpoint", i, + ) + } + + if req.TxProof.IsSome() { + return nil, fmt.Errorf( + "boarding request %d has unsupported tx proof", + i, + ) + } + + outpoint := roundwire.EncodeOutPoint(*req.Outpoint) + payload.BoardingRequests = append( + payload.BoardingRequests, + &roundwire.BoardingRequestPayload{ + Outpoint: &roundwire.OutPointPayload{ + TxId: outpoint.TxId, + Vout: outpoint.Vout, + }, + ClientKey: roundwire.EncodePubKey( + req.ClientKey, + ), + OperatorKey: roundwire.EncodePubKey( + req.OperatorKey, + ), + ExitDelay: req.ExitDelay, + }, + ) + } + + for i := range msg.VTXORequests { + req := msg.VTXORequests[i] + signingKey := roundwire.EncodeKeyDescriptor(req.SigningKey) + payload.VtxoRequests = append( + payload.VtxoRequests, + &roundwire.VTXORequestPayload{ + Amount: int64(req.Amount), + PkScriptHex: hex.EncodeToString( + req.PkScript, + ), + Expiry: req.Expiry, + ClientKey: roundwire.EncodePubKey( + req.ClientKey, + ), + OperatorKey: roundwire.EncodePubKey( + req.OperatorKey, + ), + SigningKey: &roundwire.KeyDescriptorPayload{ + KeyFamily: signingKey.KeyFamily, + KeyIndex: signingKey.KeyIndex, + PubKeyHex: signingKey.PubKeyHex, + }, + }, + ) + } + + for _, req := range msg.ForfeitRequests { + encodedOutpoint := roundwire.EncodeOutPoint(req.VTXOOutpoint) + payload.ForfeitRequests = append( + payload.ForfeitRequests, + &roundwire.ForfeitRequestPayload{ + VtxoOutpoint: &roundwire.OutPointPayload{ + TxId: encodedOutpoint.TxId, + Vout: encodedOutpoint.Vout, + }, + }, + ) + } + + for i := range msg.LeaveRequests { + req := msg.LeaveRequests[i] + output := roundwire.EncodeTxOut(req.Output) + payload.LeaveRequests = append( + payload.LeaveRequests, + &roundwire.LeaveRequestPayload{ + Output: &roundwire.TxOutPayload{ + Value: output.Value, + PkScript: output.PkScript, + }, + }, + ) + } + + if msg.Auth != nil { + payload.Auth = &roundwire.JoinRoundAuthPayload{ + MessageHex: hex.EncodeToString(msg.Auth.Message), + ValidFrom: msg.Auth.ValidFrom, + ValidUntil: msg.Auth.ValidUntil, + SignatureHex: hex.EncodeToString(msg.Auth.Signature), + } + } + + return payload, nil } // RegisterConfirmationRequest is emitted by the FSM to request chain monitoring diff --git a/round/outbox_messages_test.go b/round/outbox_messages_test.go index b190bf2a1..c47385dfd 100644 --- a/round/outbox_messages_test.go +++ b/round/outbox_messages_test.go @@ -22,10 +22,8 @@ func testRoundIDForMsg(seed string) RoundID { return RoundID(id) } -// TestOutboxMessagesToProto ensures that ToProto() methods compile and return -// the expected nil placeholders. These placeholders will be replaced with -// actual proto marshaling once the proto definitions are finalized, but this -// test prevents accidental breakage of the interface contract in the meantime. +// TestOutboxMessagesToProto ensures that ToProto() methods return non-nil +// payload wrappers for mailbox transport. func TestOutboxMessagesToProto(t *testing.T) { t.Parallel() @@ -36,15 +34,23 @@ func TestOutboxMessagesToProto(t *testing.T) { t.Run("JoinRoundRequest_ToProto", func(t *testing.T) { t.Parallel() + boardingHash := chainhash.HashH([]byte("boarding")) + msg := &JoinRoundRequest{ BoardingRequests: []types.BoardingRequest{ - {ClientKey: pubKey, OperatorKey: pubKey}, + { + Outpoint: &wire.OutPoint{ + Hash: boardingHash, + }, + ClientKey: pubKey, + OperatorKey: pubKey, + }, }, VTXORequests: []types.VTXORequest{}, } result := msg.ToProto() - require.Nil(t, result) + require.NotNil(t, result) }) t.Run("SubmitNoncesRequest_ToProto", func(t *testing.T) { @@ -65,7 +71,7 @@ func TestOutboxMessagesToProto(t *testing.T) { } result := msg.ToProto() - require.Nil(t, result) + require.NotNil(t, result) }) t.Run("SubmitPartialSigRequest_ToProto", func(t *testing.T) { @@ -89,7 +95,7 @@ func TestOutboxMessagesToProto(t *testing.T) { } result := msg.ToProto() - require.Nil(t, result) + require.NotNil(t, result) }) t.Run("SubmitForfeitSigRequest_ToProto", func(t *testing.T) { @@ -106,7 +112,7 @@ func TestOutboxMessagesToProto(t *testing.T) { } result := msg.ToProto() - require.Nil(t, result) + require.NotNil(t, result) }) } diff --git a/roundwire/payloads.go b/roundwire/payloads.go new file mode 100644 index 000000000..018c81988 --- /dev/null +++ b/roundwire/payloads.go @@ -0,0 +1,590 @@ +package roundwire + +import ( + "bytes" + "encoding/hex" + "fmt" + "sort" + "strconv" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/lightninglabs/darepo-client/lib/tree" + "github.com/lightningnetwork/lnd/keychain" + "google.golang.org/protobuf/proto" +) + +const ( + // ServiceName is the mailbox RPC service name for round state-machine + // EVENT envelopes exchanged between client round actor and + // server rounds actor. + ServiceName = "roundwire.RoundMailboxService" + + // MethodJoinRoundRequest maps a client JoinRound request. + MethodJoinRoundRequest = "JoinRoundRequest" + + // MethodSubmitNoncesRequest maps a client nonce submission. + MethodSubmitNoncesRequest = "SubmitNoncesRequest" + + // MethodSubmitPartialSigRequest maps a client partial signature + // submission. + MethodSubmitPartialSigRequest = "SubmitPartialSigRequest" + + // MethodSubmitForfeitSigRequest maps a client boarding input signature + // submission. + MethodSubmitForfeitSigRequest = "SubmitForfeitSigRequest" + + // MethodSubmitVTXOForfeitSigsRequest maps a client VTXO + // forfeit signature submission. + MethodSubmitVTXOForfeitSigsRequest = "SubmitVTXOForfeitSigsRequest" + + // MethodClientErrorResp maps a server error response to a client. + MethodClientErrorResp = "ClientErrorResp" + + // MethodClientSuccessResp maps a server successful join response. + MethodClientSuccessResp = "ClientSuccessResp" + + // MethodClientAwaitingInputSigsResp maps a server "awaiting signatures" + // response. + MethodClientAwaitingInputSigsResp = "ClientAwaitingInputSigsResp" + + // MethodClientVTXOAggNonces maps a server aggregated nonce response. + MethodClientVTXOAggNonces = "ClientVTXOAggNonces" + + // MethodClientVTXOAggSigs maps a server aggregated signature response. + MethodClientVTXOAggSigs = "ClientVTXOAggSigs" + + // MethodClientBatchInfo maps a server batch information response. + MethodClientBatchInfo = "ClientBatchInfo" + + // MethodClientRoundFailedResp maps a server round failure response. + MethodClientRoundFailedResp = "ClientRoundFailedResp" +) + +// WrapPayload returns the payload as-is for proto-native mailbox transport. +func WrapPayload(payload proto.Message) (proto.Message, error) { + if payload == nil { + return nil, fmt.Errorf("payload is required") + } + + return payload, nil +} + +// UnwrapPayload marshals a proto payload into raw bytes. +func UnwrapPayload(msg proto.Message) ([]byte, error) { + if msg == nil { + return nil, fmt.Errorf("payload is required") + } + + return proto.Marshal(msg) +} + +// DecodePayload unmarshals raw proto payload bytes into dst. +func DecodePayload(raw []byte, dst proto.Message) error { + if dst == nil { + return fmt.Errorf("destination payload is required") + } + + return (proto.UnmarshalOptions{ + DiscardUnknown: true, + }).Unmarshal(raw, dst) +} + +// EncodeOutPoint converts a wire.OutPoint into its payload representation. +func EncodeOutPoint(op wire.OutPoint) OutPointPayload { + return OutPointPayload{ + TxId: op.Hash.String(), + Vout: op.Index, + } +} + +// DecodeOutPoint converts a payload outpoint into wire.OutPoint. +func DecodeOutPoint(op *OutPointPayload) (wire.OutPoint, error) { + if op == nil { + return wire.OutPoint{}, fmt.Errorf( + "outpoint payload is required", + ) + } + + h, err := chainhash.NewHashFromStr(op.TxId) + if err != nil { + return wire.OutPoint{}, err + } + + return wire.OutPoint{ + Hash: *h, + Index: op.Vout, + }, nil +} + +// EncodeTxOut converts a wire.TxOut into its payload representation. +func EncodeTxOut(out *wire.TxOut) TxOutPayload { + if out == nil { + return TxOutPayload{} + } + + return TxOutPayload{ + Value: out.Value, + PkScript: hex.EncodeToString(out.PkScript), + } +} + +// DecodeTxOut converts a payload txout into wire.TxOut. +func DecodeTxOut(out *TxOutPayload) (*wire.TxOut, error) { + if out == nil { + return nil, fmt.Errorf("txout payload is required") + } + + pkScript, err := hex.DecodeString(out.PkScript) + if err != nil { + return nil, err + } + + return &wire.TxOut{ + Value: out.Value, + PkScript: pkScript, + }, nil +} + +// EncodePubKey encodes a compressed public key as hex string. +func EncodePubKey(pk *btcec.PublicKey) string { + if pk == nil { + return "" + } + + return hex.EncodeToString(pk.SerializeCompressed()) +} + +// DecodePubKey decodes a compressed public key hex string. +func DecodePubKey(pkHex string) (*btcec.PublicKey, error) { + if pkHex == "" { + return nil, nil + } + + raw, err := hex.DecodeString(pkHex) + if err != nil { + return nil, err + } + + return btcec.ParsePubKey(raw) +} + +// EncodeKeyDescriptor converts a key descriptor into payload form. +func EncodeKeyDescriptor(desc keychain.KeyDescriptor) KeyDescriptorPayload { + return KeyDescriptorPayload{ + KeyFamily: int32(desc.KeyLocator.Family), + KeyIndex: desc.KeyLocator.Index, + PubKeyHex: EncodePubKey(desc.PubKey), + } +} + +// DecodeKeyDescriptor converts payload key descriptor to keychain descriptor. +func DecodeKeyDescriptor( + desc *KeyDescriptorPayload, +) (keychain.KeyDescriptor, error) { + + if desc == nil { + return keychain.KeyDescriptor{}, fmt.Errorf( + "key descriptor payload is required", + ) + } + + pubKey, err := DecodePubKey(desc.PubKeyHex) + if err != nil { + return keychain.KeyDescriptor{}, err + } + + return keychain.KeyDescriptor{ + KeyLocator: keychain.KeyLocator{ + Family: keychain.KeyFamily(desc.KeyFamily), + Index: desc.KeyIndex, + }, + PubKey: pubKey, + }, nil +} + +// EncodeNonce encodes a MuSig2 nonce into hex. +func EncodeNonce(n tree.Musig2PubNonce) string { + return hex.EncodeToString(n[:]) +} + +// DecodeNonce decodes a MuSig2 nonce from hex. +func DecodeNonce(nonceHex string) (tree.Musig2PubNonce, error) { + var nonce tree.Musig2PubNonce + + raw, err := hex.DecodeString(nonceHex) + if err != nil { + return nonce, err + } + + if len(raw) != len(nonce) { + return nonce, fmt.Errorf("invalid nonce length: got %d want %d", + len(raw), len(nonce)) + } + + copy(nonce[:], raw) + + return nonce, nil +} + +// EncodePartialSignature encodes a MuSig2 partial signature to hex. +func EncodePartialSignature(sig *musig2.PartialSignature) (string, error) { + if sig == nil { + return "", nil + } + + var buf bytes.Buffer + if err := sig.Encode(&buf); err != nil { + return "", err + } + + return hex.EncodeToString(buf.Bytes()), nil +} + +// DecodePartialSignature decodes a MuSig2 partial signature from hex. +func DecodePartialSignature(sigHex string) (*musig2.PartialSignature, error) { + if sigHex == "" { + return nil, nil + } + + raw, err := hex.DecodeString(sigHex) + if err != nil { + return nil, err + } + + sig := &musig2.PartialSignature{} + if err := sig.Decode(bytes.NewReader(raw)); err != nil { + return nil, err + } + + return sig, nil +} + +// EncodeSchnorrSignature encodes a schnorr signature to hex. +func EncodeSchnorrSignature(sig *schnorr.Signature) string { + if sig == nil { + return "" + } + + return hex.EncodeToString(sig.Serialize()) +} + +// DecodeSchnorrSignature decodes a schnorr signature from hex. +func DecodeSchnorrSignature(sigHex string) (*schnorr.Signature, error) { + if sigHex == "" { + return nil, nil + } + + raw, err := hex.DecodeString(sigHex) + if err != nil { + return nil, err + } + + return schnorr.ParseSignature(raw) +} + +// EncodeMsgTx serializes a wire.MsgTx to hex. +func EncodeMsgTx(tx *wire.MsgTx) (string, error) { + if tx == nil { + return "", nil + } + + var buf bytes.Buffer + if err := tx.Serialize(&buf); err != nil { + return "", err + } + + return hex.EncodeToString(buf.Bytes()), nil +} + +// DecodeMsgTx deserializes a wire.MsgTx from hex. +func DecodeMsgTx(txHex string) (*wire.MsgTx, error) { + if txHex == "" { + return nil, nil + } + + raw, err := hex.DecodeString(txHex) + if err != nil { + return nil, err + } + + tx := &wire.MsgTx{} + if err := tx.Deserialize(bytes.NewReader(raw)); err != nil { + return nil, err + } + + return tx, nil +} + +// EncodePSBT serializes a psbt.Packet to hex. +func EncodePSBT(packet *psbt.Packet) (string, error) { + if packet == nil { + return "", nil + } + + var buf bytes.Buffer + if err := packet.Serialize(&buf); err != nil { + return "", err + } + + return hex.EncodeToString(buf.Bytes()), nil +} + +// DecodePSBT deserializes a psbt.Packet from hex. +func DecodePSBT(psbtHex string) (*psbt.Packet, error) { + if psbtHex == "" { + return nil, nil + } + + raw, err := hex.DecodeString(psbtHex) + if err != nil { + return nil, err + } + + return psbt.NewFromRawBytes(bytes.NewReader(raw), false) +} + +// EncodeTree converts a tree.Tree into its payload representation. +func EncodeTree(t *tree.Tree) (*TreePayload, error) { + if t == nil { + return nil, nil + } + + root, err := encodeNode(t.Root) + if err != nil { + return nil, err + } + + var batchOutput *TxOutPayload + if t.BatchOutput != nil { + encoded := EncodeTxOut(t.BatchOutput) + batchOutput = &encoded + } + + batchOutpoint := EncodeOutPoint(t.BatchOutpoint) + + return &TreePayload{ + Root: root, + BatchOutpoint: &OutPointPayload{ + TxId: batchOutpoint.TxId, + Vout: batchOutpoint.Vout, + }, + BatchOutput: batchOutput, + SweepTapscriptRoot: hex.EncodeToString( + t.SweepTapscriptRoot, + ), + }, nil +} + +// DecodeTree converts a tree payload into a tree.Tree. +func DecodeTree(payload *TreePayload) (*tree.Tree, error) { + if payload == nil { + return nil, nil + } + + root, err := decodeNode(payload.Root) + if err != nil { + return nil, err + } + + if payload.BatchOutpoint == nil { + return nil, fmt.Errorf("batch outpoint is required") + } + + batchOutpoint, err := DecodeOutPoint(payload.BatchOutpoint) + if err != nil { + return nil, err + } + + var batchOutput *wire.TxOut + if payload.BatchOutput != nil { + batchOutput, err = DecodeTxOut(payload.BatchOutput) + if err != nil { + return nil, err + } + } + + sweepRoot, err := hex.DecodeString(payload.SweepTapscriptRoot) + if err != nil { + return nil, err + } + + return &tree.Tree{ + Root: root, + BatchOutpoint: batchOutpoint, + BatchOutput: batchOutput, + SweepTapscriptRoot: sweepRoot, + }, nil +} + +func encodeNode(n *tree.Node) (*NodePayload, error) { + if n == nil { + return nil, nil + } + + outs := make([]*TxOutPayload, 0, len(n.Outputs)) + for _, out := range n.Outputs { + encoded := EncodeTxOut(out) + outs = append(outs, &encoded) + } + + cosigners := make([]string, 0, len(n.CoSigners)) + for _, signer := range n.CoSigners { + cosigners = append(cosigners, EncodePubKey(signer)) + } + + children := make([]*NodeChildPayload, 0, len(n.Children)) + if len(n.Children) > 0 { + indices := make([]int, 0, len(n.Children)) + for idx := range n.Children { + indices = append(indices, int(idx)) + } + sort.Ints(indices) + + for _, idx := range indices { + child := n.Children[uint32(idx)] + encoded, err := encodeNode(child) + if err != nil { + return nil, err + } + + children = append(children, &NodeChildPayload{ + Index: uint32(idx), + Node: encoded, + }) + } + } + + input := EncodeOutPoint(n.Input) + + return &NodePayload{ + Input: &input, + Outputs: outs, + CoSigners: cosigners, + Children: children, + Amount: int64(n.Amount), + SignatureHex: EncodeSchnorrSignature(n.Signature), + FinalKeyHex: EncodePubKey(n.FinalKey), + }, nil +} + +func decodeNode(p *NodePayload) (*tree.Node, error) { + if p == nil { + return nil, nil + } + + if p.Input == nil { + return nil, fmt.Errorf("node input is required") + } + + input, err := DecodeOutPoint(p.Input) + if err != nil { + return nil, err + } + + outputs := make([]*wire.TxOut, 0, len(p.Outputs)) + for _, out := range p.Outputs { + if out == nil { + continue + } + + decoded, err := DecodeTxOut(out) + if err != nil { + return nil, err + } + + outputs = append(outputs, decoded) + } + + cosigners := make([]*btcec.PublicKey, 0, len(p.CoSigners)) + for _, signerHex := range p.CoSigners { + signer, err := DecodePubKey(signerHex) + if err != nil { + return nil, err + } + + cosigners = append(cosigners, signer) + } + + children := make(map[uint32]*tree.Node, len(p.Children)) + for _, child := range p.Children { + decoded, err := decodeNode(child.Node) + if err != nil { + return nil, err + } + + children[child.Index] = decoded + } + + sig, err := DecodeSchnorrSignature(p.SignatureHex) + if err != nil { + return nil, err + } + + finalKey, err := DecodePubKey(p.FinalKeyHex) + if err != nil { + return nil, err + } + + return &tree.Node{ + Input: input, + Outputs: outputs, + CoSigners: cosigners, + Children: children, + Amount: btcutil.Amount(p.Amount), + Signature: sig, + FinalKey: finalKey, + }, nil +} + +// SortSignerNonceBundles sorts signer bundles and per-signer nonce entries. +func SortSignerNonceBundles(entries []*SignerNonceBundle) { + sort.Slice(entries, func(i, j int) bool { + return entries[i].SignerKeyHex < entries[j].SignerKeyHex + }) + + for i := range entries { + sort.Slice(entries[i].Nonces, func(a, b int) bool { + return entries[i].Nonces[a].TxIdHex < + entries[i].Nonces[b].TxIdHex + }) + } +} + +// SortSignerSigBundles sorts signer bundles and per-signer signature entries. +func SortSignerSigBundles(entries []*SignerSigBundle) { + sort.Slice(entries, func(i, j int) bool { + return entries[i].SignerKeyHex < entries[j].SignerKeyHex + }) + + for i := range entries { + sort.Slice(entries[i].Signatures, func(a, b int) bool { + return entries[i].Signatures[a].TxIdHex < + entries[i].Signatures[b].TxIdHex + }) + } +} + +// SortTxNonceEntries sorts tx nonce entries by txid string. +func SortTxNonceEntries(entries []*TxNonceEntry) { + sort.Slice(entries, func(i, j int) bool { + return entries[i].TxIdHex < entries[j].TxIdHex + }) +} + +// SortTxSigEntries sorts tx signature entries by txid string. +func SortTxSigEntries(entries []*TxSigEntry) { + sort.Slice(entries, func(i, j int) bool { + return entries[i].TxIdHex < entries[j].TxIdHex + }) +} + +// ParseIndexKey parses a map key encoded with strconv.Itoa. +func ParseIndexKey(k string) (int, error) { + return strconv.Atoi(k) +} diff --git a/roundwire/roundwire.pb.go b/roundwire/roundwire.pb.go new file mode 100644 index 000000000..55deb9e42 --- /dev/null +++ b/roundwire/roundwire.pb.go @@ -0,0 +1,2111 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.6 +// protoc v3.21.12 +// source: roundwire.proto + +package roundwire + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type OutPointPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + TxId string `protobuf:"bytes,1,opt,name=tx_id,json=txId,proto3" json:"tx_id,omitempty"` + Vout uint32 `protobuf:"varint,2,opt,name=vout,proto3" json:"vout,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *OutPointPayload) Reset() { + *x = OutPointPayload{} + mi := &file_roundwire_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OutPointPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OutPointPayload) ProtoMessage() {} + +func (x *OutPointPayload) ProtoReflect() protoreflect.Message { + mi := &file_roundwire_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OutPointPayload.ProtoReflect.Descriptor instead. +func (*OutPointPayload) Descriptor() ([]byte, []int) { + return file_roundwire_proto_rawDescGZIP(), []int{0} +} + +func (x *OutPointPayload) GetTxId() string { + if x != nil { + return x.TxId + } + return "" +} + +func (x *OutPointPayload) GetVout() uint32 { + if x != nil { + return x.Vout + } + return 0 +} + +type TxOutPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + Value int64 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"` + PkScript string `protobuf:"bytes,2,opt,name=pk_script,json=pkScript,proto3" json:"pk_script,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TxOutPayload) Reset() { + *x = TxOutPayload{} + mi := &file_roundwire_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TxOutPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TxOutPayload) ProtoMessage() {} + +func (x *TxOutPayload) ProtoReflect() protoreflect.Message { + mi := &file_roundwire_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TxOutPayload.ProtoReflect.Descriptor instead. +func (*TxOutPayload) Descriptor() ([]byte, []int) { + return file_roundwire_proto_rawDescGZIP(), []int{1} +} + +func (x *TxOutPayload) GetValue() int64 { + if x != nil { + return x.Value + } + return 0 +} + +func (x *TxOutPayload) GetPkScript() string { + if x != nil { + return x.PkScript + } + return "" +} + +type KeyDescriptorPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + KeyFamily int32 `protobuf:"varint,1,opt,name=key_family,json=keyFamily,proto3" json:"key_family,omitempty"` + KeyIndex uint32 `protobuf:"varint,2,opt,name=key_index,json=keyIndex,proto3" json:"key_index,omitempty"` + PubKeyHex string `protobuf:"bytes,3,opt,name=pub_key_hex,json=pubKeyHex,proto3" json:"pub_key_hex,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *KeyDescriptorPayload) Reset() { + *x = KeyDescriptorPayload{} + mi := &file_roundwire_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *KeyDescriptorPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*KeyDescriptorPayload) ProtoMessage() {} + +func (x *KeyDescriptorPayload) ProtoReflect() protoreflect.Message { + mi := &file_roundwire_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use KeyDescriptorPayload.ProtoReflect.Descriptor instead. +func (*KeyDescriptorPayload) Descriptor() ([]byte, []int) { + return file_roundwire_proto_rawDescGZIP(), []int{2} +} + +func (x *KeyDescriptorPayload) GetKeyFamily() int32 { + if x != nil { + return x.KeyFamily + } + return 0 +} + +func (x *KeyDescriptorPayload) GetKeyIndex() uint32 { + if x != nil { + return x.KeyIndex + } + return 0 +} + +func (x *KeyDescriptorPayload) GetPubKeyHex() string { + if x != nil { + return x.PubKeyHex + } + return "" +} + +type JoinRoundAuthPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + MessageHex string `protobuf:"bytes,1,opt,name=message_hex,json=messageHex,proto3" json:"message_hex,omitempty"` + ValidFrom uint32 `protobuf:"varint,2,opt,name=valid_from,json=validFrom,proto3" json:"valid_from,omitempty"` + ValidUntil uint32 `protobuf:"varint,3,opt,name=valid_until,json=validUntil,proto3" json:"valid_until,omitempty"` + SignatureHex string `protobuf:"bytes,4,opt,name=signature_hex,json=signatureHex,proto3" json:"signature_hex,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *JoinRoundAuthPayload) Reset() { + *x = JoinRoundAuthPayload{} + mi := &file_roundwire_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *JoinRoundAuthPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*JoinRoundAuthPayload) ProtoMessage() {} + +func (x *JoinRoundAuthPayload) ProtoReflect() protoreflect.Message { + mi := &file_roundwire_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use JoinRoundAuthPayload.ProtoReflect.Descriptor instead. +func (*JoinRoundAuthPayload) Descriptor() ([]byte, []int) { + return file_roundwire_proto_rawDescGZIP(), []int{3} +} + +func (x *JoinRoundAuthPayload) GetMessageHex() string { + if x != nil { + return x.MessageHex + } + return "" +} + +func (x *JoinRoundAuthPayload) GetValidFrom() uint32 { + if x != nil { + return x.ValidFrom + } + return 0 +} + +func (x *JoinRoundAuthPayload) GetValidUntil() uint32 { + if x != nil { + return x.ValidUntil + } + return 0 +} + +func (x *JoinRoundAuthPayload) GetSignatureHex() string { + if x != nil { + return x.SignatureHex + } + return "" +} + +type BoardingRequestPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + Outpoint *OutPointPayload `protobuf:"bytes,1,opt,name=outpoint,proto3" json:"outpoint,omitempty"` + ClientKey string `protobuf:"bytes,2,opt,name=client_key,json=clientKey,proto3" json:"client_key,omitempty"` + OperatorKey string `protobuf:"bytes,3,opt,name=operator_key,json=operatorKey,proto3" json:"operator_key,omitempty"` + ExitDelay uint32 `protobuf:"varint,4,opt,name=exit_delay,json=exitDelay,proto3" json:"exit_delay,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *BoardingRequestPayload) Reset() { + *x = BoardingRequestPayload{} + mi := &file_roundwire_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BoardingRequestPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BoardingRequestPayload) ProtoMessage() {} + +func (x *BoardingRequestPayload) ProtoReflect() protoreflect.Message { + mi := &file_roundwire_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BoardingRequestPayload.ProtoReflect.Descriptor instead. +func (*BoardingRequestPayload) Descriptor() ([]byte, []int) { + return file_roundwire_proto_rawDescGZIP(), []int{4} +} + +func (x *BoardingRequestPayload) GetOutpoint() *OutPointPayload { + if x != nil { + return x.Outpoint + } + return nil +} + +func (x *BoardingRequestPayload) GetClientKey() string { + if x != nil { + return x.ClientKey + } + return "" +} + +func (x *BoardingRequestPayload) GetOperatorKey() string { + if x != nil { + return x.OperatorKey + } + return "" +} + +func (x *BoardingRequestPayload) GetExitDelay() uint32 { + if x != nil { + return x.ExitDelay + } + return 0 +} + +type VTXORequestPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + Amount int64 `protobuf:"varint,1,opt,name=amount,proto3" json:"amount,omitempty"` + PkScriptHex string `protobuf:"bytes,2,opt,name=pk_script_hex,json=pkScriptHex,proto3" json:"pk_script_hex,omitempty"` + Expiry uint32 `protobuf:"varint,3,opt,name=expiry,proto3" json:"expiry,omitempty"` + ClientKey string `protobuf:"bytes,4,opt,name=client_key,json=clientKey,proto3" json:"client_key,omitempty"` + OperatorKey string `protobuf:"bytes,5,opt,name=operator_key,json=operatorKey,proto3" json:"operator_key,omitempty"` + SigningKey *KeyDescriptorPayload `protobuf:"bytes,6,opt,name=signing_key,json=signingKey,proto3" json:"signing_key,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *VTXORequestPayload) Reset() { + *x = VTXORequestPayload{} + mi := &file_roundwire_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VTXORequestPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VTXORequestPayload) ProtoMessage() {} + +func (x *VTXORequestPayload) ProtoReflect() protoreflect.Message { + mi := &file_roundwire_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use VTXORequestPayload.ProtoReflect.Descriptor instead. +func (*VTXORequestPayload) Descriptor() ([]byte, []int) { + return file_roundwire_proto_rawDescGZIP(), []int{5} +} + +func (x *VTXORequestPayload) GetAmount() int64 { + if x != nil { + return x.Amount + } + return 0 +} + +func (x *VTXORequestPayload) GetPkScriptHex() string { + if x != nil { + return x.PkScriptHex + } + return "" +} + +func (x *VTXORequestPayload) GetExpiry() uint32 { + if x != nil { + return x.Expiry + } + return 0 +} + +func (x *VTXORequestPayload) GetClientKey() string { + if x != nil { + return x.ClientKey + } + return "" +} + +func (x *VTXORequestPayload) GetOperatorKey() string { + if x != nil { + return x.OperatorKey + } + return "" +} + +func (x *VTXORequestPayload) GetSigningKey() *KeyDescriptorPayload { + if x != nil { + return x.SigningKey + } + return nil +} + +type ForfeitRequestPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + VtxoOutpoint *OutPointPayload `protobuf:"bytes,1,opt,name=vtxo_outpoint,json=vtxoOutpoint,proto3" json:"vtxo_outpoint,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ForfeitRequestPayload) Reset() { + *x = ForfeitRequestPayload{} + mi := &file_roundwire_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ForfeitRequestPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ForfeitRequestPayload) ProtoMessage() {} + +func (x *ForfeitRequestPayload) ProtoReflect() protoreflect.Message { + mi := &file_roundwire_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ForfeitRequestPayload.ProtoReflect.Descriptor instead. +func (*ForfeitRequestPayload) Descriptor() ([]byte, []int) { + return file_roundwire_proto_rawDescGZIP(), []int{6} +} + +func (x *ForfeitRequestPayload) GetVtxoOutpoint() *OutPointPayload { + if x != nil { + return x.VtxoOutpoint + } + return nil +} + +type LeaveRequestPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + Output *TxOutPayload `protobuf:"bytes,1,opt,name=output,proto3" json:"output,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LeaveRequestPayload) Reset() { + *x = LeaveRequestPayload{} + mi := &file_roundwire_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LeaveRequestPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LeaveRequestPayload) ProtoMessage() {} + +func (x *LeaveRequestPayload) ProtoReflect() protoreflect.Message { + mi := &file_roundwire_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LeaveRequestPayload.ProtoReflect.Descriptor instead. +func (*LeaveRequestPayload) Descriptor() ([]byte, []int) { + return file_roundwire_proto_rawDescGZIP(), []int{7} +} + +func (x *LeaveRequestPayload) GetOutput() *TxOutPayload { + if x != nil { + return x.Output + } + return nil +} + +type JoinRoundRequestPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + RoundId string `protobuf:"bytes,1,opt,name=round_id,json=roundId,proto3" json:"round_id,omitempty"` + Identifier string `protobuf:"bytes,2,opt,name=identifier,proto3" json:"identifier,omitempty"` + BoardingRequests []*BoardingRequestPayload `protobuf:"bytes,3,rep,name=boarding_requests,json=boardingRequests,proto3" json:"boarding_requests,omitempty"` + VtxoRequests []*VTXORequestPayload `protobuf:"bytes,4,rep,name=vtxo_requests,json=vtxoRequests,proto3" json:"vtxo_requests,omitempty"` + ForfeitRequests []*ForfeitRequestPayload `protobuf:"bytes,5,rep,name=forfeit_requests,json=forfeitRequests,proto3" json:"forfeit_requests,omitempty"` + LeaveRequests []*LeaveRequestPayload `protobuf:"bytes,6,rep,name=leave_requests,json=leaveRequests,proto3" json:"leave_requests,omitempty"` + Auth *JoinRoundAuthPayload `protobuf:"bytes,7,opt,name=auth,proto3" json:"auth,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *JoinRoundRequestPayload) Reset() { + *x = JoinRoundRequestPayload{} + mi := &file_roundwire_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *JoinRoundRequestPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*JoinRoundRequestPayload) ProtoMessage() {} + +func (x *JoinRoundRequestPayload) ProtoReflect() protoreflect.Message { + mi := &file_roundwire_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use JoinRoundRequestPayload.ProtoReflect.Descriptor instead. +func (*JoinRoundRequestPayload) Descriptor() ([]byte, []int) { + return file_roundwire_proto_rawDescGZIP(), []int{8} +} + +func (x *JoinRoundRequestPayload) GetRoundId() string { + if x != nil { + return x.RoundId + } + return "" +} + +func (x *JoinRoundRequestPayload) GetIdentifier() string { + if x != nil { + return x.Identifier + } + return "" +} + +func (x *JoinRoundRequestPayload) GetBoardingRequests() []*BoardingRequestPayload { + if x != nil { + return x.BoardingRequests + } + return nil +} + +func (x *JoinRoundRequestPayload) GetVtxoRequests() []*VTXORequestPayload { + if x != nil { + return x.VtxoRequests + } + return nil +} + +func (x *JoinRoundRequestPayload) GetForfeitRequests() []*ForfeitRequestPayload { + if x != nil { + return x.ForfeitRequests + } + return nil +} + +func (x *JoinRoundRequestPayload) GetLeaveRequests() []*LeaveRequestPayload { + if x != nil { + return x.LeaveRequests + } + return nil +} + +func (x *JoinRoundRequestPayload) GetAuth() *JoinRoundAuthPayload { + if x != nil { + return x.Auth + } + return nil +} + +type TxNonceEntry struct { + state protoimpl.MessageState `protogen:"open.v1"` + TxIdHex string `protobuf:"bytes,1,opt,name=tx_id_hex,json=txIdHex,proto3" json:"tx_id_hex,omitempty"` + NonceHex string `protobuf:"bytes,2,opt,name=nonce_hex,json=nonceHex,proto3" json:"nonce_hex,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TxNonceEntry) Reset() { + *x = TxNonceEntry{} + mi := &file_roundwire_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TxNonceEntry) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TxNonceEntry) ProtoMessage() {} + +func (x *TxNonceEntry) ProtoReflect() protoreflect.Message { + mi := &file_roundwire_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TxNonceEntry.ProtoReflect.Descriptor instead. +func (*TxNonceEntry) Descriptor() ([]byte, []int) { + return file_roundwire_proto_rawDescGZIP(), []int{9} +} + +func (x *TxNonceEntry) GetTxIdHex() string { + if x != nil { + return x.TxIdHex + } + return "" +} + +func (x *TxNonceEntry) GetNonceHex() string { + if x != nil { + return x.NonceHex + } + return "" +} + +type SignerNonceBundle struct { + state protoimpl.MessageState `protogen:"open.v1"` + SignerKeyHex string `protobuf:"bytes,1,opt,name=signer_key_hex,json=signerKeyHex,proto3" json:"signer_key_hex,omitempty"` + Nonces []*TxNonceEntry `protobuf:"bytes,2,rep,name=nonces,proto3" json:"nonces,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SignerNonceBundle) Reset() { + *x = SignerNonceBundle{} + mi := &file_roundwire_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SignerNonceBundle) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SignerNonceBundle) ProtoMessage() {} + +func (x *SignerNonceBundle) ProtoReflect() protoreflect.Message { + mi := &file_roundwire_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SignerNonceBundle.ProtoReflect.Descriptor instead. +func (*SignerNonceBundle) Descriptor() ([]byte, []int) { + return file_roundwire_proto_rawDescGZIP(), []int{10} +} + +func (x *SignerNonceBundle) GetSignerKeyHex() string { + if x != nil { + return x.SignerKeyHex + } + return "" +} + +func (x *SignerNonceBundle) GetNonces() []*TxNonceEntry { + if x != nil { + return x.Nonces + } + return nil +} + +type SubmitNoncesPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + RoundId string `protobuf:"bytes,1,opt,name=round_id,json=roundId,proto3" json:"round_id,omitempty"` + Entries []*SignerNonceBundle `protobuf:"bytes,2,rep,name=entries,proto3" json:"entries,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubmitNoncesPayload) Reset() { + *x = SubmitNoncesPayload{} + mi := &file_roundwire_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubmitNoncesPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubmitNoncesPayload) ProtoMessage() {} + +func (x *SubmitNoncesPayload) ProtoReflect() protoreflect.Message { + mi := &file_roundwire_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubmitNoncesPayload.ProtoReflect.Descriptor instead. +func (*SubmitNoncesPayload) Descriptor() ([]byte, []int) { + return file_roundwire_proto_rawDescGZIP(), []int{11} +} + +func (x *SubmitNoncesPayload) GetRoundId() string { + if x != nil { + return x.RoundId + } + return "" +} + +func (x *SubmitNoncesPayload) GetEntries() []*SignerNonceBundle { + if x != nil { + return x.Entries + } + return nil +} + +type TxSigEntry struct { + state protoimpl.MessageState `protogen:"open.v1"` + TxIdHex string `protobuf:"bytes,1,opt,name=tx_id_hex,json=txIdHex,proto3" json:"tx_id_hex,omitempty"` + SignatureHex string `protobuf:"bytes,2,opt,name=signature_hex,json=signatureHex,proto3" json:"signature_hex,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TxSigEntry) Reset() { + *x = TxSigEntry{} + mi := &file_roundwire_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TxSigEntry) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TxSigEntry) ProtoMessage() {} + +func (x *TxSigEntry) ProtoReflect() protoreflect.Message { + mi := &file_roundwire_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TxSigEntry.ProtoReflect.Descriptor instead. +func (*TxSigEntry) Descriptor() ([]byte, []int) { + return file_roundwire_proto_rawDescGZIP(), []int{12} +} + +func (x *TxSigEntry) GetTxIdHex() string { + if x != nil { + return x.TxIdHex + } + return "" +} + +func (x *TxSigEntry) GetSignatureHex() string { + if x != nil { + return x.SignatureHex + } + return "" +} + +type SignerSigBundle struct { + state protoimpl.MessageState `protogen:"open.v1"` + SignerKeyHex string `protobuf:"bytes,1,opt,name=signer_key_hex,json=signerKeyHex,proto3" json:"signer_key_hex,omitempty"` + Signatures []*TxSigEntry `protobuf:"bytes,2,rep,name=signatures,proto3" json:"signatures,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SignerSigBundle) Reset() { + *x = SignerSigBundle{} + mi := &file_roundwire_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SignerSigBundle) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SignerSigBundle) ProtoMessage() {} + +func (x *SignerSigBundle) ProtoReflect() protoreflect.Message { + mi := &file_roundwire_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SignerSigBundle.ProtoReflect.Descriptor instead. +func (*SignerSigBundle) Descriptor() ([]byte, []int) { + return file_roundwire_proto_rawDescGZIP(), []int{13} +} + +func (x *SignerSigBundle) GetSignerKeyHex() string { + if x != nil { + return x.SignerKeyHex + } + return "" +} + +func (x *SignerSigBundle) GetSignatures() []*TxSigEntry { + if x != nil { + return x.Signatures + } + return nil +} + +type SubmitPartialSigsPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + RoundId string `protobuf:"bytes,1,opt,name=round_id,json=roundId,proto3" json:"round_id,omitempty"` + Entries []*SignerSigBundle `protobuf:"bytes,2,rep,name=entries,proto3" json:"entries,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubmitPartialSigsPayload) Reset() { + *x = SubmitPartialSigsPayload{} + mi := &file_roundwire_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubmitPartialSigsPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubmitPartialSigsPayload) ProtoMessage() {} + +func (x *SubmitPartialSigsPayload) ProtoReflect() protoreflect.Message { + mi := &file_roundwire_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubmitPartialSigsPayload.ProtoReflect.Descriptor instead. +func (*SubmitPartialSigsPayload) Descriptor() ([]byte, []int) { + return file_roundwire_proto_rawDescGZIP(), []int{14} +} + +func (x *SubmitPartialSigsPayload) GetRoundId() string { + if x != nil { + return x.RoundId + } + return "" +} + +func (x *SubmitPartialSigsPayload) GetEntries() []*SignerSigBundle { + if x != nil { + return x.Entries + } + return nil +} + +type BoardingInputSigPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + InputIndex int32 `protobuf:"varint,1,opt,name=input_index,json=inputIndex,proto3" json:"input_index,omitempty"` + Outpoint *OutPointPayload `protobuf:"bytes,2,opt,name=outpoint,proto3" json:"outpoint,omitempty"` + SignatureHex string `protobuf:"bytes,3,opt,name=signature_hex,json=signatureHex,proto3" json:"signature_hex,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *BoardingInputSigPayload) Reset() { + *x = BoardingInputSigPayload{} + mi := &file_roundwire_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BoardingInputSigPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BoardingInputSigPayload) ProtoMessage() {} + +func (x *BoardingInputSigPayload) ProtoReflect() protoreflect.Message { + mi := &file_roundwire_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BoardingInputSigPayload.ProtoReflect.Descriptor instead. +func (*BoardingInputSigPayload) Descriptor() ([]byte, []int) { + return file_roundwire_proto_rawDescGZIP(), []int{15} +} + +func (x *BoardingInputSigPayload) GetInputIndex() int32 { + if x != nil { + return x.InputIndex + } + return 0 +} + +func (x *BoardingInputSigPayload) GetOutpoint() *OutPointPayload { + if x != nil { + return x.Outpoint + } + return nil +} + +func (x *BoardingInputSigPayload) GetSignatureHex() string { + if x != nil { + return x.SignatureHex + } + return "" +} + +type SubmitForfeitSigsPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + RoundId string `protobuf:"bytes,1,opt,name=round_id,json=roundId,proto3" json:"round_id,omitempty"` + Signatures []*BoardingInputSigPayload `protobuf:"bytes,2,rep,name=signatures,proto3" json:"signatures,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubmitForfeitSigsPayload) Reset() { + *x = SubmitForfeitSigsPayload{} + mi := &file_roundwire_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubmitForfeitSigsPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubmitForfeitSigsPayload) ProtoMessage() {} + +func (x *SubmitForfeitSigsPayload) ProtoReflect() protoreflect.Message { + mi := &file_roundwire_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubmitForfeitSigsPayload.ProtoReflect.Descriptor instead. +func (*SubmitForfeitSigsPayload) Descriptor() ([]byte, []int) { + return file_roundwire_proto_rawDescGZIP(), []int{16} +} + +func (x *SubmitForfeitSigsPayload) GetRoundId() string { + if x != nil { + return x.RoundId + } + return "" +} + +func (x *SubmitForfeitSigsPayload) GetSignatures() []*BoardingInputSigPayload { + if x != nil { + return x.Signatures + } + return nil +} + +type VTXOForfeitSigPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + VtxoOutpoint *OutPointPayload `protobuf:"bytes,1,opt,name=vtxo_outpoint,json=vtxoOutpoint,proto3" json:"vtxo_outpoint,omitempty"` + SignatureHex string `protobuf:"bytes,2,opt,name=signature_hex,json=signatureHex,proto3" json:"signature_hex,omitempty"` + UnsignedTxHex string `protobuf:"bytes,3,opt,name=unsigned_tx_hex,json=unsignedTxHex,proto3" json:"unsigned_tx_hex,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *VTXOForfeitSigPayload) Reset() { + *x = VTXOForfeitSigPayload{} + mi := &file_roundwire_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VTXOForfeitSigPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VTXOForfeitSigPayload) ProtoMessage() {} + +func (x *VTXOForfeitSigPayload) ProtoReflect() protoreflect.Message { + mi := &file_roundwire_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use VTXOForfeitSigPayload.ProtoReflect.Descriptor instead. +func (*VTXOForfeitSigPayload) Descriptor() ([]byte, []int) { + return file_roundwire_proto_rawDescGZIP(), []int{17} +} + +func (x *VTXOForfeitSigPayload) GetVtxoOutpoint() *OutPointPayload { + if x != nil { + return x.VtxoOutpoint + } + return nil +} + +func (x *VTXOForfeitSigPayload) GetSignatureHex() string { + if x != nil { + return x.SignatureHex + } + return "" +} + +func (x *VTXOForfeitSigPayload) GetUnsignedTxHex() string { + if x != nil { + return x.UnsignedTxHex + } + return "" +} + +type SubmitVTXOForfeitSigsPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + RoundId string `protobuf:"bytes,1,opt,name=round_id,json=roundId,proto3" json:"round_id,omitempty"` + Entries []*VTXOForfeitSigPayload `protobuf:"bytes,2,rep,name=entries,proto3" json:"entries,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubmitVTXOForfeitSigsPayload) Reset() { + *x = SubmitVTXOForfeitSigsPayload{} + mi := &file_roundwire_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubmitVTXOForfeitSigsPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubmitVTXOForfeitSigsPayload) ProtoMessage() {} + +func (x *SubmitVTXOForfeitSigsPayload) ProtoReflect() protoreflect.Message { + mi := &file_roundwire_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubmitVTXOForfeitSigsPayload.ProtoReflect.Descriptor instead. +func (*SubmitVTXOForfeitSigsPayload) Descriptor() ([]byte, []int) { + return file_roundwire_proto_rawDescGZIP(), []int{18} +} + +func (x *SubmitVTXOForfeitSigsPayload) GetRoundId() string { + if x != nil { + return x.RoundId + } + return "" +} + +func (x *SubmitVTXOForfeitSigsPayload) GetEntries() []*VTXOForfeitSigPayload { + if x != nil { + return x.Entries + } + return nil +} + +type ClientErrorRespPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + Error string `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClientErrorRespPayload) Reset() { + *x = ClientErrorRespPayload{} + mi := &file_roundwire_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClientErrorRespPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClientErrorRespPayload) ProtoMessage() {} + +func (x *ClientErrorRespPayload) ProtoReflect() protoreflect.Message { + mi := &file_roundwire_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClientErrorRespPayload.ProtoReflect.Descriptor instead. +func (*ClientErrorRespPayload) Descriptor() ([]byte, []int) { + return file_roundwire_proto_rawDescGZIP(), []int{19} +} + +func (x *ClientErrorRespPayload) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type ClientSuccessRespPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + RoundId string `protobuf:"bytes,1,opt,name=round_id,json=roundId,proto3" json:"round_id,omitempty"` + AcceptedBoardingOutpoints []*OutPointPayload `protobuf:"bytes,2,rep,name=accepted_boarding_outpoints,json=acceptedBoardingOutpoints,proto3" json:"accepted_boarding_outpoints,omitempty"` + AcceptedVtxoOutpoints []*OutPointPayload `protobuf:"bytes,3,rep,name=accepted_vtxo_outpoints,json=acceptedVtxoOutpoints,proto3" json:"accepted_vtxo_outpoints,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClientSuccessRespPayload) Reset() { + *x = ClientSuccessRespPayload{} + mi := &file_roundwire_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClientSuccessRespPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClientSuccessRespPayload) ProtoMessage() {} + +func (x *ClientSuccessRespPayload) ProtoReflect() protoreflect.Message { + mi := &file_roundwire_proto_msgTypes[20] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClientSuccessRespPayload.ProtoReflect.Descriptor instead. +func (*ClientSuccessRespPayload) Descriptor() ([]byte, []int) { + return file_roundwire_proto_rawDescGZIP(), []int{20} +} + +func (x *ClientSuccessRespPayload) GetRoundId() string { + if x != nil { + return x.RoundId + } + return "" +} + +func (x *ClientSuccessRespPayload) GetAcceptedBoardingOutpoints() []*OutPointPayload { + if x != nil { + return x.AcceptedBoardingOutpoints + } + return nil +} + +func (x *ClientSuccessRespPayload) GetAcceptedVtxoOutpoints() []*OutPointPayload { + if x != nil { + return x.AcceptedVtxoOutpoints + } + return nil +} + +type ClientAwaitingInputSigsRespPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + RoundId string `protobuf:"bytes,1,opt,name=round_id,json=roundId,proto3" json:"round_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClientAwaitingInputSigsRespPayload) Reset() { + *x = ClientAwaitingInputSigsRespPayload{} + mi := &file_roundwire_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClientAwaitingInputSigsRespPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClientAwaitingInputSigsRespPayload) ProtoMessage() {} + +func (x *ClientAwaitingInputSigsRespPayload) ProtoReflect() protoreflect.Message { + mi := &file_roundwire_proto_msgTypes[21] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClientAwaitingInputSigsRespPayload.ProtoReflect.Descriptor instead. +func (*ClientAwaitingInputSigsRespPayload) Descriptor() ([]byte, []int) { + return file_roundwire_proto_rawDescGZIP(), []int{21} +} + +func (x *ClientAwaitingInputSigsRespPayload) GetRoundId() string { + if x != nil { + return x.RoundId + } + return "" +} + +type ClientVTXOAggNoncesPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + RoundId string `protobuf:"bytes,1,opt,name=round_id,json=roundId,proto3" json:"round_id,omitempty"` + Nonces []*TxNonceEntry `protobuf:"bytes,2,rep,name=nonces,proto3" json:"nonces,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClientVTXOAggNoncesPayload) Reset() { + *x = ClientVTXOAggNoncesPayload{} + mi := &file_roundwire_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClientVTXOAggNoncesPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClientVTXOAggNoncesPayload) ProtoMessage() {} + +func (x *ClientVTXOAggNoncesPayload) ProtoReflect() protoreflect.Message { + mi := &file_roundwire_proto_msgTypes[22] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClientVTXOAggNoncesPayload.ProtoReflect.Descriptor instead. +func (*ClientVTXOAggNoncesPayload) Descriptor() ([]byte, []int) { + return file_roundwire_proto_rawDescGZIP(), []int{22} +} + +func (x *ClientVTXOAggNoncesPayload) GetRoundId() string { + if x != nil { + return x.RoundId + } + return "" +} + +func (x *ClientVTXOAggNoncesPayload) GetNonces() []*TxNonceEntry { + if x != nil { + return x.Nonces + } + return nil +} + +type ClientVTXOAggSigsPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + RoundId string `protobuf:"bytes,1,opt,name=round_id,json=roundId,proto3" json:"round_id,omitempty"` + Signatures []*TxSigEntry `protobuf:"bytes,2,rep,name=signatures,proto3" json:"signatures,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClientVTXOAggSigsPayload) Reset() { + *x = ClientVTXOAggSigsPayload{} + mi := &file_roundwire_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClientVTXOAggSigsPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClientVTXOAggSigsPayload) ProtoMessage() {} + +func (x *ClientVTXOAggSigsPayload) ProtoReflect() protoreflect.Message { + mi := &file_roundwire_proto_msgTypes[23] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClientVTXOAggSigsPayload.ProtoReflect.Descriptor instead. +func (*ClientVTXOAggSigsPayload) Descriptor() ([]byte, []int) { + return file_roundwire_proto_rawDescGZIP(), []int{23} +} + +func (x *ClientVTXOAggSigsPayload) GetRoundId() string { + if x != nil { + return x.RoundId + } + return "" +} + +func (x *ClientVTXOAggSigsPayload) GetSignatures() []*TxSigEntry { + if x != nil { + return x.Signatures + } + return nil +} + +type NodePayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + Input *OutPointPayload `protobuf:"bytes,1,opt,name=input,proto3" json:"input,omitempty"` + Outputs []*TxOutPayload `protobuf:"bytes,2,rep,name=outputs,proto3" json:"outputs,omitempty"` + CoSigners []string `protobuf:"bytes,3,rep,name=co_signers,json=coSigners,proto3" json:"co_signers,omitempty"` + Children []*NodeChildPayload `protobuf:"bytes,4,rep,name=children,proto3" json:"children,omitempty"` + Amount int64 `protobuf:"varint,5,opt,name=amount,proto3" json:"amount,omitempty"` + SignatureHex string `protobuf:"bytes,6,opt,name=signature_hex,json=signatureHex,proto3" json:"signature_hex,omitempty"` + FinalKeyHex string `protobuf:"bytes,7,opt,name=final_key_hex,json=finalKeyHex,proto3" json:"final_key_hex,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NodePayload) Reset() { + *x = NodePayload{} + mi := &file_roundwire_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NodePayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NodePayload) ProtoMessage() {} + +func (x *NodePayload) ProtoReflect() protoreflect.Message { + mi := &file_roundwire_proto_msgTypes[24] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NodePayload.ProtoReflect.Descriptor instead. +func (*NodePayload) Descriptor() ([]byte, []int) { + return file_roundwire_proto_rawDescGZIP(), []int{24} +} + +func (x *NodePayload) GetInput() *OutPointPayload { + if x != nil { + return x.Input + } + return nil +} + +func (x *NodePayload) GetOutputs() []*TxOutPayload { + if x != nil { + return x.Outputs + } + return nil +} + +func (x *NodePayload) GetCoSigners() []string { + if x != nil { + return x.CoSigners + } + return nil +} + +func (x *NodePayload) GetChildren() []*NodeChildPayload { + if x != nil { + return x.Children + } + return nil +} + +func (x *NodePayload) GetAmount() int64 { + if x != nil { + return x.Amount + } + return 0 +} + +func (x *NodePayload) GetSignatureHex() string { + if x != nil { + return x.SignatureHex + } + return "" +} + +func (x *NodePayload) GetFinalKeyHex() string { + if x != nil { + return x.FinalKeyHex + } + return "" +} + +type NodeChildPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + Index uint32 `protobuf:"varint,1,opt,name=index,proto3" json:"index,omitempty"` + Node *NodePayload `protobuf:"bytes,2,opt,name=node,proto3" json:"node,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NodeChildPayload) Reset() { + *x = NodeChildPayload{} + mi := &file_roundwire_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NodeChildPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NodeChildPayload) ProtoMessage() {} + +func (x *NodeChildPayload) ProtoReflect() protoreflect.Message { + mi := &file_roundwire_proto_msgTypes[25] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NodeChildPayload.ProtoReflect.Descriptor instead. +func (*NodeChildPayload) Descriptor() ([]byte, []int) { + return file_roundwire_proto_rawDescGZIP(), []int{25} +} + +func (x *NodeChildPayload) GetIndex() uint32 { + if x != nil { + return x.Index + } + return 0 +} + +func (x *NodeChildPayload) GetNode() *NodePayload { + if x != nil { + return x.Node + } + return nil +} + +type TreePayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + Root *NodePayload `protobuf:"bytes,1,opt,name=root,proto3" json:"root,omitempty"` + BatchOutpoint *OutPointPayload `protobuf:"bytes,2,opt,name=batch_outpoint,json=batchOutpoint,proto3" json:"batch_outpoint,omitempty"` + BatchOutput *TxOutPayload `protobuf:"bytes,3,opt,name=batch_output,json=batchOutput,proto3" json:"batch_output,omitempty"` + SweepTapscriptRoot string `protobuf:"bytes,4,opt,name=sweep_tapscript_root,json=sweepTapscriptRoot,proto3" json:"sweep_tapscript_root,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TreePayload) Reset() { + *x = TreePayload{} + mi := &file_roundwire_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TreePayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TreePayload) ProtoMessage() {} + +func (x *TreePayload) ProtoReflect() protoreflect.Message { + mi := &file_roundwire_proto_msgTypes[26] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TreePayload.ProtoReflect.Descriptor instead. +func (*TreePayload) Descriptor() ([]byte, []int) { + return file_roundwire_proto_rawDescGZIP(), []int{26} +} + +func (x *TreePayload) GetRoot() *NodePayload { + if x != nil { + return x.Root + } + return nil +} + +func (x *TreePayload) GetBatchOutpoint() *OutPointPayload { + if x != nil { + return x.BatchOutpoint + } + return nil +} + +func (x *TreePayload) GetBatchOutput() *TxOutPayload { + if x != nil { + return x.BatchOutput + } + return nil +} + +func (x *TreePayload) GetSweepTapscriptRoot() string { + if x != nil { + return x.SweepTapscriptRoot + } + return "" +} + +type TreePathPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + OutputIndex int32 `protobuf:"varint,1,opt,name=output_index,json=outputIndex,proto3" json:"output_index,omitempty"` + Tree *TreePayload `protobuf:"bytes,2,opt,name=tree,proto3" json:"tree,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TreePathPayload) Reset() { + *x = TreePathPayload{} + mi := &file_roundwire_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TreePathPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TreePathPayload) ProtoMessage() {} + +func (x *TreePathPayload) ProtoReflect() protoreflect.Message { + mi := &file_roundwire_proto_msgTypes[27] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TreePathPayload.ProtoReflect.Descriptor instead. +func (*TreePathPayload) Descriptor() ([]byte, []int) { + return file_roundwire_proto_rawDescGZIP(), []int{27} +} + +func (x *TreePathPayload) GetOutputIndex() int32 { + if x != nil { + return x.OutputIndex + } + return 0 +} + +func (x *TreePathPayload) GetTree() *TreePayload { + if x != nil { + return x.Tree + } + return nil +} + +type ConnectorLeafPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + VtxoOutpoint *OutPointPayload `protobuf:"bytes,1,opt,name=vtxo_outpoint,json=vtxoOutpoint,proto3" json:"vtxo_outpoint,omitempty"` + LeafOutpoint *OutPointPayload `protobuf:"bytes,2,opt,name=leaf_outpoint,json=leafOutpoint,proto3" json:"leaf_outpoint,omitempty"` + LeafOutput *TxOutPayload `protobuf:"bytes,3,opt,name=leaf_output,json=leafOutput,proto3" json:"leaf_output,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConnectorLeafPayload) Reset() { + *x = ConnectorLeafPayload{} + mi := &file_roundwire_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ConnectorLeafPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConnectorLeafPayload) ProtoMessage() {} + +func (x *ConnectorLeafPayload) ProtoReflect() protoreflect.Message { + mi := &file_roundwire_proto_msgTypes[28] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConnectorLeafPayload.ProtoReflect.Descriptor instead. +func (*ConnectorLeafPayload) Descriptor() ([]byte, []int) { + return file_roundwire_proto_rawDescGZIP(), []int{28} +} + +func (x *ConnectorLeafPayload) GetVtxoOutpoint() *OutPointPayload { + if x != nil { + return x.VtxoOutpoint + } + return nil +} + +func (x *ConnectorLeafPayload) GetLeafOutpoint() *OutPointPayload { + if x != nil { + return x.LeafOutpoint + } + return nil +} + +func (x *ConnectorLeafPayload) GetLeafOutput() *TxOutPayload { + if x != nil { + return x.LeafOutput + } + return nil +} + +type ClientBatchInfoPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + RoundId string `protobuf:"bytes,1,opt,name=round_id,json=roundId,proto3" json:"round_id,omitempty"` + BatchPsbtHex string `protobuf:"bytes,2,opt,name=batch_psbt_hex,json=batchPsbtHex,proto3" json:"batch_psbt_hex,omitempty"` + VtxoTreePaths []*TreePathPayload `protobuf:"bytes,3,rep,name=vtxo_tree_paths,json=vtxoTreePaths,proto3" json:"vtxo_tree_paths,omitempty"` + ConnectorLeaves []*ConnectorLeafPayload `protobuf:"bytes,4,rep,name=connector_leaves,json=connectorLeaves,proto3" json:"connector_leaves,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClientBatchInfoPayload) Reset() { + *x = ClientBatchInfoPayload{} + mi := &file_roundwire_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClientBatchInfoPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClientBatchInfoPayload) ProtoMessage() {} + +func (x *ClientBatchInfoPayload) ProtoReflect() protoreflect.Message { + mi := &file_roundwire_proto_msgTypes[29] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClientBatchInfoPayload.ProtoReflect.Descriptor instead. +func (*ClientBatchInfoPayload) Descriptor() ([]byte, []int) { + return file_roundwire_proto_rawDescGZIP(), []int{29} +} + +func (x *ClientBatchInfoPayload) GetRoundId() string { + if x != nil { + return x.RoundId + } + return "" +} + +func (x *ClientBatchInfoPayload) GetBatchPsbtHex() string { + if x != nil { + return x.BatchPsbtHex + } + return "" +} + +func (x *ClientBatchInfoPayload) GetVtxoTreePaths() []*TreePathPayload { + if x != nil { + return x.VtxoTreePaths + } + return nil +} + +func (x *ClientBatchInfoPayload) GetConnectorLeaves() []*ConnectorLeafPayload { + if x != nil { + return x.ConnectorLeaves + } + return nil +} + +type ClientRoundFailedRespPayload struct { + state protoimpl.MessageState `protogen:"open.v1"` + RoundId string `protobuf:"bytes,1,opt,name=round_id,json=roundId,proto3" json:"round_id,omitempty"` + Reason string `protobuf:"bytes,2,opt,name=reason,proto3" json:"reason,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClientRoundFailedRespPayload) Reset() { + *x = ClientRoundFailedRespPayload{} + mi := &file_roundwire_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClientRoundFailedRespPayload) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClientRoundFailedRespPayload) ProtoMessage() {} + +func (x *ClientRoundFailedRespPayload) ProtoReflect() protoreflect.Message { + mi := &file_roundwire_proto_msgTypes[30] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClientRoundFailedRespPayload.ProtoReflect.Descriptor instead. +func (*ClientRoundFailedRespPayload) Descriptor() ([]byte, []int) { + return file_roundwire_proto_rawDescGZIP(), []int{30} +} + +func (x *ClientRoundFailedRespPayload) GetRoundId() string { + if x != nil { + return x.RoundId + } + return "" +} + +func (x *ClientRoundFailedRespPayload) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +var File_roundwire_proto protoreflect.FileDescriptor + +const file_roundwire_proto_rawDesc = "" + + "\n" + + "\x0froundwire.proto\x12\troundwire\x1a\x1bgoogle/protobuf/empty.proto\":\n" + + "\x0fOutPointPayload\x12\x13\n" + + "\x05tx_id\x18\x01 \x01(\tR\x04txId\x12\x12\n" + + "\x04vout\x18\x02 \x01(\rR\x04vout\"A\n" + + "\fTxOutPayload\x12\x14\n" + + "\x05value\x18\x01 \x01(\x03R\x05value\x12\x1b\n" + + "\tpk_script\x18\x02 \x01(\tR\bpkScript\"r\n" + + "\x14KeyDescriptorPayload\x12\x1d\n" + + "\n" + + "key_family\x18\x01 \x01(\x05R\tkeyFamily\x12\x1b\n" + + "\tkey_index\x18\x02 \x01(\rR\bkeyIndex\x12\x1e\n" + + "\vpub_key_hex\x18\x03 \x01(\tR\tpubKeyHex\"\x9c\x01\n" + + "\x14JoinRoundAuthPayload\x12\x1f\n" + + "\vmessage_hex\x18\x01 \x01(\tR\n" + + "messageHex\x12\x1d\n" + + "\n" + + "valid_from\x18\x02 \x01(\rR\tvalidFrom\x12\x1f\n" + + "\vvalid_until\x18\x03 \x01(\rR\n" + + "validUntil\x12#\n" + + "\rsignature_hex\x18\x04 \x01(\tR\fsignatureHex\"\xb1\x01\n" + + "\x16BoardingRequestPayload\x126\n" + + "\boutpoint\x18\x01 \x01(\v2\x1a.roundwire.OutPointPayloadR\boutpoint\x12\x1d\n" + + "\n" + + "client_key\x18\x02 \x01(\tR\tclientKey\x12!\n" + + "\foperator_key\x18\x03 \x01(\tR\voperatorKey\x12\x1d\n" + + "\n" + + "exit_delay\x18\x04 \x01(\rR\texitDelay\"\xec\x01\n" + + "\x12VTXORequestPayload\x12\x16\n" + + "\x06amount\x18\x01 \x01(\x03R\x06amount\x12\"\n" + + "\rpk_script_hex\x18\x02 \x01(\tR\vpkScriptHex\x12\x16\n" + + "\x06expiry\x18\x03 \x01(\rR\x06expiry\x12\x1d\n" + + "\n" + + "client_key\x18\x04 \x01(\tR\tclientKey\x12!\n" + + "\foperator_key\x18\x05 \x01(\tR\voperatorKey\x12@\n" + + "\vsigning_key\x18\x06 \x01(\v2\x1f.roundwire.KeyDescriptorPayloadR\n" + + "signingKey\"X\n" + + "\x15ForfeitRequestPayload\x12?\n" + + "\rvtxo_outpoint\x18\x01 \x01(\v2\x1a.roundwire.OutPointPayloadR\fvtxoOutpoint\"F\n" + + "\x13LeaveRequestPayload\x12/\n" + + "\x06output\x18\x01 \x01(\v2\x17.roundwire.TxOutPayloadR\x06output\"\xb1\x03\n" + + "\x17JoinRoundRequestPayload\x12\x19\n" + + "\bround_id\x18\x01 \x01(\tR\aroundId\x12\x1e\n" + + "\n" + + "identifier\x18\x02 \x01(\tR\n" + + "identifier\x12N\n" + + "\x11boarding_requests\x18\x03 \x03(\v2!.roundwire.BoardingRequestPayloadR\x10boardingRequests\x12B\n" + + "\rvtxo_requests\x18\x04 \x03(\v2\x1d.roundwire.VTXORequestPayloadR\fvtxoRequests\x12K\n" + + "\x10forfeit_requests\x18\x05 \x03(\v2 .roundwire.ForfeitRequestPayloadR\x0fforfeitRequests\x12E\n" + + "\x0eleave_requests\x18\x06 \x03(\v2\x1e.roundwire.LeaveRequestPayloadR\rleaveRequests\x123\n" + + "\x04auth\x18\a \x01(\v2\x1f.roundwire.JoinRoundAuthPayloadR\x04auth\"G\n" + + "\fTxNonceEntry\x12\x1a\n" + + "\ttx_id_hex\x18\x01 \x01(\tR\atxIdHex\x12\x1b\n" + + "\tnonce_hex\x18\x02 \x01(\tR\bnonceHex\"j\n" + + "\x11SignerNonceBundle\x12$\n" + + "\x0esigner_key_hex\x18\x01 \x01(\tR\fsignerKeyHex\x12/\n" + + "\x06nonces\x18\x02 \x03(\v2\x17.roundwire.TxNonceEntryR\x06nonces\"h\n" + + "\x13SubmitNoncesPayload\x12\x19\n" + + "\bround_id\x18\x01 \x01(\tR\aroundId\x126\n" + + "\aentries\x18\x02 \x03(\v2\x1c.roundwire.SignerNonceBundleR\aentries\"M\n" + + "\n" + + "TxSigEntry\x12\x1a\n" + + "\ttx_id_hex\x18\x01 \x01(\tR\atxIdHex\x12#\n" + + "\rsignature_hex\x18\x02 \x01(\tR\fsignatureHex\"n\n" + + "\x0fSignerSigBundle\x12$\n" + + "\x0esigner_key_hex\x18\x01 \x01(\tR\fsignerKeyHex\x125\n" + + "\n" + + "signatures\x18\x02 \x03(\v2\x15.roundwire.TxSigEntryR\n" + + "signatures\"k\n" + + "\x18SubmitPartialSigsPayload\x12\x19\n" + + "\bround_id\x18\x01 \x01(\tR\aroundId\x124\n" + + "\aentries\x18\x02 \x03(\v2\x1a.roundwire.SignerSigBundleR\aentries\"\x97\x01\n" + + "\x17BoardingInputSigPayload\x12\x1f\n" + + "\vinput_index\x18\x01 \x01(\x05R\n" + + "inputIndex\x126\n" + + "\boutpoint\x18\x02 \x01(\v2\x1a.roundwire.OutPointPayloadR\boutpoint\x12#\n" + + "\rsignature_hex\x18\x03 \x01(\tR\fsignatureHex\"y\n" + + "\x18SubmitForfeitSigsPayload\x12\x19\n" + + "\bround_id\x18\x01 \x01(\tR\aroundId\x12B\n" + + "\n" + + "signatures\x18\x02 \x03(\v2\".roundwire.BoardingInputSigPayloadR\n" + + "signatures\"\xa5\x01\n" + + "\x15VTXOForfeitSigPayload\x12?\n" + + "\rvtxo_outpoint\x18\x01 \x01(\v2\x1a.roundwire.OutPointPayloadR\fvtxoOutpoint\x12#\n" + + "\rsignature_hex\x18\x02 \x01(\tR\fsignatureHex\x12&\n" + + "\x0funsigned_tx_hex\x18\x03 \x01(\tR\runsignedTxHex\"u\n" + + "\x1cSubmitVTXOForfeitSigsPayload\x12\x19\n" + + "\bround_id\x18\x01 \x01(\tR\aroundId\x12:\n" + + "\aentries\x18\x02 \x03(\v2 .roundwire.VTXOForfeitSigPayloadR\aentries\".\n" + + "\x16ClientErrorRespPayload\x12\x14\n" + + "\x05error\x18\x01 \x01(\tR\x05error\"\xe5\x01\n" + + "\x18ClientSuccessRespPayload\x12\x19\n" + + "\bround_id\x18\x01 \x01(\tR\aroundId\x12Z\n" + + "\x1baccepted_boarding_outpoints\x18\x02 \x03(\v2\x1a.roundwire.OutPointPayloadR\x19acceptedBoardingOutpoints\x12R\n" + + "\x17accepted_vtxo_outpoints\x18\x03 \x03(\v2\x1a.roundwire.OutPointPayloadR\x15acceptedVtxoOutpoints\"?\n" + + "\"ClientAwaitingInputSigsRespPayload\x12\x19\n" + + "\bround_id\x18\x01 \x01(\tR\aroundId\"h\n" + + "\x1aClientVTXOAggNoncesPayload\x12\x19\n" + + "\bround_id\x18\x01 \x01(\tR\aroundId\x12/\n" + + "\x06nonces\x18\x02 \x03(\v2\x17.roundwire.TxNonceEntryR\x06nonces\"l\n" + + "\x18ClientVTXOAggSigsPayload\x12\x19\n" + + "\bround_id\x18\x01 \x01(\tR\aroundId\x125\n" + + "\n" + + "signatures\x18\x02 \x03(\v2\x15.roundwire.TxSigEntryR\n" + + "signatures\"\xab\x02\n" + + "\vNodePayload\x120\n" + + "\x05input\x18\x01 \x01(\v2\x1a.roundwire.OutPointPayloadR\x05input\x121\n" + + "\aoutputs\x18\x02 \x03(\v2\x17.roundwire.TxOutPayloadR\aoutputs\x12\x1d\n" + + "\n" + + "co_signers\x18\x03 \x03(\tR\tcoSigners\x127\n" + + "\bchildren\x18\x04 \x03(\v2\x1b.roundwire.NodeChildPayloadR\bchildren\x12\x16\n" + + "\x06amount\x18\x05 \x01(\x03R\x06amount\x12#\n" + + "\rsignature_hex\x18\x06 \x01(\tR\fsignatureHex\x12\"\n" + + "\rfinal_key_hex\x18\a \x01(\tR\vfinalKeyHex\"T\n" + + "\x10NodeChildPayload\x12\x14\n" + + "\x05index\x18\x01 \x01(\rR\x05index\x12*\n" + + "\x04node\x18\x02 \x01(\v2\x16.roundwire.NodePayloadR\x04node\"\xea\x01\n" + + "\vTreePayload\x12*\n" + + "\x04root\x18\x01 \x01(\v2\x16.roundwire.NodePayloadR\x04root\x12A\n" + + "\x0ebatch_outpoint\x18\x02 \x01(\v2\x1a.roundwire.OutPointPayloadR\rbatchOutpoint\x12:\n" + + "\fbatch_output\x18\x03 \x01(\v2\x17.roundwire.TxOutPayloadR\vbatchOutput\x120\n" + + "\x14sweep_tapscript_root\x18\x04 \x01(\tR\x12sweepTapscriptRoot\"`\n" + + "\x0fTreePathPayload\x12!\n" + + "\foutput_index\x18\x01 \x01(\x05R\voutputIndex\x12*\n" + + "\x04tree\x18\x02 \x01(\v2\x16.roundwire.TreePayloadR\x04tree\"\xd2\x01\n" + + "\x14ConnectorLeafPayload\x12?\n" + + "\rvtxo_outpoint\x18\x01 \x01(\v2\x1a.roundwire.OutPointPayloadR\fvtxoOutpoint\x12?\n" + + "\rleaf_outpoint\x18\x02 \x01(\v2\x1a.roundwire.OutPointPayloadR\fleafOutpoint\x128\n" + + "\vleaf_output\x18\x03 \x01(\v2\x17.roundwire.TxOutPayloadR\n" + + "leafOutput\"\xe9\x01\n" + + "\x16ClientBatchInfoPayload\x12\x19\n" + + "\bround_id\x18\x01 \x01(\tR\aroundId\x12$\n" + + "\x0ebatch_psbt_hex\x18\x02 \x01(\tR\fbatchPsbtHex\x12B\n" + + "\x0fvtxo_tree_paths\x18\x03 \x03(\v2\x1a.roundwire.TreePathPayloadR\rvtxoTreePaths\x12J\n" + + "\x10connector_leaves\x18\x04 \x03(\v2\x1f.roundwire.ConnectorLeafPayloadR\x0fconnectorLeaves\"Q\n" + + "\x1cClientRoundFailedRespPayload\x12\x19\n" + + "\bround_id\x18\x01 \x01(\tR\aroundId\x12\x16\n" + + "\x06reason\x18\x02 \x01(\tR\x06reason2\x9b\b\n" + + "\x13RoundMailboxService\x12N\n" + + "\x10JoinRoundRequest\x12\".roundwire.JoinRoundRequestPayload\x1a\x16.google.protobuf.Empty\x12M\n" + + "\x13SubmitNoncesRequest\x12\x1e.roundwire.SubmitNoncesPayload\x1a\x16.google.protobuf.Empty\x12V\n" + + "\x17SubmitPartialSigRequest\x12#.roundwire.SubmitPartialSigsPayload\x1a\x16.google.protobuf.Empty\x12V\n" + + "\x17SubmitForfeitSigRequest\x12#.roundwire.SubmitForfeitSigsPayload\x1a\x16.google.protobuf.Empty\x12_\n" + + "\x1cSubmitVTXOForfeitSigsRequest\x12'.roundwire.SubmitVTXOForfeitSigsPayload\x1a\x16.google.protobuf.Empty\x12L\n" + + "\x0fClientErrorResp\x12!.roundwire.ClientErrorRespPayload\x1a\x16.google.protobuf.Empty\x12P\n" + + "\x11ClientSuccessResp\x12#.roundwire.ClientSuccessRespPayload\x1a\x16.google.protobuf.Empty\x12d\n" + + "\x1bClientAwaitingInputSigsResp\x12-.roundwire.ClientAwaitingInputSigsRespPayload\x1a\x16.google.protobuf.Empty\x12T\n" + + "\x13ClientVTXOAggNonces\x12%.roundwire.ClientVTXOAggNoncesPayload\x1a\x16.google.protobuf.Empty\x12P\n" + + "\x11ClientVTXOAggSigs\x12#.roundwire.ClientVTXOAggSigsPayload\x1a\x16.google.protobuf.Empty\x12L\n" + + "\x0fClientBatchInfo\x12!.roundwire.ClientBatchInfoPayload\x1a\x16.google.protobuf.Empty\x12X\n" + + "\x15ClientRoundFailedResp\x12'.roundwire.ClientRoundFailedRespPayload\x1a\x16.google.protobuf.EmptyB2Z0github.com/lightninglabs/darepo-client/roundwireb\x06proto3" + +var ( + file_roundwire_proto_rawDescOnce sync.Once + file_roundwire_proto_rawDescData []byte +) + +func file_roundwire_proto_rawDescGZIP() []byte { + file_roundwire_proto_rawDescOnce.Do(func() { + file_roundwire_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_roundwire_proto_rawDesc), len(file_roundwire_proto_rawDesc))) + }) + return file_roundwire_proto_rawDescData +} + +var file_roundwire_proto_msgTypes = make([]protoimpl.MessageInfo, 31) +var file_roundwire_proto_goTypes = []any{ + (*OutPointPayload)(nil), // 0: roundwire.OutPointPayload + (*TxOutPayload)(nil), // 1: roundwire.TxOutPayload + (*KeyDescriptorPayload)(nil), // 2: roundwire.KeyDescriptorPayload + (*JoinRoundAuthPayload)(nil), // 3: roundwire.JoinRoundAuthPayload + (*BoardingRequestPayload)(nil), // 4: roundwire.BoardingRequestPayload + (*VTXORequestPayload)(nil), // 5: roundwire.VTXORequestPayload + (*ForfeitRequestPayload)(nil), // 6: roundwire.ForfeitRequestPayload + (*LeaveRequestPayload)(nil), // 7: roundwire.LeaveRequestPayload + (*JoinRoundRequestPayload)(nil), // 8: roundwire.JoinRoundRequestPayload + (*TxNonceEntry)(nil), // 9: roundwire.TxNonceEntry + (*SignerNonceBundle)(nil), // 10: roundwire.SignerNonceBundle + (*SubmitNoncesPayload)(nil), // 11: roundwire.SubmitNoncesPayload + (*TxSigEntry)(nil), // 12: roundwire.TxSigEntry + (*SignerSigBundle)(nil), // 13: roundwire.SignerSigBundle + (*SubmitPartialSigsPayload)(nil), // 14: roundwire.SubmitPartialSigsPayload + (*BoardingInputSigPayload)(nil), // 15: roundwire.BoardingInputSigPayload + (*SubmitForfeitSigsPayload)(nil), // 16: roundwire.SubmitForfeitSigsPayload + (*VTXOForfeitSigPayload)(nil), // 17: roundwire.VTXOForfeitSigPayload + (*SubmitVTXOForfeitSigsPayload)(nil), // 18: roundwire.SubmitVTXOForfeitSigsPayload + (*ClientErrorRespPayload)(nil), // 19: roundwire.ClientErrorRespPayload + (*ClientSuccessRespPayload)(nil), // 20: roundwire.ClientSuccessRespPayload + (*ClientAwaitingInputSigsRespPayload)(nil), // 21: roundwire.ClientAwaitingInputSigsRespPayload + (*ClientVTXOAggNoncesPayload)(nil), // 22: roundwire.ClientVTXOAggNoncesPayload + (*ClientVTXOAggSigsPayload)(nil), // 23: roundwire.ClientVTXOAggSigsPayload + (*NodePayload)(nil), // 24: roundwire.NodePayload + (*NodeChildPayload)(nil), // 25: roundwire.NodeChildPayload + (*TreePayload)(nil), // 26: roundwire.TreePayload + (*TreePathPayload)(nil), // 27: roundwire.TreePathPayload + (*ConnectorLeafPayload)(nil), // 28: roundwire.ConnectorLeafPayload + (*ClientBatchInfoPayload)(nil), // 29: roundwire.ClientBatchInfoPayload + (*ClientRoundFailedRespPayload)(nil), // 30: roundwire.ClientRoundFailedRespPayload + (*emptypb.Empty)(nil), // 31: google.protobuf.Empty +} +var file_roundwire_proto_depIdxs = []int32{ + 0, // 0: roundwire.BoardingRequestPayload.outpoint:type_name -> roundwire.OutPointPayload + 2, // 1: roundwire.VTXORequestPayload.signing_key:type_name -> roundwire.KeyDescriptorPayload + 0, // 2: roundwire.ForfeitRequestPayload.vtxo_outpoint:type_name -> roundwire.OutPointPayload + 1, // 3: roundwire.LeaveRequestPayload.output:type_name -> roundwire.TxOutPayload + 4, // 4: roundwire.JoinRoundRequestPayload.boarding_requests:type_name -> roundwire.BoardingRequestPayload + 5, // 5: roundwire.JoinRoundRequestPayload.vtxo_requests:type_name -> roundwire.VTXORequestPayload + 6, // 6: roundwire.JoinRoundRequestPayload.forfeit_requests:type_name -> roundwire.ForfeitRequestPayload + 7, // 7: roundwire.JoinRoundRequestPayload.leave_requests:type_name -> roundwire.LeaveRequestPayload + 3, // 8: roundwire.JoinRoundRequestPayload.auth:type_name -> roundwire.JoinRoundAuthPayload + 9, // 9: roundwire.SignerNonceBundle.nonces:type_name -> roundwire.TxNonceEntry + 10, // 10: roundwire.SubmitNoncesPayload.entries:type_name -> roundwire.SignerNonceBundle + 12, // 11: roundwire.SignerSigBundle.signatures:type_name -> roundwire.TxSigEntry + 13, // 12: roundwire.SubmitPartialSigsPayload.entries:type_name -> roundwire.SignerSigBundle + 0, // 13: roundwire.BoardingInputSigPayload.outpoint:type_name -> roundwire.OutPointPayload + 15, // 14: roundwire.SubmitForfeitSigsPayload.signatures:type_name -> roundwire.BoardingInputSigPayload + 0, // 15: roundwire.VTXOForfeitSigPayload.vtxo_outpoint:type_name -> roundwire.OutPointPayload + 17, // 16: roundwire.SubmitVTXOForfeitSigsPayload.entries:type_name -> roundwire.VTXOForfeitSigPayload + 0, // 17: roundwire.ClientSuccessRespPayload.accepted_boarding_outpoints:type_name -> roundwire.OutPointPayload + 0, // 18: roundwire.ClientSuccessRespPayload.accepted_vtxo_outpoints:type_name -> roundwire.OutPointPayload + 9, // 19: roundwire.ClientVTXOAggNoncesPayload.nonces:type_name -> roundwire.TxNonceEntry + 12, // 20: roundwire.ClientVTXOAggSigsPayload.signatures:type_name -> roundwire.TxSigEntry + 0, // 21: roundwire.NodePayload.input:type_name -> roundwire.OutPointPayload + 1, // 22: roundwire.NodePayload.outputs:type_name -> roundwire.TxOutPayload + 25, // 23: roundwire.NodePayload.children:type_name -> roundwire.NodeChildPayload + 24, // 24: roundwire.NodeChildPayload.node:type_name -> roundwire.NodePayload + 24, // 25: roundwire.TreePayload.root:type_name -> roundwire.NodePayload + 0, // 26: roundwire.TreePayload.batch_outpoint:type_name -> roundwire.OutPointPayload + 1, // 27: roundwire.TreePayload.batch_output:type_name -> roundwire.TxOutPayload + 26, // 28: roundwire.TreePathPayload.tree:type_name -> roundwire.TreePayload + 0, // 29: roundwire.ConnectorLeafPayload.vtxo_outpoint:type_name -> roundwire.OutPointPayload + 0, // 30: roundwire.ConnectorLeafPayload.leaf_outpoint:type_name -> roundwire.OutPointPayload + 1, // 31: roundwire.ConnectorLeafPayload.leaf_output:type_name -> roundwire.TxOutPayload + 27, // 32: roundwire.ClientBatchInfoPayload.vtxo_tree_paths:type_name -> roundwire.TreePathPayload + 28, // 33: roundwire.ClientBatchInfoPayload.connector_leaves:type_name -> roundwire.ConnectorLeafPayload + 8, // 34: roundwire.RoundMailboxService.JoinRoundRequest:input_type -> roundwire.JoinRoundRequestPayload + 11, // 35: roundwire.RoundMailboxService.SubmitNoncesRequest:input_type -> roundwire.SubmitNoncesPayload + 14, // 36: roundwire.RoundMailboxService.SubmitPartialSigRequest:input_type -> roundwire.SubmitPartialSigsPayload + 16, // 37: roundwire.RoundMailboxService.SubmitForfeitSigRequest:input_type -> roundwire.SubmitForfeitSigsPayload + 18, // 38: roundwire.RoundMailboxService.SubmitVTXOForfeitSigsRequest:input_type -> roundwire.SubmitVTXOForfeitSigsPayload + 19, // 39: roundwire.RoundMailboxService.ClientErrorResp:input_type -> roundwire.ClientErrorRespPayload + 20, // 40: roundwire.RoundMailboxService.ClientSuccessResp:input_type -> roundwire.ClientSuccessRespPayload + 21, // 41: roundwire.RoundMailboxService.ClientAwaitingInputSigsResp:input_type -> roundwire.ClientAwaitingInputSigsRespPayload + 22, // 42: roundwire.RoundMailboxService.ClientVTXOAggNonces:input_type -> roundwire.ClientVTXOAggNoncesPayload + 23, // 43: roundwire.RoundMailboxService.ClientVTXOAggSigs:input_type -> roundwire.ClientVTXOAggSigsPayload + 29, // 44: roundwire.RoundMailboxService.ClientBatchInfo:input_type -> roundwire.ClientBatchInfoPayload + 30, // 45: roundwire.RoundMailboxService.ClientRoundFailedResp:input_type -> roundwire.ClientRoundFailedRespPayload + 31, // 46: roundwire.RoundMailboxService.JoinRoundRequest:output_type -> google.protobuf.Empty + 31, // 47: roundwire.RoundMailboxService.SubmitNoncesRequest:output_type -> google.protobuf.Empty + 31, // 48: roundwire.RoundMailboxService.SubmitPartialSigRequest:output_type -> google.protobuf.Empty + 31, // 49: roundwire.RoundMailboxService.SubmitForfeitSigRequest:output_type -> google.protobuf.Empty + 31, // 50: roundwire.RoundMailboxService.SubmitVTXOForfeitSigsRequest:output_type -> google.protobuf.Empty + 31, // 51: roundwire.RoundMailboxService.ClientErrorResp:output_type -> google.protobuf.Empty + 31, // 52: roundwire.RoundMailboxService.ClientSuccessResp:output_type -> google.protobuf.Empty + 31, // 53: roundwire.RoundMailboxService.ClientAwaitingInputSigsResp:output_type -> google.protobuf.Empty + 31, // 54: roundwire.RoundMailboxService.ClientVTXOAggNonces:output_type -> google.protobuf.Empty + 31, // 55: roundwire.RoundMailboxService.ClientVTXOAggSigs:output_type -> google.protobuf.Empty + 31, // 56: roundwire.RoundMailboxService.ClientBatchInfo:output_type -> google.protobuf.Empty + 31, // 57: roundwire.RoundMailboxService.ClientRoundFailedResp:output_type -> google.protobuf.Empty + 46, // [46:58] is the sub-list for method output_type + 34, // [34:46] is the sub-list for method input_type + 34, // [34:34] is the sub-list for extension type_name + 34, // [34:34] is the sub-list for extension extendee + 0, // [0:34] is the sub-list for field type_name +} + +func init() { file_roundwire_proto_init() } +func file_roundwire_proto_init() { + if File_roundwire_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_roundwire_proto_rawDesc), len(file_roundwire_proto_rawDesc)), + NumEnums: 0, + NumMessages: 31, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_roundwire_proto_goTypes, + DependencyIndexes: file_roundwire_proto_depIdxs, + MessageInfos: file_roundwire_proto_msgTypes, + }.Build() + File_roundwire_proto = out.File + file_roundwire_proto_goTypes = nil + file_roundwire_proto_depIdxs = nil +} diff --git a/roundwire/roundwire.proto b/roundwire/roundwire.proto new file mode 100644 index 000000000..e33ec6eb2 --- /dev/null +++ b/roundwire/roundwire.proto @@ -0,0 +1,217 @@ +syntax = "proto3"; + +package roundwire; + +import "google/protobuf/empty.proto"; + +option go_package = "github.com/lightninglabs/darepo-client/roundwire"; + +// RoundMailboxService defines round mailbox method payloads. +// +// These methods are transported over mailbox envelopes and are not used as +// strict unary RPCs in the round FSM path. +service RoundMailboxService { + rpc JoinRoundRequest (JoinRoundRequestPayload) + returns (google.protobuf.Empty); + rpc SubmitNoncesRequest (SubmitNoncesPayload) + returns (google.protobuf.Empty); + rpc SubmitPartialSigRequest (SubmitPartialSigsPayload) + returns (google.protobuf.Empty); + rpc SubmitForfeitSigRequest (SubmitForfeitSigsPayload) + returns (google.protobuf.Empty); + rpc SubmitVTXOForfeitSigsRequest (SubmitVTXOForfeitSigsPayload) + returns (google.protobuf.Empty); + + rpc ClientErrorResp (ClientErrorRespPayload) + returns (google.protobuf.Empty); + rpc ClientSuccessResp (ClientSuccessRespPayload) + returns (google.protobuf.Empty); + rpc ClientAwaitingInputSigsResp (ClientAwaitingInputSigsRespPayload) + returns (google.protobuf.Empty); + rpc ClientVTXOAggNonces (ClientVTXOAggNoncesPayload) + returns (google.protobuf.Empty); + rpc ClientVTXOAggSigs (ClientVTXOAggSigsPayload) + returns (google.protobuf.Empty); + rpc ClientBatchInfo (ClientBatchInfoPayload) + returns (google.protobuf.Empty); + rpc ClientRoundFailedResp (ClientRoundFailedRespPayload) + returns (google.protobuf.Empty); +} + +message OutPointPayload { + string tx_id = 1; + uint32 vout = 2; +} + +message TxOutPayload { + int64 value = 1; + string pk_script = 2; +} + +message KeyDescriptorPayload { + int32 key_family = 1; + uint32 key_index = 2; + string pub_key_hex = 3; +} + +message JoinRoundAuthPayload { + string message_hex = 1; + uint32 valid_from = 2; + uint32 valid_until = 3; + string signature_hex = 4; +} + +message BoardingRequestPayload { + OutPointPayload outpoint = 1; + string client_key = 2; + string operator_key = 3; + uint32 exit_delay = 4; +} + +message VTXORequestPayload { + int64 amount = 1; + string pk_script_hex = 2; + uint32 expiry = 3; + string client_key = 4; + string operator_key = 5; + KeyDescriptorPayload signing_key = 6; +} + +message ForfeitRequestPayload { + OutPointPayload vtxo_outpoint = 1; +} + +message LeaveRequestPayload { + TxOutPayload output = 1; +} + +message JoinRoundRequestPayload { + string round_id = 1; + string identifier = 2; + repeated BoardingRequestPayload boarding_requests = 3; + repeated VTXORequestPayload vtxo_requests = 4; + repeated ForfeitRequestPayload forfeit_requests = 5; + repeated LeaveRequestPayload leave_requests = 6; + JoinRoundAuthPayload auth = 7; +} + +message TxNonceEntry { + string tx_id_hex = 1; + string nonce_hex = 2; +} + +message SignerNonceBundle { + string signer_key_hex = 1; + repeated TxNonceEntry nonces = 2; +} + +message SubmitNoncesPayload { + string round_id = 1; + repeated SignerNonceBundle entries = 2; +} + +message TxSigEntry { + string tx_id_hex = 1; + string signature_hex = 2; +} + +message SignerSigBundle { + string signer_key_hex = 1; + repeated TxSigEntry signatures = 2; +} + +message SubmitPartialSigsPayload { + string round_id = 1; + repeated SignerSigBundle entries = 2; +} + +message BoardingInputSigPayload { + int32 input_index = 1; + OutPointPayload outpoint = 2; + string signature_hex = 3; +} + +message SubmitForfeitSigsPayload { + string round_id = 1; + repeated BoardingInputSigPayload signatures = 2; +} + +message VTXOForfeitSigPayload { + OutPointPayload vtxo_outpoint = 1; + string signature_hex = 2; + string unsigned_tx_hex = 3; +} + +message SubmitVTXOForfeitSigsPayload { + string round_id = 1; + repeated VTXOForfeitSigPayload entries = 2; +} + +message ClientErrorRespPayload { + string error = 1; +} + +message ClientSuccessRespPayload { + string round_id = 1; + repeated OutPointPayload accepted_boarding_outpoints = 2; + repeated OutPointPayload accepted_vtxo_outpoints = 3; +} + +message ClientAwaitingInputSigsRespPayload { + string round_id = 1; +} + +message ClientVTXOAggNoncesPayload { + string round_id = 1; + repeated TxNonceEntry nonces = 2; +} + +message ClientVTXOAggSigsPayload { + string round_id = 1; + repeated TxSigEntry signatures = 2; +} + +message NodePayload { + OutPointPayload input = 1; + repeated TxOutPayload outputs = 2; + repeated string co_signers = 3; + repeated NodeChildPayload children = 4; + int64 amount = 5; + string signature_hex = 6; + string final_key_hex = 7; +} + +message NodeChildPayload { + uint32 index = 1; + NodePayload node = 2; +} + +message TreePayload { + NodePayload root = 1; + OutPointPayload batch_outpoint = 2; + TxOutPayload batch_output = 3; + string sweep_tapscript_root = 4; +} + +message TreePathPayload { + int32 output_index = 1; + TreePayload tree = 2; +} + +message ConnectorLeafPayload { + OutPointPayload vtxo_outpoint = 1; + OutPointPayload leaf_outpoint = 2; + TxOutPayload leaf_output = 3; +} + +message ClientBatchInfoPayload { + string round_id = 1; + string batch_psbt_hex = 2; + repeated TreePathPayload vtxo_tree_paths = 3; + repeated ConnectorLeafPayload connector_leaves = 4; +} + +message ClientRoundFailedRespPayload { + string round_id = 1; + string reason = 2; +} diff --git a/roundwire/roundwire_grpc.pb.go b/roundwire/roundwire_grpc.pb.go new file mode 100644 index 000000000..e01d5961d --- /dev/null +++ b/roundwire/roundwire_grpc.pb.go @@ -0,0 +1,550 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v3.21.12 +// source: roundwire.proto + +package roundwire + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + emptypb "google.golang.org/protobuf/types/known/emptypb" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + RoundMailboxService_JoinRoundRequest_FullMethodName = "/roundwire.RoundMailboxService/JoinRoundRequest" + RoundMailboxService_SubmitNoncesRequest_FullMethodName = "/roundwire.RoundMailboxService/SubmitNoncesRequest" + RoundMailboxService_SubmitPartialSigRequest_FullMethodName = "/roundwire.RoundMailboxService/SubmitPartialSigRequest" + RoundMailboxService_SubmitForfeitSigRequest_FullMethodName = "/roundwire.RoundMailboxService/SubmitForfeitSigRequest" + RoundMailboxService_SubmitVTXOForfeitSigsRequest_FullMethodName = "/roundwire.RoundMailboxService/SubmitVTXOForfeitSigsRequest" + RoundMailboxService_ClientErrorResp_FullMethodName = "/roundwire.RoundMailboxService/ClientErrorResp" + RoundMailboxService_ClientSuccessResp_FullMethodName = "/roundwire.RoundMailboxService/ClientSuccessResp" + RoundMailboxService_ClientAwaitingInputSigsResp_FullMethodName = "/roundwire.RoundMailboxService/ClientAwaitingInputSigsResp" + RoundMailboxService_ClientVTXOAggNonces_FullMethodName = "/roundwire.RoundMailboxService/ClientVTXOAggNonces" + RoundMailboxService_ClientVTXOAggSigs_FullMethodName = "/roundwire.RoundMailboxService/ClientVTXOAggSigs" + RoundMailboxService_ClientBatchInfo_FullMethodName = "/roundwire.RoundMailboxService/ClientBatchInfo" + RoundMailboxService_ClientRoundFailedResp_FullMethodName = "/roundwire.RoundMailboxService/ClientRoundFailedResp" +) + +// RoundMailboxServiceClient is the client API for RoundMailboxService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// RoundMailboxService defines round mailbox method payloads. +// +// These methods are transported over mailbox envelopes and are not used as +// strict unary RPCs in the round FSM path. +type RoundMailboxServiceClient interface { + JoinRoundRequest(ctx context.Context, in *JoinRoundRequestPayload, opts ...grpc.CallOption) (*emptypb.Empty, error) + SubmitNoncesRequest(ctx context.Context, in *SubmitNoncesPayload, opts ...grpc.CallOption) (*emptypb.Empty, error) + SubmitPartialSigRequest(ctx context.Context, in *SubmitPartialSigsPayload, opts ...grpc.CallOption) (*emptypb.Empty, error) + SubmitForfeitSigRequest(ctx context.Context, in *SubmitForfeitSigsPayload, opts ...grpc.CallOption) (*emptypb.Empty, error) + SubmitVTXOForfeitSigsRequest(ctx context.Context, in *SubmitVTXOForfeitSigsPayload, opts ...grpc.CallOption) (*emptypb.Empty, error) + ClientErrorResp(ctx context.Context, in *ClientErrorRespPayload, opts ...grpc.CallOption) (*emptypb.Empty, error) + ClientSuccessResp(ctx context.Context, in *ClientSuccessRespPayload, opts ...grpc.CallOption) (*emptypb.Empty, error) + ClientAwaitingInputSigsResp(ctx context.Context, in *ClientAwaitingInputSigsRespPayload, opts ...grpc.CallOption) (*emptypb.Empty, error) + ClientVTXOAggNonces(ctx context.Context, in *ClientVTXOAggNoncesPayload, opts ...grpc.CallOption) (*emptypb.Empty, error) + ClientVTXOAggSigs(ctx context.Context, in *ClientVTXOAggSigsPayload, opts ...grpc.CallOption) (*emptypb.Empty, error) + ClientBatchInfo(ctx context.Context, in *ClientBatchInfoPayload, opts ...grpc.CallOption) (*emptypb.Empty, error) + ClientRoundFailedResp(ctx context.Context, in *ClientRoundFailedRespPayload, opts ...grpc.CallOption) (*emptypb.Empty, error) +} + +type roundMailboxServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewRoundMailboxServiceClient(cc grpc.ClientConnInterface) RoundMailboxServiceClient { + return &roundMailboxServiceClient{cc} +} + +func (c *roundMailboxServiceClient) JoinRoundRequest(ctx context.Context, in *JoinRoundRequestPayload, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, RoundMailboxService_JoinRoundRequest_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *roundMailboxServiceClient) SubmitNoncesRequest(ctx context.Context, in *SubmitNoncesPayload, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, RoundMailboxService_SubmitNoncesRequest_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *roundMailboxServiceClient) SubmitPartialSigRequest(ctx context.Context, in *SubmitPartialSigsPayload, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, RoundMailboxService_SubmitPartialSigRequest_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *roundMailboxServiceClient) SubmitForfeitSigRequest(ctx context.Context, in *SubmitForfeitSigsPayload, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, RoundMailboxService_SubmitForfeitSigRequest_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *roundMailboxServiceClient) SubmitVTXOForfeitSigsRequest(ctx context.Context, in *SubmitVTXOForfeitSigsPayload, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, RoundMailboxService_SubmitVTXOForfeitSigsRequest_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *roundMailboxServiceClient) ClientErrorResp(ctx context.Context, in *ClientErrorRespPayload, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, RoundMailboxService_ClientErrorResp_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *roundMailboxServiceClient) ClientSuccessResp(ctx context.Context, in *ClientSuccessRespPayload, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, RoundMailboxService_ClientSuccessResp_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *roundMailboxServiceClient) ClientAwaitingInputSigsResp(ctx context.Context, in *ClientAwaitingInputSigsRespPayload, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, RoundMailboxService_ClientAwaitingInputSigsResp_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *roundMailboxServiceClient) ClientVTXOAggNonces(ctx context.Context, in *ClientVTXOAggNoncesPayload, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, RoundMailboxService_ClientVTXOAggNonces_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *roundMailboxServiceClient) ClientVTXOAggSigs(ctx context.Context, in *ClientVTXOAggSigsPayload, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, RoundMailboxService_ClientVTXOAggSigs_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *roundMailboxServiceClient) ClientBatchInfo(ctx context.Context, in *ClientBatchInfoPayload, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, RoundMailboxService_ClientBatchInfo_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *roundMailboxServiceClient) ClientRoundFailedResp(ctx context.Context, in *ClientRoundFailedRespPayload, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, RoundMailboxService_ClientRoundFailedResp_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// RoundMailboxServiceServer is the server API for RoundMailboxService service. +// All implementations must embed UnimplementedRoundMailboxServiceServer +// for forward compatibility. +// +// RoundMailboxService defines round mailbox method payloads. +// +// These methods are transported over mailbox envelopes and are not used as +// strict unary RPCs in the round FSM path. +type RoundMailboxServiceServer interface { + JoinRoundRequest(context.Context, *JoinRoundRequestPayload) (*emptypb.Empty, error) + SubmitNoncesRequest(context.Context, *SubmitNoncesPayload) (*emptypb.Empty, error) + SubmitPartialSigRequest(context.Context, *SubmitPartialSigsPayload) (*emptypb.Empty, error) + SubmitForfeitSigRequest(context.Context, *SubmitForfeitSigsPayload) (*emptypb.Empty, error) + SubmitVTXOForfeitSigsRequest(context.Context, *SubmitVTXOForfeitSigsPayload) (*emptypb.Empty, error) + ClientErrorResp(context.Context, *ClientErrorRespPayload) (*emptypb.Empty, error) + ClientSuccessResp(context.Context, *ClientSuccessRespPayload) (*emptypb.Empty, error) + ClientAwaitingInputSigsResp(context.Context, *ClientAwaitingInputSigsRespPayload) (*emptypb.Empty, error) + ClientVTXOAggNonces(context.Context, *ClientVTXOAggNoncesPayload) (*emptypb.Empty, error) + ClientVTXOAggSigs(context.Context, *ClientVTXOAggSigsPayload) (*emptypb.Empty, error) + ClientBatchInfo(context.Context, *ClientBatchInfoPayload) (*emptypb.Empty, error) + ClientRoundFailedResp(context.Context, *ClientRoundFailedRespPayload) (*emptypb.Empty, error) + mustEmbedUnimplementedRoundMailboxServiceServer() +} + +// UnimplementedRoundMailboxServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedRoundMailboxServiceServer struct{} + +func (UnimplementedRoundMailboxServiceServer) JoinRoundRequest(context.Context, *JoinRoundRequestPayload) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method JoinRoundRequest not implemented") +} +func (UnimplementedRoundMailboxServiceServer) SubmitNoncesRequest(context.Context, *SubmitNoncesPayload) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method SubmitNoncesRequest not implemented") +} +func (UnimplementedRoundMailboxServiceServer) SubmitPartialSigRequest(context.Context, *SubmitPartialSigsPayload) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method SubmitPartialSigRequest not implemented") +} +func (UnimplementedRoundMailboxServiceServer) SubmitForfeitSigRequest(context.Context, *SubmitForfeitSigsPayload) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method SubmitForfeitSigRequest not implemented") +} +func (UnimplementedRoundMailboxServiceServer) SubmitVTXOForfeitSigsRequest(context.Context, *SubmitVTXOForfeitSigsPayload) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method SubmitVTXOForfeitSigsRequest not implemented") +} +func (UnimplementedRoundMailboxServiceServer) ClientErrorResp(context.Context, *ClientErrorRespPayload) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method ClientErrorResp not implemented") +} +func (UnimplementedRoundMailboxServiceServer) ClientSuccessResp(context.Context, *ClientSuccessRespPayload) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method ClientSuccessResp not implemented") +} +func (UnimplementedRoundMailboxServiceServer) ClientAwaitingInputSigsResp(context.Context, *ClientAwaitingInputSigsRespPayload) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method ClientAwaitingInputSigsResp not implemented") +} +func (UnimplementedRoundMailboxServiceServer) ClientVTXOAggNonces(context.Context, *ClientVTXOAggNoncesPayload) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method ClientVTXOAggNonces not implemented") +} +func (UnimplementedRoundMailboxServiceServer) ClientVTXOAggSigs(context.Context, *ClientVTXOAggSigsPayload) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method ClientVTXOAggSigs not implemented") +} +func (UnimplementedRoundMailboxServiceServer) ClientBatchInfo(context.Context, *ClientBatchInfoPayload) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method ClientBatchInfo not implemented") +} +func (UnimplementedRoundMailboxServiceServer) ClientRoundFailedResp(context.Context, *ClientRoundFailedRespPayload) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method ClientRoundFailedResp not implemented") +} +func (UnimplementedRoundMailboxServiceServer) mustEmbedUnimplementedRoundMailboxServiceServer() {} +func (UnimplementedRoundMailboxServiceServer) testEmbeddedByValue() {} + +// UnsafeRoundMailboxServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to RoundMailboxServiceServer will +// result in compilation errors. +type UnsafeRoundMailboxServiceServer interface { + mustEmbedUnimplementedRoundMailboxServiceServer() +} + +func RegisterRoundMailboxServiceServer(s grpc.ServiceRegistrar, srv RoundMailboxServiceServer) { + // If the following call pancis, it indicates UnimplementedRoundMailboxServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&RoundMailboxService_ServiceDesc, srv) +} + +func _RoundMailboxService_JoinRoundRequest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(JoinRoundRequestPayload) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RoundMailboxServiceServer).JoinRoundRequest(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RoundMailboxService_JoinRoundRequest_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RoundMailboxServiceServer).JoinRoundRequest(ctx, req.(*JoinRoundRequestPayload)) + } + return interceptor(ctx, in, info, handler) +} + +func _RoundMailboxService_SubmitNoncesRequest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SubmitNoncesPayload) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RoundMailboxServiceServer).SubmitNoncesRequest(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RoundMailboxService_SubmitNoncesRequest_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RoundMailboxServiceServer).SubmitNoncesRequest(ctx, req.(*SubmitNoncesPayload)) + } + return interceptor(ctx, in, info, handler) +} + +func _RoundMailboxService_SubmitPartialSigRequest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SubmitPartialSigsPayload) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RoundMailboxServiceServer).SubmitPartialSigRequest(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RoundMailboxService_SubmitPartialSigRequest_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RoundMailboxServiceServer).SubmitPartialSigRequest(ctx, req.(*SubmitPartialSigsPayload)) + } + return interceptor(ctx, in, info, handler) +} + +func _RoundMailboxService_SubmitForfeitSigRequest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SubmitForfeitSigsPayload) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RoundMailboxServiceServer).SubmitForfeitSigRequest(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RoundMailboxService_SubmitForfeitSigRequest_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RoundMailboxServiceServer).SubmitForfeitSigRequest(ctx, req.(*SubmitForfeitSigsPayload)) + } + return interceptor(ctx, in, info, handler) +} + +func _RoundMailboxService_SubmitVTXOForfeitSigsRequest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SubmitVTXOForfeitSigsPayload) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RoundMailboxServiceServer).SubmitVTXOForfeitSigsRequest(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RoundMailboxService_SubmitVTXOForfeitSigsRequest_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RoundMailboxServiceServer).SubmitVTXOForfeitSigsRequest(ctx, req.(*SubmitVTXOForfeitSigsPayload)) + } + return interceptor(ctx, in, info, handler) +} + +func _RoundMailboxService_ClientErrorResp_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ClientErrorRespPayload) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RoundMailboxServiceServer).ClientErrorResp(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RoundMailboxService_ClientErrorResp_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RoundMailboxServiceServer).ClientErrorResp(ctx, req.(*ClientErrorRespPayload)) + } + return interceptor(ctx, in, info, handler) +} + +func _RoundMailboxService_ClientSuccessResp_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ClientSuccessRespPayload) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RoundMailboxServiceServer).ClientSuccessResp(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RoundMailboxService_ClientSuccessResp_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RoundMailboxServiceServer).ClientSuccessResp(ctx, req.(*ClientSuccessRespPayload)) + } + return interceptor(ctx, in, info, handler) +} + +func _RoundMailboxService_ClientAwaitingInputSigsResp_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ClientAwaitingInputSigsRespPayload) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RoundMailboxServiceServer).ClientAwaitingInputSigsResp(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RoundMailboxService_ClientAwaitingInputSigsResp_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RoundMailboxServiceServer).ClientAwaitingInputSigsResp(ctx, req.(*ClientAwaitingInputSigsRespPayload)) + } + return interceptor(ctx, in, info, handler) +} + +func _RoundMailboxService_ClientVTXOAggNonces_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ClientVTXOAggNoncesPayload) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RoundMailboxServiceServer).ClientVTXOAggNonces(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RoundMailboxService_ClientVTXOAggNonces_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RoundMailboxServiceServer).ClientVTXOAggNonces(ctx, req.(*ClientVTXOAggNoncesPayload)) + } + return interceptor(ctx, in, info, handler) +} + +func _RoundMailboxService_ClientVTXOAggSigs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ClientVTXOAggSigsPayload) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RoundMailboxServiceServer).ClientVTXOAggSigs(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RoundMailboxService_ClientVTXOAggSigs_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RoundMailboxServiceServer).ClientVTXOAggSigs(ctx, req.(*ClientVTXOAggSigsPayload)) + } + return interceptor(ctx, in, info, handler) +} + +func _RoundMailboxService_ClientBatchInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ClientBatchInfoPayload) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RoundMailboxServiceServer).ClientBatchInfo(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RoundMailboxService_ClientBatchInfo_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RoundMailboxServiceServer).ClientBatchInfo(ctx, req.(*ClientBatchInfoPayload)) + } + return interceptor(ctx, in, info, handler) +} + +func _RoundMailboxService_ClientRoundFailedResp_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ClientRoundFailedRespPayload) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RoundMailboxServiceServer).ClientRoundFailedResp(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RoundMailboxService_ClientRoundFailedResp_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RoundMailboxServiceServer).ClientRoundFailedResp(ctx, req.(*ClientRoundFailedRespPayload)) + } + return interceptor(ctx, in, info, handler) +} + +// RoundMailboxService_ServiceDesc is the grpc.ServiceDesc for RoundMailboxService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var RoundMailboxService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "roundwire.RoundMailboxService", + HandlerType: (*RoundMailboxServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "JoinRoundRequest", + Handler: _RoundMailboxService_JoinRoundRequest_Handler, + }, + { + MethodName: "SubmitNoncesRequest", + Handler: _RoundMailboxService_SubmitNoncesRequest_Handler, + }, + { + MethodName: "SubmitPartialSigRequest", + Handler: _RoundMailboxService_SubmitPartialSigRequest_Handler, + }, + { + MethodName: "SubmitForfeitSigRequest", + Handler: _RoundMailboxService_SubmitForfeitSigRequest_Handler, + }, + { + MethodName: "SubmitVTXOForfeitSigsRequest", + Handler: _RoundMailboxService_SubmitVTXOForfeitSigsRequest_Handler, + }, + { + MethodName: "ClientErrorResp", + Handler: _RoundMailboxService_ClientErrorResp_Handler, + }, + { + MethodName: "ClientSuccessResp", + Handler: _RoundMailboxService_ClientSuccessResp_Handler, + }, + { + MethodName: "ClientAwaitingInputSigsResp", + Handler: _RoundMailboxService_ClientAwaitingInputSigsResp_Handler, + }, + { + MethodName: "ClientVTXOAggNonces", + Handler: _RoundMailboxService_ClientVTXOAggNonces_Handler, + }, + { + MethodName: "ClientVTXOAggSigs", + Handler: _RoundMailboxService_ClientVTXOAggSigs_Handler, + }, + { + MethodName: "ClientBatchInfo", + Handler: _RoundMailboxService_ClientBatchInfo_Handler, + }, + { + MethodName: "ClientRoundFailedResp", + Handler: _RoundMailboxService_ClientRoundFailedResp_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "roundwire.proto", +} diff --git a/roundwire/roundwire_mailboxrpc.pb.go b/roundwire/roundwire_mailboxrpc.pb.go new file mode 100644 index 000000000..84e74b131 --- /dev/null +++ b/roundwire/roundwire_mailboxrpc.pb.go @@ -0,0 +1,452 @@ +// Code generated by protoc-gen-mailboxrpc. DO NOT EDIT. + +package roundwire + +import ( + context "context" + fmt "fmt" + rpc "github.com/lightninglabs/darepo-client/mailbox/rpc" + proto "google.golang.org/protobuf/proto" + emptypb "google.golang.org/protobuf/types/known/emptypb" +) + +// RoundMailboxServiceMailboxClient is a typed mailbox RPC client for RoundMailboxService. +type RoundMailboxServiceMailboxClient struct { + // C is the underlying RPC-over-mailbox runtime client. + C rpc.RPCClient +} + +// NewRoundMailboxServiceMailboxClient creates a typed mailbox client. +func NewRoundMailboxServiceMailboxClient(c rpc.RPCClient) *RoundMailboxServiceMailboxClient { + return &RoundMailboxServiceMailboxClient{ + C: c, + } +} + +// RoundMailboxServiceMailboxServer is the mailbox server interface for RoundMailboxService. +type RoundMailboxServiceMailboxServer interface { + // JoinRoundRequest handles JoinRoundRequest. + JoinRoundRequest(ctx context.Context, req *JoinRoundRequestPayload) (*emptypb.Empty, error) + // SubmitNoncesRequest handles SubmitNoncesRequest. + SubmitNoncesRequest(ctx context.Context, req *SubmitNoncesPayload) (*emptypb.Empty, error) + // SubmitPartialSigRequest handles SubmitPartialSigRequest. + SubmitPartialSigRequest(ctx context.Context, req *SubmitPartialSigsPayload) (*emptypb.Empty, error) + // SubmitForfeitSigRequest handles SubmitForfeitSigRequest. + SubmitForfeitSigRequest(ctx context.Context, req *SubmitForfeitSigsPayload) (*emptypb.Empty, error) + // SubmitVTXOForfeitSigsRequest handles SubmitVTXOForfeitSigsRequest. + SubmitVTXOForfeitSigsRequest(ctx context.Context, req *SubmitVTXOForfeitSigsPayload) (*emptypb.Empty, error) + // ClientErrorResp handles ClientErrorResp. + ClientErrorResp(ctx context.Context, req *ClientErrorRespPayload) (*emptypb.Empty, error) + // ClientSuccessResp handles ClientSuccessResp. + ClientSuccessResp(ctx context.Context, req *ClientSuccessRespPayload) (*emptypb.Empty, error) + // ClientAwaitingInputSigsResp handles ClientAwaitingInputSigsResp. + ClientAwaitingInputSigsResp(ctx context.Context, req *ClientAwaitingInputSigsRespPayload) (*emptypb.Empty, error) + // ClientVTXOAggNonces handles ClientVTXOAggNonces. + ClientVTXOAggNonces(ctx context.Context, req *ClientVTXOAggNoncesPayload) (*emptypb.Empty, error) + // ClientVTXOAggSigs handles ClientVTXOAggSigs. + ClientVTXOAggSigs(ctx context.Context, req *ClientVTXOAggSigsPayload) (*emptypb.Empty, error) + // ClientBatchInfo handles ClientBatchInfo. + ClientBatchInfo(ctx context.Context, req *ClientBatchInfoPayload) (*emptypb.Empty, error) + // ClientRoundFailedResp handles ClientRoundFailedResp. + ClientRoundFailedResp(ctx context.Context, req *ClientRoundFailedRespPayload) (*emptypb.Empty, error) +} + +// RegisterRoundMailboxServiceMailboxServer registers handlers for RoundMailboxService. +func RegisterRoundMailboxServiceMailboxServer(r rpc.Router, impl RoundMailboxServiceMailboxServer) { + r.Handle("roundwire.RoundMailboxService", "JoinRoundRequest", func() proto.Message { + return &JoinRoundRequestPayload{} + }, func(ctx context.Context, msg proto.Message) (proto.Message, error) { + req, ok := msg.(*JoinRoundRequestPayload) + if !ok { + return nil, fmt.Errorf("unexpected request type: %T", msg) + } + + return impl.JoinRoundRequest(ctx, req) + }) + r.Handle("roundwire.RoundMailboxService", "SubmitNoncesRequest", func() proto.Message { + return &SubmitNoncesPayload{} + }, func(ctx context.Context, msg proto.Message) (proto.Message, error) { + req, ok := msg.(*SubmitNoncesPayload) + if !ok { + return nil, fmt.Errorf("unexpected request type: %T", msg) + } + + return impl.SubmitNoncesRequest(ctx, req) + }) + r.Handle("roundwire.RoundMailboxService", "SubmitPartialSigRequest", func() proto.Message { + return &SubmitPartialSigsPayload{} + }, func(ctx context.Context, msg proto.Message) (proto.Message, error) { + req, ok := msg.(*SubmitPartialSigsPayload) + if !ok { + return nil, fmt.Errorf("unexpected request type: %T", msg) + } + + return impl.SubmitPartialSigRequest(ctx, req) + }) + r.Handle("roundwire.RoundMailboxService", "SubmitForfeitSigRequest", func() proto.Message { + return &SubmitForfeitSigsPayload{} + }, func(ctx context.Context, msg proto.Message) (proto.Message, error) { + req, ok := msg.(*SubmitForfeitSigsPayload) + if !ok { + return nil, fmt.Errorf("unexpected request type: %T", msg) + } + + return impl.SubmitForfeitSigRequest(ctx, req) + }) + r.Handle("roundwire.RoundMailboxService", "SubmitVTXOForfeitSigsRequest", func() proto.Message { + return &SubmitVTXOForfeitSigsPayload{} + }, func(ctx context.Context, msg proto.Message) (proto.Message, error) { + req, ok := msg.(*SubmitVTXOForfeitSigsPayload) + if !ok { + return nil, fmt.Errorf("unexpected request type: %T", msg) + } + + return impl.SubmitVTXOForfeitSigsRequest(ctx, req) + }) + r.Handle("roundwire.RoundMailboxService", "ClientErrorResp", func() proto.Message { + return &ClientErrorRespPayload{} + }, func(ctx context.Context, msg proto.Message) (proto.Message, error) { + req, ok := msg.(*ClientErrorRespPayload) + if !ok { + return nil, fmt.Errorf("unexpected request type: %T", msg) + } + + return impl.ClientErrorResp(ctx, req) + }) + r.Handle("roundwire.RoundMailboxService", "ClientSuccessResp", func() proto.Message { + return &ClientSuccessRespPayload{} + }, func(ctx context.Context, msg proto.Message) (proto.Message, error) { + req, ok := msg.(*ClientSuccessRespPayload) + if !ok { + return nil, fmt.Errorf("unexpected request type: %T", msg) + } + + return impl.ClientSuccessResp(ctx, req) + }) + r.Handle("roundwire.RoundMailboxService", "ClientAwaitingInputSigsResp", func() proto.Message { + return &ClientAwaitingInputSigsRespPayload{} + }, func(ctx context.Context, msg proto.Message) (proto.Message, error) { + req, ok := msg.(*ClientAwaitingInputSigsRespPayload) + if !ok { + return nil, fmt.Errorf("unexpected request type: %T", msg) + } + + return impl.ClientAwaitingInputSigsResp(ctx, req) + }) + r.Handle("roundwire.RoundMailboxService", "ClientVTXOAggNonces", func() proto.Message { + return &ClientVTXOAggNoncesPayload{} + }, func(ctx context.Context, msg proto.Message) (proto.Message, error) { + req, ok := msg.(*ClientVTXOAggNoncesPayload) + if !ok { + return nil, fmt.Errorf("unexpected request type: %T", msg) + } + + return impl.ClientVTXOAggNonces(ctx, req) + }) + r.Handle("roundwire.RoundMailboxService", "ClientVTXOAggSigs", func() proto.Message { + return &ClientVTXOAggSigsPayload{} + }, func(ctx context.Context, msg proto.Message) (proto.Message, error) { + req, ok := msg.(*ClientVTXOAggSigsPayload) + if !ok { + return nil, fmt.Errorf("unexpected request type: %T", msg) + } + + return impl.ClientVTXOAggSigs(ctx, req) + }) + r.Handle("roundwire.RoundMailboxService", "ClientBatchInfo", func() proto.Message { + return &ClientBatchInfoPayload{} + }, func(ctx context.Context, msg proto.Message) (proto.Message, error) { + req, ok := msg.(*ClientBatchInfoPayload) + if !ok { + return nil, fmt.Errorf("unexpected request type: %T", msg) + } + + return impl.ClientBatchInfo(ctx, req) + }) + r.Handle("roundwire.RoundMailboxService", "ClientRoundFailedResp", func() proto.Message { + return &ClientRoundFailedRespPayload{} + }, func(ctx context.Context, msg proto.Message) (proto.Message, error) { + req, ok := msg.(*ClientRoundFailedRespPayload) + if !ok { + return nil, fmt.Errorf("unexpected request type: %T", msg) + } + + return impl.ClientRoundFailedResp(ctx, req) + }) +} + +// JoinRoundRequest calls the JoinRoundRequest RPC. +func (c *RoundMailboxServiceMailboxClient) JoinRoundRequest(ctx context.Context, req *JoinRoundRequestPayload, opts ...rpc.RPCOptions) (*emptypb.Empty, error) { + var opt rpc.RPCOptions + if len(opts) > 0 { + opt = opts[0] + } + + result, err := c.C.SendRPC(ctx, rpc.ServiceMethod{ + Service: "roundwire.RoundMailboxService", + Method: "JoinRoundRequest", + }, req, opt) + if err != nil { + return nil, err + } + + resp := new(emptypb.Empty) + if err := c.C.AwaitRPC(ctx, result.CorrelationID, resp); err != nil { + return nil, err + } + + return resp, nil +} + +// SubmitNoncesRequest calls the SubmitNoncesRequest RPC. +func (c *RoundMailboxServiceMailboxClient) SubmitNoncesRequest(ctx context.Context, req *SubmitNoncesPayload, opts ...rpc.RPCOptions) (*emptypb.Empty, error) { + var opt rpc.RPCOptions + if len(opts) > 0 { + opt = opts[0] + } + + result, err := c.C.SendRPC(ctx, rpc.ServiceMethod{ + Service: "roundwire.RoundMailboxService", + Method: "SubmitNoncesRequest", + }, req, opt) + if err != nil { + return nil, err + } + + resp := new(emptypb.Empty) + if err := c.C.AwaitRPC(ctx, result.CorrelationID, resp); err != nil { + return nil, err + } + + return resp, nil +} + +// SubmitPartialSigRequest calls the SubmitPartialSigRequest RPC. +func (c *RoundMailboxServiceMailboxClient) SubmitPartialSigRequest(ctx context.Context, req *SubmitPartialSigsPayload, opts ...rpc.RPCOptions) (*emptypb.Empty, error) { + var opt rpc.RPCOptions + if len(opts) > 0 { + opt = opts[0] + } + + result, err := c.C.SendRPC(ctx, rpc.ServiceMethod{ + Service: "roundwire.RoundMailboxService", + Method: "SubmitPartialSigRequest", + }, req, opt) + if err != nil { + return nil, err + } + + resp := new(emptypb.Empty) + if err := c.C.AwaitRPC(ctx, result.CorrelationID, resp); err != nil { + return nil, err + } + + return resp, nil +} + +// SubmitForfeitSigRequest calls the SubmitForfeitSigRequest RPC. +func (c *RoundMailboxServiceMailboxClient) SubmitForfeitSigRequest(ctx context.Context, req *SubmitForfeitSigsPayload, opts ...rpc.RPCOptions) (*emptypb.Empty, error) { + var opt rpc.RPCOptions + if len(opts) > 0 { + opt = opts[0] + } + + result, err := c.C.SendRPC(ctx, rpc.ServiceMethod{ + Service: "roundwire.RoundMailboxService", + Method: "SubmitForfeitSigRequest", + }, req, opt) + if err != nil { + return nil, err + } + + resp := new(emptypb.Empty) + if err := c.C.AwaitRPC(ctx, result.CorrelationID, resp); err != nil { + return nil, err + } + + return resp, nil +} + +// SubmitVTXOForfeitSigsRequest calls the SubmitVTXOForfeitSigsRequest RPC. +func (c *RoundMailboxServiceMailboxClient) SubmitVTXOForfeitSigsRequest(ctx context.Context, req *SubmitVTXOForfeitSigsPayload, opts ...rpc.RPCOptions) (*emptypb.Empty, error) { + var opt rpc.RPCOptions + if len(opts) > 0 { + opt = opts[0] + } + + result, err := c.C.SendRPC(ctx, rpc.ServiceMethod{ + Service: "roundwire.RoundMailboxService", + Method: "SubmitVTXOForfeitSigsRequest", + }, req, opt) + if err != nil { + return nil, err + } + + resp := new(emptypb.Empty) + if err := c.C.AwaitRPC(ctx, result.CorrelationID, resp); err != nil { + return nil, err + } + + return resp, nil +} + +// ClientErrorResp calls the ClientErrorResp RPC. +func (c *RoundMailboxServiceMailboxClient) ClientErrorResp(ctx context.Context, req *ClientErrorRespPayload, opts ...rpc.RPCOptions) (*emptypb.Empty, error) { + var opt rpc.RPCOptions + if len(opts) > 0 { + opt = opts[0] + } + + result, err := c.C.SendRPC(ctx, rpc.ServiceMethod{ + Service: "roundwire.RoundMailboxService", + Method: "ClientErrorResp", + }, req, opt) + if err != nil { + return nil, err + } + + resp := new(emptypb.Empty) + if err := c.C.AwaitRPC(ctx, result.CorrelationID, resp); err != nil { + return nil, err + } + + return resp, nil +} + +// ClientSuccessResp calls the ClientSuccessResp RPC. +func (c *RoundMailboxServiceMailboxClient) ClientSuccessResp(ctx context.Context, req *ClientSuccessRespPayload, opts ...rpc.RPCOptions) (*emptypb.Empty, error) { + var opt rpc.RPCOptions + if len(opts) > 0 { + opt = opts[0] + } + + result, err := c.C.SendRPC(ctx, rpc.ServiceMethod{ + Service: "roundwire.RoundMailboxService", + Method: "ClientSuccessResp", + }, req, opt) + if err != nil { + return nil, err + } + + resp := new(emptypb.Empty) + if err := c.C.AwaitRPC(ctx, result.CorrelationID, resp); err != nil { + return nil, err + } + + return resp, nil +} + +// ClientAwaitingInputSigsResp calls the ClientAwaitingInputSigsResp RPC. +func (c *RoundMailboxServiceMailboxClient) ClientAwaitingInputSigsResp(ctx context.Context, req *ClientAwaitingInputSigsRespPayload, opts ...rpc.RPCOptions) (*emptypb.Empty, error) { + var opt rpc.RPCOptions + if len(opts) > 0 { + opt = opts[0] + } + + result, err := c.C.SendRPC(ctx, rpc.ServiceMethod{ + Service: "roundwire.RoundMailboxService", + Method: "ClientAwaitingInputSigsResp", + }, req, opt) + if err != nil { + return nil, err + } + + resp := new(emptypb.Empty) + if err := c.C.AwaitRPC(ctx, result.CorrelationID, resp); err != nil { + return nil, err + } + + return resp, nil +} + +// ClientVTXOAggNonces calls the ClientVTXOAggNonces RPC. +func (c *RoundMailboxServiceMailboxClient) ClientVTXOAggNonces(ctx context.Context, req *ClientVTXOAggNoncesPayload, opts ...rpc.RPCOptions) (*emptypb.Empty, error) { + var opt rpc.RPCOptions + if len(opts) > 0 { + opt = opts[0] + } + + result, err := c.C.SendRPC(ctx, rpc.ServiceMethod{ + Service: "roundwire.RoundMailboxService", + Method: "ClientVTXOAggNonces", + }, req, opt) + if err != nil { + return nil, err + } + + resp := new(emptypb.Empty) + if err := c.C.AwaitRPC(ctx, result.CorrelationID, resp); err != nil { + return nil, err + } + + return resp, nil +} + +// ClientVTXOAggSigs calls the ClientVTXOAggSigs RPC. +func (c *RoundMailboxServiceMailboxClient) ClientVTXOAggSigs(ctx context.Context, req *ClientVTXOAggSigsPayload, opts ...rpc.RPCOptions) (*emptypb.Empty, error) { + var opt rpc.RPCOptions + if len(opts) > 0 { + opt = opts[0] + } + + result, err := c.C.SendRPC(ctx, rpc.ServiceMethod{ + Service: "roundwire.RoundMailboxService", + Method: "ClientVTXOAggSigs", + }, req, opt) + if err != nil { + return nil, err + } + + resp := new(emptypb.Empty) + if err := c.C.AwaitRPC(ctx, result.CorrelationID, resp); err != nil { + return nil, err + } + + return resp, nil +} + +// ClientBatchInfo calls the ClientBatchInfo RPC. +func (c *RoundMailboxServiceMailboxClient) ClientBatchInfo(ctx context.Context, req *ClientBatchInfoPayload, opts ...rpc.RPCOptions) (*emptypb.Empty, error) { + var opt rpc.RPCOptions + if len(opts) > 0 { + opt = opts[0] + } + + result, err := c.C.SendRPC(ctx, rpc.ServiceMethod{ + Service: "roundwire.RoundMailboxService", + Method: "ClientBatchInfo", + }, req, opt) + if err != nil { + return nil, err + } + + resp := new(emptypb.Empty) + if err := c.C.AwaitRPC(ctx, result.CorrelationID, resp); err != nil { + return nil, err + } + + return resp, nil +} + +// ClientRoundFailedResp calls the ClientRoundFailedResp RPC. +func (c *RoundMailboxServiceMailboxClient) ClientRoundFailedResp(ctx context.Context, req *ClientRoundFailedRespPayload, opts ...rpc.RPCOptions) (*emptypb.Empty, error) { + var opt rpc.RPCOptions + if len(opts) > 0 { + opt = opts[0] + } + + result, err := c.C.SendRPC(ctx, rpc.ServiceMethod{ + Service: "roundwire.RoundMailboxService", + Method: "ClientRoundFailedResp", + }, req, opt) + if err != nil { + return nil, err + } + + resp := new(emptypb.Empty) + if err := c.C.AwaitRPC(ctx, result.CorrelationID, resp); err != nil { + return nil, err + } + + return resp, nil +} diff --git a/scripts/gen_protos.sh b/scripts/gen_protos.sh index 161f22b17..a9e0a8d27 100755 --- a/scripts/gen_protos.sh +++ b/scripts/gen_protos.sh @@ -60,6 +60,12 @@ generate "arkrpc" # Generate daemonrpc protos for the client daemon's own gRPC API. generate "daemonrpc" +# Generate toy OOR mailbox unary stubs. +generate "oorwire" + +# Generate round mailbox payload/message stubs. +generate "roundwire" + # Generate adminrpc protos if present. if [ -d "adminrpc" ]; then generate "adminrpc" diff --git a/sdk/address_test.go b/sdk/address_test.go new file mode 100644 index 000000000..1f30a07c7 --- /dev/null +++ b/sdk/address_test.go @@ -0,0 +1,66 @@ +package sdk + +import ( + "testing" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcutil/bech32" + "github.com/stretchr/testify/require" +) + +func TestEncodeDecodeReceiveAddress(t *testing.T) { + t.Parallel() + + operatorPriv, err := btcec.NewPrivateKey() + require.NoError(t, err) + + recipientPriv, err := btcec.NewPrivateKey() + require.NoError(t, err) + + addr, err := encodeReceiveAddress( + "tark", operatorPriv.PubKey(), recipientPriv.PubKey(), 144, + ) + require.NoError(t, err) + require.NotEmpty(t, addr) + + decoded, err := decodeReceiveAddress(addr) + require.NoError(t, err) + require.Equal(t, "tark", decoded.hrp) + require.Equal(t, uint32(144), decoded.exitDelay) + require.Equal(t, schnorr.SerializePubKey(operatorPriv.PubKey()), + schnorr.SerializePubKey(decoded.operatorKey)) + require.Equal(t, schnorr.SerializePubKey(recipientPriv.PubKey()), + schnorr.SerializePubKey(decoded.recipientKey)) +} + +func TestDecodeReceiveAddressRejectsUnsupportedVersion(t *testing.T) { + t.Parallel() + + operatorPriv, err := btcec.NewPrivateKey() + require.NoError(t, err) + + recipientPriv, err := btcec.NewPrivateKey() + require.NoError(t, err) + + addr, err := encodeReceiveAddress( + "tark", operatorPriv.PubKey(), recipientPriv.PubKey(), 144, + ) + require.NoError(t, err) + + hrp, addrData, err := bech32.DecodeNoLimit(addr) + require.NoError(t, err) + + payload, err := bech32.ConvertBits(addrData, 5, 8, false) + require.NoError(t, err) + payload[0] = receiveAddressVersionV1 + 1 + + modifiedData, err := bech32.ConvertBits(payload, 8, 5, true) + require.NoError(t, err) + + modifiedAddr, err := bech32.EncodeM(hrp, modifiedData) + require.NoError(t, err) + + _, err = decodeReceiveAddress(modifiedAddr) + require.ErrorContains(t, err, "unsupported recipient address version") +} diff --git a/sdk/client.go b/sdk/client.go new file mode 100644 index 000000000..dddf159c2 --- /dev/null +++ b/sdk/client.go @@ -0,0 +1,792 @@ +package sdk + +import ( + "bytes" + "context" + "encoding/binary" + "encoding/hex" + "fmt" + "sync" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/bech32" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/lightninglabs/darepo-client/baselib/actor" + "github.com/lightninglabs/darepo-client/lib/scripts" + clienttree "github.com/lightninglabs/darepo-client/lib/tree" + oortx "github.com/lightninglabs/darepo-client/lib/tx/oor" + mailboxrpc "github.com/lightninglabs/darepo-client/mailbox/rpc" + clientoor "github.com/lightninglabs/darepo-client/oor" + "github.com/lightninglabs/darepo-client/vtxo" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/keychain" +) + +// IncomingTransfer is a wallet-targeted incoming transfer payload fetched from +// the operator side (for now via a polling adapter owned by the caller). +type IncomingTransfer struct { + SessionID clientoor.SessionID + ArkPSBT *psbt.Packet +} + +// VTXOStateStore is the VTXO persistence contract needed by SDK transfer +// flows. +type VTXOStateStore interface { + ListLiveVTXOs(ctx context.Context) ([]*vtxo.Descriptor, error) + GetVTXO(ctx context.Context, outpoint wire.OutPoint) ( + *vtxo.Descriptor, error) + SaveVTXO(ctx context.Context, desc *vtxo.Descriptor) error +} + +// Config wires the embedded SDK client to a running local runtime. +// +// The runtime ownership model is explicit: the caller owns actor startup and +// connector lifecycle; the SDK owns only its local OOR actor. +type Config struct { + // RequestRoundOutputs forwards output registration to the local round + // runtime. + RequestRoundOutputs func(ctx context.Context, + amounts []btcutil.Amount) error + + // TriggerRoundJoin asks the local round runtime to emit a join request. + TriggerRoundJoin func(ctx context.Context) error + + // LastCompletedRoundID returns the latest local confirmed round ID. + LastCompletedRoundID func(ctx context.Context) (string, error) + + // RPCClient is the mailbox unary RPC client bound to the operator. + RPCClient mailboxrpc.RPCClient + + // Signer signs checkpoint inputs for outgoing OOR finalize. + Signer input.Signer + + // SpendMarker marks local outgoing inputs spent after finalize. + SpendMarker clientoor.InputSpendMarker + + // DeliveryStore backs the SDK-owned local OOR actor. + DeliveryStore actor.DeliveryStore + + // VTXOStore is the local persisted VTXO state. + VTXOStore VTXOStateStore + + // OperatorKey is the active operator key used in VTXO script + // derivations. + OperatorKey *btcec.PublicKey + + // DeriveReceiveKey derives a fresh receive key for incoming OOR + // outputs. + DeriveReceiveKey func(ctx context.Context) (keychain.KeyDescriptor, + error) + + // ReceiveAddressHRP is the bech32m human-readable prefix used for + // SDK receive addresses. + ReceiveAddressHRP string + + // ReceiveExitDelay is the unilateral CSV delay encoded in newly + // generated receive addresses. + ReceiveExitDelay uint32 + + // FetchIncomingTransfers resolves incoming transfer payloads for one + // recipient script. + FetchIncomingTransfers func(ctx context.Context, + recipientPkScript []byte) ([]IncomingTransfer, error) +} + +// Client is a high-level embedded SDK façade over a running local runtime. +type Client struct { + cfg Config + + oorActor *clientoor.OORClientActor + + receiveMu sync.Mutex + receiveTargets map[string]*receiveTarget +} + +// receiveTarget keeps recipient-owned metadata for one generated +// receive address. +type receiveTarget struct { + address string + + keyDesc keychain.KeyDescriptor + + pkScript []byte + exitDelay uint32 + + processed map[clientoor.SessionID]struct{} +} + +// New creates a new SDK client with runtime dependency injection. +func New(cfg Config) (*Client, error) { + if cfg.VTXOStore == nil { + return nil, fmt.Errorf("vtxo store is required") + } + if cfg.LastCompletedRoundID == nil { + return nil, fmt.Errorf( + "last completed round id callback is required", + ) + } + if cfg.TriggerRoundJoin == nil { + return nil, fmt.Errorf("round join callback is required") + } + if cfg.RequestRoundOutputs == nil { + return nil, fmt.Errorf( + "round output registration callback is required", + ) + } + if cfg.RPCClient == nil { + return nil, fmt.Errorf("rpc client is required") + } + if cfg.Signer == nil { + return nil, fmt.Errorf("signer is required") + } + if cfg.SpendMarker == nil { + return nil, fmt.Errorf("spend marker is required") + } + if cfg.DeliveryStore == nil { + return nil, fmt.Errorf("delivery store is required") + } + if cfg.OperatorKey == nil { + return nil, fmt.Errorf("operator key is required") + } + if cfg.DeriveReceiveKey == nil { + return nil, fmt.Errorf("receive key deriver is required") + } + if cfg.ReceiveAddressHRP == "" { + return nil, fmt.Errorf("receive address hrp is required") + } + if cfg.ReceiveExitDelay == 0 { + return nil, fmt.Errorf("receive exit delay is required") + } + if cfg.FetchIncomingTransfers == nil { + return nil, fmt.Errorf("incoming transfer fetcher is required") + } + + return &Client{ + cfg: cfg, + receiveTargets: make(map[string]*receiveTarget), + }, nil +} + +const ( + receiveAddressVersionV1 = uint8(1) + + receiveAddressPayloadLen = 1 + 32 + 32 + 4 +) + +type decodedReceiveAddress struct { + hrp string + + operatorKey *btcec.PublicKey + recipientKey *btcec.PublicKey + + exitDelay uint32 +} + +// Stop releases SDK-owned resources. +func (c *Client) Stop() { + if c.oorActor != nil { + c.oorActor.Stop() + c.oorActor = nil + } +} + +// RequestRoundOutputs registers desired output amounts for the next round. +func (c *Client) RequestRoundOutputs(ctx context.Context, + amounts []btcutil.Amount) error { + + return c.cfg.RequestRoundOutputs(ctx, amounts) +} + +// JoinRound asks the local round runtime to emit a join request. +func (c *Client) JoinRound(ctx context.Context) error { + return c.cfg.TriggerRoundJoin(ctx) +} + +// CompletedRoundID returns the latest locally confirmed round ID. +func (c *Client) CompletedRoundID(ctx context.Context) (string, error) { + return c.cfg.LastCompletedRoundID(ctx) +} + +// LiveVTXOs returns all local live (non-terminal) VTXOs. +func (c *Client) LiveVTXOs(ctx context.Context) ([]*vtxo.Descriptor, error) { + return c.cfg.VTXOStore.ListLiveVTXOs(ctx) +} + +// LiveBalance returns the sum of local live VTXO amounts. +func (c *Client) LiveBalance(ctx context.Context) (btcutil.Amount, error) { + live, err := c.LiveVTXOs(ctx) + if err != nil { + return 0, err + } + + var total btcutil.Amount + for i := range live { + total += live[i].Amount + } + + return total, nil +} + +// NewReceiveAddress derives a fresh recipient address for incoming OOR +// transfers and tracks it for SyncIncoming. +func (c *Client) NewReceiveAddress(ctx context.Context) (string, error) { + recipientKey, err := c.cfg.DeriveReceiveKey(ctx) + if err != nil { + return "", fmt.Errorf("derive receive key: %w", err) + } + if recipientKey.PubKey == nil { + return "", fmt.Errorf("receive key is missing pubkey") + } + + address, err := encodeReceiveAddress( + c.cfg.ReceiveAddressHRP, c.cfg.OperatorKey, + recipientKey.PubKey, c.cfg.ReceiveExitDelay, + ) + if err != nil { + return "", err + } + + recipientPkScript, err := recipientVTXOPkScript( + recipientKey.PubKey, c.cfg.OperatorKey, c.cfg.ReceiveExitDelay, + ) + if err != nil { + return "", err + } + + scriptKey := hex.EncodeToString(recipientPkScript) + + c.receiveMu.Lock() + defer c.receiveMu.Unlock() + + target := c.receiveTargets[scriptKey] + if target == nil { + target = &receiveTarget{ + processed: make(map[clientoor.SessionID]struct{}), + } + c.receiveTargets[scriptKey] = target + } + + target.address = address + target.keyDesc = recipientKey + target.pkScript = append([]byte(nil), recipientPkScript...) + target.exitDelay = c.cfg.ReceiveExitDelay + + return address, nil +} + +// SyncIncoming fetches and materializes all unprocessed incoming OOR transfers +// addressed to locally generated receive addresses. +func (c *Client) SyncIncoming(ctx context.Context) (int, error) { + targets := c.receiveTargetSnapshots() + if len(targets) == 0 { + return 0, nil + } + + roundID, err := c.cfg.LastCompletedRoundID(ctx) + if err != nil { + return 0, fmt.Errorf("recipient completed round id: %w", err) + } + + processed := 0 + + for i := range targets { + target := targets[i] + + incoming, err := c.cfg.FetchIncomingTransfers( + ctx, target.pkScript, + ) + if err != nil { + return processed, fmt.Errorf("list incoming transfers "+ + "for address %s: %w", target.address, err) + } + + for j := range incoming { + transfer := incoming[j] + if transfer.ArkPSBT == nil || + transfer.ArkPSBT.UnsignedTx == nil { + + return processed, fmt.Errorf( + "incoming transfer missing ark "+ + "psbt for address %s", + target.address, + ) + } + + sessionID := transfer.SessionID + if sessionID == (clientoor.SessionID{}) { + sessionID = clientoor.SessionID( + transfer.ArkPSBT.UnsignedTx.TxHash(), + ) + } + + if c.isTransferProcessed(target.scriptKey, sessionID) { + continue + } + + err = c.materializeIncomingTransfer( + ctx, target, roundID, sessionID, + transfer.ArkPSBT, + ) + if err != nil { + return processed, err + } + + c.markTransferProcessed(target.scriptKey, sessionID) + processed++ + } + } + + return processed, nil +} + +// SendOORPayment performs a single-input outgoing OOR transfer to a recipient +// address. Recipient materialization is handled by the recipient runtime via +// SyncIncoming. +func (c *Client) SendOORPayment(ctx context.Context, recipientAddress string, + amount btcutil.Amount) error { + + decodedAddress, err := decodeReceiveAddress(recipientAddress) + if err != nil { + return err + } + if decodedAddress.hrp != c.cfg.ReceiveAddressHRP { + return fmt.Errorf("recipient address hrp mismatch: "+ + "expected=%s got=%s", c.cfg.ReceiveAddressHRP, + decodedAddress.hrp) + } + expectedOperator := schnorr.SerializePubKey(c.cfg.OperatorKey) + gotOperator := schnorr.SerializePubKey(decodedAddress.operatorKey) + if !bytes.Equal(expectedOperator, gotOperator) { + return fmt.Errorf("recipient address operator key mismatch") + } + if amount <= 0 { + return fmt.Errorf("amount must be positive") + } + + if err := c.ensureOORActor(); err != nil { + return err + } + + senderLiveVTXOs, err := c.cfg.VTXOStore.ListLiveVTXOs(ctx) + if err != nil { + return fmt.Errorf("list sender live vtxos: %w", err) + } + if len(senderLiveVTXOs) == 0 { + return fmt.Errorf("sender has no live vtxos") + } + + input, err := selectSingleInput(senderLiveVTXOs, amount) + if err != nil { + return err + } + + recipientPkScript, err := recipientVTXOPkScript( + decodedAddress.recipientKey, decodedAddress.operatorKey, + decodedAddress.exitDelay, + ) + if err != nil { + return err + } + + startResp := c.oorActor.Receive( + ctx, &clientoor.StartTransferRequest{ + Policy: scripts.CheckpointPolicy{ + OperatorKey: c.cfg.OperatorKey, + CSVDelay: input.RelativeExpiry, + }, + Inputs: []clientoor.TransferInput{{ + VTXO: input, + OwnerLeafScript: []byte{txscript.OP_1}, + }}, + Recipients: []oortx.RecipientOutput{{ + PkScript: recipientPkScript, + Value: amount, + }}, + }, + ) + if startResp.IsErr() { + return fmt.Errorf("start oor transfer: %w", startResp.Err()) + } + + senderInput, err := c.cfg.VTXOStore.GetVTXO(ctx, input.Outpoint) + if err != nil { + return fmt.Errorf("load sender input after oor: %w", err) + } + if senderInput.Status != vtxo.VTXOStatusSpent { + return fmt.Errorf("sender input not marked spent after oor") + } + + return nil +} + +func (c *Client) ensureOORActor() error { + if c.oorActor != nil { + return nil + } + + c.oorActor = clientoor.NewOORClientActor(clientoor.ClientActorCfg{ + OutboxHandler: &clientoor.MailboxOutboxHandler{ + RPCClient: c.cfg.RPCClient, + Signer: c.cfg.Signer, + SpendMarker: c.cfg.SpendMarker, + }, + DeliveryStore: c.cfg.DeliveryStore, + }) + + return nil +} + +type receiveTargetSnapshot struct { + scriptKey string + address string + + keyDesc keychain.KeyDescriptor + + pkScript []byte + exitDelay uint32 +} + +func (c *Client) receiveTargetSnapshots() []receiveTargetSnapshot { + c.receiveMu.Lock() + defer c.receiveMu.Unlock() + + targets := make([]receiveTargetSnapshot, 0, len(c.receiveTargets)) + for scriptKey, target := range c.receiveTargets { + targets = append(targets, receiveTargetSnapshot{ + scriptKey: scriptKey, + address: target.address, + keyDesc: target.keyDesc, + pkScript: append([]byte(nil), target.pkScript...), + exitDelay: target.exitDelay, + }) + } + + return targets +} + +func (c *Client) isTransferProcessed(scriptKey string, + sessionID clientoor.SessionID) bool { + + c.receiveMu.Lock() + defer c.receiveMu.Unlock() + + target := c.receiveTargets[scriptKey] + if target == nil { + return false + } + + _, ok := target.processed[sessionID] + + return ok +} + +func (c *Client) markTransferProcessed(scriptKey string, + sessionID clientoor.SessionID) { + + c.receiveMu.Lock() + defer c.receiveMu.Unlock() + + target := c.receiveTargets[scriptKey] + if target == nil { + return + } + + if target.processed == nil { + target.processed = make(map[clientoor.SessionID]struct{}) + } + + target.processed[sessionID] = struct{}{} +} + +func (c *Client) materializeIncomingTransfer( + ctx context.Context, target receiveTargetSnapshot, + roundID string, sessionID clientoor.SessionID, + arkPSBT *psbt.Packet) error { + + receiveSession, receiveOutbox, err := clientoor.DriveIncomingTransfer( + ctx, sessionID, arkPSBT, + ) + if err != nil { + return fmt.Errorf("drive incoming transfer for address %s: %w", + target.address, err) + } + + incomingHandler := &incomingReceiveOutboxHandler{ + recipientKey: target.keyDesc, + operatorKey: c.cfg.OperatorKey, + exitDelay: target.exitDelay, + roundID: roundID, + vtxoStore: c.cfg.VTXOStore, + } + + if err := driveOutboxToFSM( + ctx, receiveSession.ID, receiveSession.FSM, + incomingHandler, receiveOutbox, + ); err != nil { + return fmt.Errorf("process incoming outbox for address %s: %w", + target.address, err) + } + + if len(incomingHandler.materialized) == 0 { + return fmt.Errorf("incoming materialization produced no "+ + "vtxos for address %s", target.address) + } + + return nil +} + +func selectSingleInput(vtxos []*vtxo.Descriptor, + amount btcutil.Amount) (*vtxo.Descriptor, error) { + + for i := range vtxos { + desc := vtxos[i] + if desc.Amount != amount { + continue + } + + if desc.ClientKey.PubKey == nil { + return nil, fmt.Errorf("input client key is missing") + } + if desc.OperatorKey == nil { + return nil, fmt.Errorf("input operator key is missing") + } + + return desc, nil + } + + return nil, fmt.Errorf( + "no single live vtxo matches requested amount=%d", amount, + ) +} + +func recipientVTXOPkScript(ownerKey, operatorKey *btcec.PublicKey, + exitDelay uint32) ([]byte, error) { + + tapKey, err := scripts.VTXOTapKey(ownerKey, operatorKey, exitDelay) + if err != nil { + return nil, fmt.Errorf("derive recipient tap key: %w", err) + } + + pkScript, err := txscript.PayToTaprootScript(tapKey) + if err != nil { + return nil, fmt.Errorf("derive recipient pkScript: %w", err) + } + + return pkScript, nil +} + +func encodeReceiveAddress(hrp string, operatorKey, + recipientKey *btcec.PublicKey, exitDelay uint32) (string, error) { + + if hrp == "" { + return "", fmt.Errorf("receive address hrp is required") + } + if operatorKey == nil { + return "", fmt.Errorf("operator key is required") + } + if recipientKey == nil { + return "", fmt.Errorf("recipient key is required") + } + if exitDelay == 0 { + return "", fmt.Errorf( + "receive address exit delay must be positive", + ) + } + + payload := make([]byte, 0, receiveAddressPayloadLen) + payload = append(payload, receiveAddressVersionV1) + payload = append(payload, schnorr.SerializePubKey(operatorKey)...) + payload = append(payload, schnorr.SerializePubKey(recipientKey)...) + + delayBytes := make([]byte, 4) + binary.BigEndian.PutUint32(delayBytes, exitDelay) + payload = append(payload, delayBytes...) + + addrData, err := bech32.ConvertBits(payload, 8, 5, true) + if err != nil { + return "", err + } + + address, err := bech32.EncodeM(hrp, addrData) + if err != nil { + return "", err + } + + return address, nil +} + +func decodeReceiveAddress(address string) (*decodedReceiveAddress, error) { + if address == "" { + return nil, fmt.Errorf("recipient address is required") + } + + hrp, addrData, err := bech32.DecodeNoLimit(address) + if err != nil { + return nil, fmt.Errorf("decode recipient address: %w", err) + } + + payload, err := bech32.ConvertBits(addrData, 5, 8, false) + if err != nil { + return nil, fmt.Errorf( + "decode recipient address payload: %w", err, + ) + } + + if len(payload) != receiveAddressPayloadLen { + return nil, fmt.Errorf("invalid recipient address payload "+ + "length: expected=%d got=%d", receiveAddressPayloadLen, + len(payload)) + } + + version := payload[0] + if version != receiveAddressVersionV1 { + return nil, fmt.Errorf("unsupported recipient address "+ + "version: %d", version) + } + + operatorKey, err := schnorr.ParsePubKey(payload[1:33]) + if err != nil { + return nil, fmt.Errorf("parse recipient address operator "+ + "key: %w", err) + } + + recipientKey, err := schnorr.ParsePubKey(payload[33:65]) + if err != nil { + return nil, fmt.Errorf("parse recipient address recipient "+ + "key: %w", err) + } + + exitDelay := binary.BigEndian.Uint32(payload[65:]) + if exitDelay == 0 { + return nil, fmt.Errorf("recipient address exit delay must be " + + "positive") + } + + return &decodedReceiveAddress{ + hrp: hrp, + operatorKey: operatorKey, + recipientKey: recipientKey, + exitDelay: exitDelay, + }, nil +} + +type incomingReceiveOutboxHandler struct { + recipientKey keychain.KeyDescriptor + operatorKey *btcec.PublicKey + exitDelay uint32 + roundID string + vtxoStore VTXOStateStore + + materialized []*vtxo.Descriptor +} + +func (h *incomingReceiveOutboxHandler) Handle(ctx context.Context, + _ clientoor.SessionID, + outbox clientoor.OutboxEvent) ([]clientoor.Event, error) { + + switch msg := outbox.(type) { + case *clientoor.IncomingTransferNotification: + return nil, nil + + case *clientoor.MaterializeIncomingVTXOsRequest: + if msg.ArkPSBT == nil || msg.ArkPSBT.UnsignedTx == nil { + return nil, fmt.Errorf("ark psbt must be provided") + } + if h.vtxoStore == nil { + return nil, fmt.Errorf("vtxo store is required") + } + if h.roundID == "" { + return nil, fmt.Errorf("round id is required") + } + + arkTxid := msg.ArkPSBT.UnsignedTx.TxHash() + + for i := range msg.Recipients { + recipient := msg.Recipients[i] + + desc := &vtxo.Descriptor{ + Outpoint: wire.OutPoint{ + Hash: arkTxid, + Index: recipient.OutputIndex, + }, + Amount: recipient.Value, + PkScript: recipient.PkScript, + ClientKey: h.recipientKey, + OperatorKey: h.operatorKey, + RelativeExpiry: h.exitDelay, + RoundID: h.roundID, + CommitmentTxID: arkTxid, + TreePath: &clienttree.Tree{}, + Status: vtxo.VTXOStatusLive, + } + + err := h.vtxoStore.SaveVTXO(ctx, desc) + if err != nil { + existing, getErr := h.vtxoStore.GetVTXO( + ctx, desc.Outpoint, + ) + if getErr != nil || existing == nil { + return nil, err + } + + if existing.Amount != desc.Amount { + return nil, err + } + + desc = existing + } + + h.materialized = append(h.materialized, desc) + } + + if len(h.materialized) == 0 { + return nil, fmt.Errorf( + "no incoming recipients materialized", + ) + } + + return []clientoor.Event{ + &clientoor.IncomingHandledEvent{}, + }, nil + + case *clientoor.SendIncomingAckRequest: + return []clientoor.Event{ + &clientoor.IncomingAckSentEvent{}, + }, nil + + default: + return nil, nil + } +} + +func driveOutboxToFSM(ctx context.Context, sessionID clientoor.SessionID, + fsm *clientoor.StateMachine, handler clientoor.OutboxHandler, + outbox []clientoor.OutboxEvent) error { + + for i := range outbox { + followUps, err := handler.Handle(ctx, sessionID, outbox[i]) + if err != nil { + return err + } + + for j := range followUps { + result := fsm.AskEvent(ctx, followUps[j]).Await(ctx) + if result.IsErr() { + return result.Err() + } + + next := result.UnwrapOr(nil) + if err := driveOutboxToFSM( + ctx, sessionID, fsm, handler, next, + ); err != nil { + return err + } + } + } + + return nil +} diff --git a/serverconn/actor.go b/serverconn/actor.go index 88ed37172..ac2f27ae0 100644 --- a/serverconn/actor.go +++ b/serverconn/actor.go @@ -36,6 +36,8 @@ type ( envelopeRecordTLV = tlv.TlvType2 msgIDRecordTLV = tlv.TlvType3 idempotencyRecordTLV = tlv.TlvType4 + serviceRecordTLV = tlv.TlvType5 + methodRecordTLV = tlv.TlvType6 ) // ServerMessage is an interface that client FSM outbox messages must implement @@ -47,6 +49,16 @@ type ServerMessage interface { ToProto() proto.Message } +// RoutedServerMessage is implemented by outbox messages that carry explicit +// mailbox RPC routing metadata for EVENT envelopes. +type RoutedServerMessage interface { + // RPCService returns the mailbox service name. + RPCService() string + + // RPCMethod returns the mailbox method name. + RPCMethod() string +} + // InboundServerMessage is implemented by actor messages that arrive from the // server via the mailbox ingress loop. FromProto mirrors the ToProto method // on ServerMessage, completing the bidirectional proto<->actor message @@ -121,6 +133,12 @@ type SendClientEventRequest struct { // IdempotencyKey identifies the semantic operation for remote dedupe. // Retries of the same persisted request must reuse this key. IdempotencyKey string + + // Service is the mailbox RPC service used for EVENT routing. + Service string + + // Method is the mailbox RPC method used for EVENT routing. + Method string } // MessageType returns a human-readable type name for logging. @@ -144,7 +162,12 @@ func (m *SendClientEventRequest) TLVType() tlv.Type { // proto↔bytes conversion inside the TLV record, keeping the codec contract // simple and uniform across message types. func (m *SendClientEventRequest) Encode(w io.Writer) error { - anyMsg, err := anypb.New(m.Message.ToProto()) + protoMsg := m.Message.ToProto() + if protoMsg == nil { + return fmt.Errorf("nil proto message") + } + + anyMsg, err := anypb.New(protoMsg) if err != nil { return fmt.Errorf("wrap proto in Any: %w", err) } @@ -170,6 +193,20 @@ func (m *SendClientEventRequest) Encode(w io.Writer) error { StableEventIdempotencyKey(anyBytes) } idempotencyBytes := []byte(idempotencyKey) + service := m.Service + method := m.Method + if (service == "" || method == "") && m.Message != nil { + if routed, ok := m.Message.(RoutedServerMessage); ok { + if service == "" { + service = routed.RPCService() + } + if method == "" { + method = routed.RPCMethod() + } + } + } + serviceBytes := []byte(service) + methodBytes := []byte(method) payload := tlv.NewRecordT[protoPayloadRecordTLV]( mailboxconn.WrappedProto[*anypb.Any]{Val: anyMsg}, @@ -180,9 +217,16 @@ func (m *SendClientEventRequest) Encode(w io.Writer) error { idemRec := tlv.NewPrimitiveRecord[idempotencyRecordTLV]( idempotencyBytes, ) + serviceRec := tlv.NewPrimitiveRecord[serviceRecordTLV]( + serviceBytes, + ) + methodRec := tlv.NewPrimitiveRecord[methodRecordTLV]( + methodBytes, + ) stream, err := tlv.NewStream( payload.Record(), msgIDRec.Record(), idemRec.Record(), + serviceRec.Record(), methodRec.Record(), ) if err != nil { return err @@ -203,9 +247,12 @@ func (m *SendClientEventRequest) Decode(r io.Reader) error { msgIDRec := tlv.ZeroRecordT[msgIDRecordTLV, []byte]() idemRec := tlv.ZeroRecordT[idempotencyRecordTLV, []byte]() + serviceRec := tlv.ZeroRecordT[serviceRecordTLV, []byte]() + methodRec := tlv.ZeroRecordT[methodRecordTLV, []byte]() stream, err := tlv.NewStream( payload.Record(), msgIDRec.Record(), idemRec.Record(), + serviceRec.Record(), methodRec.Record(), ) if err != nil { return err @@ -218,6 +265,8 @@ func (m *SendClientEventRequest) Decode(r io.Reader) error { m.Message = &rawServerMessage{anyMsg: payload.Val.Val} m.MsgID = string(msgIDRec.Val) m.IdempotencyKey = string(idemRec.Val) + m.Service = string(serviceRec.Val) + m.Method = string(methodRec.Val) return nil } @@ -404,6 +453,12 @@ func (a *ServerConnectionActor) handleSendClientEvent(ctx context.Context, req *SendClientEventRequest) fn.Result[ServerConnResp] { protoMsg := req.Message.ToProto() + if protoMsg == nil { + return fn.Err[ServerConnResp](fmt.Errorf( + "message ToProto() returned nil for mailbox %q", + a.cfg.RemoteMailboxID, + )) + } body, err := anypb.New(protoMsg) if err != nil { @@ -414,6 +469,19 @@ func (a *ServerConnectionActor) handleSendClientEvent(ctx context.Context, msgID := req.MsgID idempotencyKey := req.IdempotencyKey + service := req.Service + method := req.Method + + if service == "" || method == "" { + if routed, ok := req.Message.(RoutedServerMessage); ok { + if service == "" { + service = routed.RPCService() + } + if method == "" { + method = routed.RPCMethod() + } + } + } // Only marshal the body bytes when we need to derive stable IDs. // On replay (both IDs already set from the persisted TLV), this @@ -449,6 +517,8 @@ func (a *ServerConnectionActor) handleSendClientEvent(ctx context.Context, Rpc: &mailboxpb.RpcMeta{ Kind: mailboxpb.RpcMeta_KIND_EVENT, ReplyTo: a.cfg.LocalMailboxID, + Service: service, + Method: method, }, }